diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6073234..fcf785e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,18 @@
+## 0.3.0
+
+* BREAKING: formatters such as `valueFormatter` now takes node in addition to value
+* BREAKING: `valueStyleBuilder` now takes node in addition to value
+* `style` in `PropertyOverrides` is now optional
+* `PropertyOverrides` can optionally specify `onSecondaryTap`, `onLongPress` and a mouse `cursor`
+* Upgrade `golden_toolkit` to 0.15.0
+* Upgrade example dependencies
+
+## 0.2.0
+
+* Upgrade `scrollable_positioned_list` to 0.3.2
+* Upgrade `provider` to 6.0.3
+* Upgrade `rows_lint` to 0.1.1
+
 ## 0.1.0
 
 * Initial release.
diff --git a/README.md b/README.md
index 2d8f6a2..f0d63cd 100644
--- a/README.md
+++ b/README.md
@@ -164,7 +164,7 @@ can be changed with a formatter:
 ```dart
 JsonDataExplorer(
   nodes: state.displayNodes,
-  propertyNameFormatter: (name) => '$name ->',
+  propertyNameFormatter: (node, name) => '$name ->',
 )
 ```
 
@@ -183,7 +183,7 @@ An example is adding interaction to values that contains links:
 ```dart
 JsonDataExplorer(
   nodes: state.displayNodes,
-  valueStyleBuilder: (value, style) {
+  valueStyleBuilder: (node, value, style) {
     final isUrl = _valueIsUrl(value);
     return PropertyOverrides(
       style: isUrl
@@ -203,7 +203,7 @@ value types:
 ```dart
 JsonDataExplorer(
   nodes: state.displayNodes,
-  valueStyleBuilder: (value, style) {
+  valueStyleBuilder: (node, value, style) {
     if (value is num) {
       return PropertyOverrides(
         style: style.copyWith(
diff --git a/example/lib/main.dart b/example/lib/main.dart
index e40319f..09c66c2 100644
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -254,11 +254,11 @@ class _DataExplorerPageState extends State<DataExplorerPage> {
                         : const SizedBox(),
 
                     /// Creates a custom format for classes and array names.
-                    rootNameFormatter: (dynamic name) => '$name',
+                    rootNameFormatter: (dynamic node, dynamic name) => '$name',
 
                     /// Dynamically changes the property value style and
                     /// interaction when an URL is detected.
-                    valueStyleBuilder: (dynamic value, style) {
+                    valueStyleBuilder: (node, dynamic value, style) {
                       final isUrl = _valueIsUrl(value);
                       return PropertyOverrides(
                         style: isUrl
@@ -267,6 +267,12 @@ class _DataExplorerPageState extends State<DataExplorerPage> {
                               )
                             : style,
                         onTap: isUrl ? () => _launchUrl(value as String) : null,
+                        onSecondaryTap: () {
+                          print('onSecondaryTap: ${node.key}: $value');
+                        },
+                        onLongPress: () {
+                          print('onLongPress: ${node.key}: $value');
+                        },
                       );
                     },
 
diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift
index 4618f38..a1cdfd0 100644
--- a/example/macos/Flutter/GeneratedPluginRegistrant.swift
+++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -5,7 +5,7 @@
 import FlutterMacOS
 import Foundation
 
-import path_provider_macos
+import path_provider_foundation
 import url_launcher_macos
 
 func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
diff --git a/example/pubspec.lock b/example/pubspec.lock
index 3529304..1f94d79 100644
--- a/example/pubspec.lock
+++ b/example/pubspec.lock
@@ -5,79 +5,82 @@ packages:
     dependency: transitive
     description:
       name: async
-      url: "https://pub.dartlang.org"
+      sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0
+      url: "https://pub.dev"
     source: hosted
-    version: "2.8.2"
+    version: "2.10.0"
   boolean_selector:
     dependency: transitive
     description:
       name: boolean_selector
-      url: "https://pub.dartlang.org"
+      sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
+      url: "https://pub.dev"
     source: hosted
-    version: "2.1.0"
+    version: "2.1.1"
   characters:
     dependency: transitive
     description:
       name: characters
-      url: "https://pub.dartlang.org"
-    source: hosted
-    version: "1.2.0"
-  charcode:
-    dependency: transitive
-    description:
-      name: charcode
-      url: "https://pub.dartlang.org"
+      sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c
+      url: "https://pub.dev"
     source: hosted
-    version: "1.3.1"
+    version: "1.2.1"
   clock:
     dependency: transitive
     description:
       name: clock
-      url: "https://pub.dartlang.org"
+      sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
+      url: "https://pub.dev"
     source: hosted
-    version: "1.1.0"
+    version: "1.1.1"
   collection:
     dependency: transitive
     description:
       name: collection
-      url: "https://pub.dartlang.org"
+      sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0
+      url: "https://pub.dev"
     source: hosted
-    version: "1.15.0"
+    version: "1.17.0"
   crypto:
     dependency: transitive
     description:
       name: crypto
-      url: "https://pub.dartlang.org"
+      sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
+      url: "https://pub.dev"
     source: hosted
-    version: "3.0.1"
+    version: "3.0.3"
   effective_dart:
     dependency: transitive
     description:
       name: effective_dart
-      url: "https://pub.dartlang.org"
+      sha256: "6a69783c808344084b65667e87ff600823531e95810a8a15882cb542fe22de80"
+      url: "https://pub.dev"
     source: hosted
     version: "1.3.2"
   fake_async:
     dependency: transitive
     description:
       name: fake_async
-      url: "https://pub.dartlang.org"
+      sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
+      url: "https://pub.dev"
     source: hosted
-    version: "1.2.0"
+    version: "1.3.1"
   ffi:
     dependency: transitive
     description:
       name: ffi
-      url: "https://pub.dartlang.org"
+      sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99
+      url: "https://pub.dev"
     source: hosted
-    version: "1.1.2"
+    version: "2.0.2"
   file:
     dependency: transitive
     description:
       name: file
-      url: "https://pub.dartlang.org"
+      sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
+      url: "https://pub.dev"
     source: hosted
-    version: "6.1.2"
+    version: "6.1.4"
   flutter:
     dependency: "direct main"
     description: flutter
@@ -97,163 +100,177 @@ packages:
     dependency: "direct main"
     description:
       name: google_fonts
-      url: "https://pub.dartlang.org"
+      sha256: "6b6f10f0ce3c42f6552d1c70d2c28d764cf22bb487f50f66cca31dcd5194f4d6"
+      url: "https://pub.dev"
     source: hosted
-    version: "2.3.1"
+    version: "4.0.4"
   http:
     dependency: "direct main"
     description:
       name: http
-      url: "https://pub.dartlang.org"
+      sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2"
+      url: "https://pub.dev"
     source: hosted
-    version: "0.13.4"
+    version: "0.13.6"
   http_parser:
     dependency: transitive
     description:
       name: http_parser
-      url: "https://pub.dartlang.org"
+      sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
+      url: "https://pub.dev"
     source: hosted
-    version: "4.0.0"
+    version: "4.0.2"
   js:
     dependency: transitive
     description:
       name: js
-      url: "https://pub.dartlang.org"
+      sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7"
+      url: "https://pub.dev"
     source: hosted
-    version: "0.6.3"
+    version: "0.6.5"
   json_data_explorer:
     dependency: "direct main"
     description:
       path: ".."
       relative: true
     source: path
-    version: "0.0.1"
+    version: "0.3.0"
   matcher:
     dependency: transitive
     description:
       name: matcher
-      url: "https://pub.dartlang.org"
+      sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72"
+      url: "https://pub.dev"
     source: hosted
-    version: "0.12.11"
+    version: "0.12.13"
   material_color_utilities:
     dependency: transitive
     description:
       name: material_color_utilities
-      url: "https://pub.dartlang.org"
+      sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
+      url: "https://pub.dev"
     source: hosted
-    version: "0.1.3"
+    version: "0.2.0"
   meta:
     dependency: transitive
     description:
       name: meta
-      url: "https://pub.dartlang.org"
+      sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42"
+      url: "https://pub.dev"
     source: hosted
-    version: "1.7.0"
+    version: "1.8.0"
   nested:
     dependency: transitive
     description:
       name: nested
-      url: "https://pub.dartlang.org"
+      sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
+      url: "https://pub.dev"
     source: hosted
     version: "1.0.0"
   path:
     dependency: transitive
     description:
       name: path
-      url: "https://pub.dartlang.org"
+      sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b
+      url: "https://pub.dev"
     source: hosted
-    version: "1.8.0"
+    version: "1.8.2"
   path_provider:
     dependency: transitive
     description:
       name: path_provider
-      url: "https://pub.dartlang.org"
+      sha256: "3087813781ab814e4157b172f1a11c46be20179fcc9bea043e0fba36bc0acaa2"
+      url: "https://pub.dev"
     source: hosted
-    version: "2.0.9"
+    version: "2.0.15"
   path_provider_android:
     dependency: transitive
     description:
       name: path_provider_android
-      url: "https://pub.dartlang.org"
+      sha256: "2cec049d282c7f13c594b4a73976b0b4f2d7a1838a6dd5aaf7bd9719196bee86"
+      url: "https://pub.dev"
     source: hosted
-    version: "2.0.12"
-  path_provider_ios:
+    version: "2.0.27"
+  path_provider_foundation:
     dependency: transitive
     description:
-      name: path_provider_ios
-      url: "https://pub.dartlang.org"
+      name: path_provider_foundation
+      sha256: "1995d88ec2948dac43edf8fe58eb434d35d22a2940ecee1a9fefcd62beee6eb3"
+      url: "https://pub.dev"
     source: hosted
-    version: "2.0.8"
+    version: "2.2.3"
   path_provider_linux:
     dependency: transitive
     description:
       name: path_provider_linux
-      url: "https://pub.dartlang.org"
+      sha256: ffbb8cc9ed2c9ec0e4b7a541e56fd79b138e8f47d2fb86815f15358a349b3b57
+      url: "https://pub.dev"
     source: hosted
-    version: "2.1.5"
-  path_provider_macos:
-    dependency: transitive
-    description:
-      name: path_provider_macos
-      url: "https://pub.dartlang.org"
-    source: hosted
-    version: "2.0.5"
+    version: "2.1.11"
   path_provider_platform_interface:
     dependency: transitive
     description:
       name: path_provider_platform_interface
-      url: "https://pub.dartlang.org"
+      sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec"
+      url: "https://pub.dev"
     source: hosted
-    version: "2.0.3"
+    version: "2.0.6"
   path_provider_windows:
     dependency: transitive
     description:
       name: path_provider_windows
-      url: "https://pub.dartlang.org"
+      sha256: "1cb68ba4cd3a795033de62ba1b7b4564dace301f952de6bfb3cd91b202b6ee96"
+      url: "https://pub.dev"
     source: hosted
-    version: "2.0.5"
+    version: "2.1.7"
   platform:
     dependency: transitive
     description:
       name: platform
-      url: "https://pub.dartlang.org"
+      sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76"
+      url: "https://pub.dev"
     source: hosted
     version: "3.1.0"
   plugin_platform_interface:
     dependency: transitive
     description:
       name: plugin_platform_interface
-      url: "https://pub.dartlang.org"
+      sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc"
+      url: "https://pub.dev"
     source: hosted
-    version: "2.1.2"
+    version: "2.1.4"
   process:
     dependency: transitive
     description:
       name: process
-      url: "https://pub.dartlang.org"
+      sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09"
+      url: "https://pub.dev"
     source: hosted
     version: "4.2.4"
   provider:
     dependency: transitive
     description:
       name: provider
-      url: "https://pub.dartlang.org"
+      sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f
+      url: "https://pub.dev"
     source: hosted
-    version: "6.0.2"
+    version: "6.0.5"
   rows_lint:
     dependency: "direct dev"
     description:
       name: rows_lint
-      url: "https://pub.dartlang.org"
+      sha256: "772b3e0759a62282365c16ea3e51e2bab5823b916f6ae6f4bfe2012e28028afc"
+      url: "https://pub.dev"
     source: hosted
-    version: "0.1.0"
+    version: "0.1.1"
   scrollable_positioned_list:
     dependency: transitive
     description:
       name: scrollable_positioned_list
-      url: "https://pub.dartlang.org"
+      sha256: "1b54d5f1329a1e263269abc9e2543d90806131aa14fe7c6062a8054d57249287"
+      url: "https://pub.dev"
     source: hosted
-    version: "0.2.3"
+    version: "0.3.8"
   sky_engine:
     dependency: transitive
     description: flutter
@@ -263,128 +280,146 @@ packages:
     dependency: transitive
     description:
       name: source_span
-      url: "https://pub.dartlang.org"
+      sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
+      url: "https://pub.dev"
     source: hosted
-    version: "1.8.1"
+    version: "1.9.1"
   stack_trace:
     dependency: transitive
     description:
       name: stack_trace
-      url: "https://pub.dartlang.org"
+      sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
+      url: "https://pub.dev"
     source: hosted
-    version: "1.10.0"
+    version: "1.11.0"
   stream_channel:
     dependency: transitive
     description:
       name: stream_channel
-      url: "https://pub.dartlang.org"
+      sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
+      url: "https://pub.dev"
     source: hosted
-    version: "2.1.0"
+    version: "2.1.1"
   string_scanner:
     dependency: transitive
     description:
       name: string_scanner
-      url: "https://pub.dartlang.org"
+      sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
+      url: "https://pub.dev"
     source: hosted
-    version: "1.1.0"
+    version: "1.2.0"
   term_glyph:
     dependency: transitive
     description:
       name: term_glyph
-      url: "https://pub.dartlang.org"
+      sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
+      url: "https://pub.dev"
     source: hosted
-    version: "1.2.0"
+    version: "1.2.1"
   test_api:
     dependency: transitive
     description:
       name: test_api
-      url: "https://pub.dartlang.org"
+      sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206
+      url: "https://pub.dev"
     source: hosted
-    version: "0.4.8"
+    version: "0.4.16"
   typed_data:
     dependency: transitive
     description:
       name: typed_data
-      url: "https://pub.dartlang.org"
+      sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
+      url: "https://pub.dev"
     source: hosted
-    version: "1.3.0"
+    version: "1.3.2"
   url_launcher:
     dependency: "direct main"
     description:
       name: url_launcher
-      url: "https://pub.dartlang.org"
+      sha256: eb1e00ab44303d50dd487aab67ebc575456c146c6af44422f9c13889984c00f3
+      url: "https://pub.dev"
     source: hosted
-    version: "6.0.20"
+    version: "6.1.11"
   url_launcher_android:
     dependency: transitive
     description:
       name: url_launcher_android
-      url: "https://pub.dartlang.org"
+      sha256: eed4e6a1164aa9794409325c3b707ff424d4d1c2a785e7db67f8bbda00e36e51
+      url: "https://pub.dev"
     source: hosted
-    version: "6.0.15"
+    version: "6.0.35"
   url_launcher_ios:
     dependency: transitive
     description:
       name: url_launcher_ios
-      url: "https://pub.dartlang.org"
+      sha256: "9af7ea73259886b92199f9e42c116072f05ff9bea2dcb339ab935dfc957392c2"
+      url: "https://pub.dev"
     source: hosted
-    version: "6.0.15"
+    version: "6.1.4"
   url_launcher_linux:
     dependency: transitive
     description:
       name: url_launcher_linux
-      url: "https://pub.dartlang.org"
+      sha256: "207f4ddda99b95b4d4868320a352d374b0b7e05eefad95a4a26f57da413443f5"
+      url: "https://pub.dev"
     source: hosted
-    version: "3.0.0"
+    version: "3.0.5"
   url_launcher_macos:
     dependency: transitive
     description:
       name: url_launcher_macos
-      url: "https://pub.dartlang.org"
+      sha256: "91ee3e75ea9dadf38036200c5d3743518f4a5eb77a8d13fda1ee5764373f185e"
+      url: "https://pub.dev"
     source: hosted
-    version: "3.0.0"
+    version: "3.0.5"
   url_launcher_platform_interface:
     dependency: transitive
     description:
       name: url_launcher_platform_interface
-      url: "https://pub.dartlang.org"
+      sha256: "6c9ca697a5ae218ce56cece69d46128169a58aa8653c1b01d26fcd4aad8c4370"
+      url: "https://pub.dev"
     source: hosted
-    version: "2.0.5"
+    version: "2.1.2"
   url_launcher_web:
     dependency: transitive
     description:
       name: url_launcher_web
-      url: "https://pub.dartlang.org"
+      sha256: "6bb1e5d7fe53daf02a8fee85352432a40b1f868a81880e99ec7440113d5cfcab"
+      url: "https://pub.dev"
     source: hosted
-    version: "2.0.9"
+    version: "2.0.17"
   url_launcher_windows:
     dependency: transitive
     description:
       name: url_launcher_windows
-      url: "https://pub.dartlang.org"
+      sha256: "254708f17f7c20a9c8c471f67d86d76d4a3f9c1591aad1e15292008aceb82771"
+      url: "https://pub.dev"
     source: hosted
-    version: "3.0.0"
+    version: "3.0.6"
   vector_math:
     dependency: transitive
     description:
       name: vector_math
-      url: "https://pub.dartlang.org"
+      sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
+      url: "https://pub.dev"
     source: hosted
-    version: "2.1.1"
+    version: "2.1.4"
   win32:
     dependency: transitive
     description:
       name: win32
-      url: "https://pub.dartlang.org"
+      sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c"
+      url: "https://pub.dev"
     source: hosted
-    version: "2.5.0"
+    version: "4.1.4"
   xdg_directories:
     dependency: transitive
     description:
       name: xdg_directories
-      url: "https://pub.dartlang.org"
+      sha256: ee1505df1426458f7f60aac270645098d318a8b4766d85fde75f76f2e21807d1
+      url: "https://pub.dev"
     source: hosted
-    version: "0.2.0+1"
+    version: "1.0.0"
 sdks:
-  dart: ">=2.15.0 <3.0.0"
-  flutter: ">=2.10.0"
+  dart: ">=2.19.0 <3.0.0"
+  flutter: ">=3.3.0"
diff --git a/example/pubspec.yaml b/example/pubspec.yaml
index 59b8d1c..fd0bd61 100644
--- a/example/pubspec.yaml
+++ b/example/pubspec.yaml
@@ -13,13 +13,13 @@ dependencies:
     path: ../
 
   http: ^0.13.4
-  google_fonts: ^2.3.1
+  google_fonts: ^4.0.4
   url_launcher: ^6.0.20
 
 dev_dependencies:
   flutter_test:
     sdk: flutter
-  rows_lint: 0.1.0
+  rows_lint: ^0.1.1
 
 flutter:
   uses-material-design: true
diff --git a/example/windows/flutter/generated_plugins.cmake b/example/windows/flutter/generated_plugins.cmake
index 411af46..88b22e5 100644
--- a/example/windows/flutter/generated_plugins.cmake
+++ b/example/windows/flutter/generated_plugins.cmake
@@ -6,6 +6,9 @@ list(APPEND FLUTTER_PLUGIN_LIST
   url_launcher_windows
 )
 
+list(APPEND FLUTTER_FFI_PLUGIN_LIST
+)
+
 set(PLUGIN_BUNDLED_LIBRARIES)
 
 foreach(plugin ${FLUTTER_PLUGIN_LIST})
@@ -14,3 +17,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST})
   list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
   list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
 endforeach(plugin)
+
+foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST})
+  add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin})
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries})
+endforeach(ffi_plugin)
diff --git a/lib/src/data_explorer_store.dart b/lib/src/data_explorer_store.dart
index 795473d..43ad0cd 100644
--- a/lib/src/data_explorer_store.dart
+++ b/lib/src/data_explorer_store.dart
@@ -630,9 +630,26 @@ class DataExplorerStore extends ChangeNotifier {
   /// initially only upper root nodes will be in the list.
   ///
   /// [notifyListeners] is called to notify all registered listeners.
-  Future buildNodes(dynamic jsonObject, {bool areAllCollapsed = false}) async {
+  /// [mapScalarNodeValue] can optionally change scalar values to be a different
+  /// value after construction of the nodes and before they are in the store.
+  Future buildNodes(
+    dynamic jsonObject, {
+    bool areAllCollapsed = false,
+    dynamic Function(NodeViewModelState node)? mapScalarNodeValue,
+  }) async {
     final builtNodes = buildViewModelNodes(jsonObject);
     final flatList = flatten(builtNodes);
+    if (mapScalarNodeValue != null) {
+      for (final n in flatList) {
+        if ((n.value is! Map<String, dynamic>) && (n.value is! List)) {
+          final dynamic newValue = mapScalarNodeValue(n);
+          if ((newValue is Map<String, dynamic>) || (newValue is List)) {
+            throw ArgumentError('mapScalarNodeValue must return a scalar');
+          }
+          n.value = newValue;
+        }
+      }
+    }
 
     _allNodes = UnmodifiableListView(flatList);
     _displayNodes = List.from(flatList);
@@ -655,7 +672,7 @@ class DataExplorerStore extends ChangeNotifier {
 
   void _doSearch() {
     for (final node in _allNodes) {
-      final matchesIndexes = _getSearchTermMatchesIndexes(node.key);
+      final matchesIndexes = getSearchTermMatchesIndexes(node.key);
 
       for (final matchIndex in matchesIndexes) {
         _searchResults.add(
@@ -669,7 +686,7 @@ class DataExplorerStore extends ChangeNotifier {
 
       if (!node.isRoot) {
         final matchesIndexes =
-            _getSearchTermMatchesIndexes(node.value.toString());
+            getSearchTermMatchesIndexes(node.value.toString());
 
         for (final matchIndex in matchesIndexes) {
           _searchResults.add(
@@ -686,14 +703,21 @@ class DataExplorerStore extends ChangeNotifier {
     notifyListeners();
   }
 
-  /// Finds all occurences of [searchTerm] in [victim] and retrieves all their
+  /// Finds all occurrences of [regexp] in [victim] and retrieves all their
   /// indexes.
-  Iterable<int> _getSearchTermMatchesIndexes(String victim) {
-    final pattern = RegExp(searchTerm, caseSensitive: false);
-
-    final matches = pattern.allMatches(victim).map((match) => match.start);
+  static Iterable<RegExpMatch> getIndexesOfMatches(
+    String regexp,
+    String victim, {
+    bool caseSensitive = false,
+  }) {
+    final pattern = RegExp(regexp, caseSensitive: caseSensitive);
+    return pattern.allMatches(victim);
+  }
 
-    return matches;
+  /// Finds all occurences of [searchTerm] in [victim] and retrieves all their
+  /// indexes.
+  Iterable<int> getSearchTermMatchesIndexes(String victim) {
+    return getIndexesOfMatches(searchTerm, victim).map((match) => match.start);
   }
 
   /// Expands all the parent nodes of each [SearchResult.node] in
diff --git a/lib/src/data_explorer_theme.dart b/lib/src/data_explorer_theme.dart
index b874318..95defcc 100644
--- a/lib/src/data_explorer_theme.dart
+++ b/lib/src/data_explorer_theme.dart
@@ -31,6 +31,12 @@ class DataExplorerTheme {
   /// If not set falls back to [valueSearchHighlightTextStyle].
   final TextStyle focusedValueSearchHighlightTextStyle;
 
+  /// Whether or not to highlight only matched groups in the search term regex.
+  /// This is limited right now to highlighting all substrings that match any
+  /// of the matching groups, rather than the exact groups, as dart does not
+  /// yet support providing positions of matched regexp groups.
+  final bool highlightOnlyRegExpGroups;
+
   /// Indentation lines color.
   final Color indentationLineColor;
 
@@ -54,6 +60,7 @@ class DataExplorerTheme {
     TextStyle? valueSearchHighlightTextStyle,
     TextStyle? focusedKeySearchHighlightTextStyle,
     TextStyle? focusedValueSearchHighlightTextStyle,
+    this.highlightOnlyRegExpGroups = false,
     this.indentationLineColor = Colors.grey,
     this.highlightColor,
     this.indentationPadding = 8.0,
@@ -88,6 +95,7 @@ class DataExplorerTheme {
     required this.valueSearchHighlightTextStyle,
     required this.focusedKeySearchNodeHighlightTextStyle,
     required this.focusedValueSearchHighlightTextStyle,
+    required this.highlightOnlyRegExpGroups,
     required this.indentationLineColor,
     required this.highlightColor,
     required this.indentationPadding,
@@ -134,6 +142,7 @@ class DataExplorerTheme {
       fontWeight: FontWeight.bold,
       backgroundColor: Colors.lightGreen,
     ),
+    highlightOnlyRegExpGroups: false,
     indentationLineColor: Colors.grey,
     highlightColor: Colors.black12,
     indentationPadding: 8.0,
@@ -148,6 +157,7 @@ class DataExplorerTheme {
     TextStyle? valueSearchHighlightTextStyle,
     TextStyle? focusedKeySearchNodeHighlightTextStyle,
     TextStyle? focusedValueSearchHighlightTextStyle,
+    bool? highlightOnlyRegExpGroups,
     Color? indentationLineColor,
     Color? highlightColor,
     double? indentationPadding,
@@ -172,6 +182,8 @@ class DataExplorerTheme {
         focusedValueSearchHighlightTextStyle:
             focusedValueSearchHighlightTextStyle ??
                 this.focusedValueSearchHighlightTextStyle,
+        highlightOnlyRegExpGroups:
+            highlightOnlyRegExpGroups ?? this.highlightOnlyRegExpGroups,
       );
 
   @override
diff --git a/lib/src/json_data_explorer.dart b/lib/src/json_data_explorer.dart
index 9620861..657ac31 100644
--- a/lib/src/json_data_explorer.dart
+++ b/lib/src/json_data_explorer.dart
@@ -12,9 +12,9 @@ typedef NodeBuilder = Widget Function(
   NodeViewModelState node,
 );
 
-/// Signature for a function that takes a generic value and converts it to a
-/// string.
-typedef Formatter = String Function(dynamic value);
+/// Signature for a function that takes a generic value (associated with a
+/// given node in the model) and converts it to a string.
+typedef Formatter = String Function(NodeViewModelState node, dynamic value);
 
 /// Signature for a function that takes a generic value and the current theme
 /// property value style and returns a [StyleBuilder] that allows the style
@@ -23,16 +23,26 @@ typedef Formatter = String Function(dynamic value);
 /// See also:
 /// * [PropertyStyle]
 typedef StyleBuilder = PropertyOverrides Function(
+  NodeViewModelState node,
   dynamic value,
   TextStyle style,
 );
 
 /// Holds information about a property value style and interaction.
 class PropertyOverrides {
-  final TextStyle style;
+  final TextStyle? style;
   final VoidCallback? onTap;
-
-  const PropertyOverrides({required this.style, this.onTap});
+  final VoidCallback? onSecondaryTap;
+  final VoidCallback? onLongPress;
+  final MouseCursor? cursor;
+
+  const PropertyOverrides({
+    this.style,
+    this.onTap,
+    this.onSecondaryTap,
+    this.onLongPress,
+    this.cursor,
+  });
 }
 
 /// A widget to display a list of Json nodes.
@@ -263,15 +273,18 @@ class JsonAttribute extends StatelessWidget {
 
     final valueStyle = valueStyleBuilder != null
         ? valueStyleBuilder!.call(
+            node,
             node.value,
             theme.valueTextStyle,
           )
-        : PropertyOverrides(style: theme.valueTextStyle);
+        : const PropertyOverrides();
 
     final hasInteraction = node.isRoot || valueStyle.onTap != null;
 
     return MouseRegion(
-      cursor: hasInteraction ? SystemMouseCursors.click : MouseCursor.defer,
+      cursor: valueStyle.cursor != null
+          ? valueStyle.cursor!
+          : (hasInteraction ? SystemMouseCursors.click : MouseCursor.defer),
       onEnter: (event) {
         node.highlight();
         node.focus();
@@ -291,6 +304,8 @@ class JsonAttribute extends StatelessWidget {
                 }
               }
             : null,
+        onLongPress: valueStyle.onLongPress,
+        onSecondaryTap: valueStyle.onSecondaryTap,
         child: AnimatedBuilder(
           animation: node,
 
@@ -341,11 +356,13 @@ class JsonAttribute extends StatelessWidget {
                             node: node,
                             searchTerm: searchTerm,
                             valueFormatter: valueFormatter,
-                            style: valueStyle.style,
+                            style: valueStyle.style ?? theme.valueTextStyle,
                             searchHighlightStyle:
                                 theme.valueSearchHighlightTextStyle,
                             focusedSearchHighlightStyle:
                                 theme.focusedValueSearchHighlightTextStyle,
+                            highlightOnlyRegExpGroups:
+                                theme.highlightOnlyRegExpGroups,
                           ),
                         ),
                       ),
@@ -414,9 +431,9 @@ class _RootNodeWidget extends StatelessWidget {
 
   String _keyName() {
     if (node.isRoot) {
-      return rootNameFormatter?.call(node.key) ?? '${node.key}:';
+      return rootNameFormatter?.call(node, node.key) ?? '${node.key}:';
     }
-    return propertyNameFormatter?.call(node.key) ?? '${node.key}:';
+    return propertyNameFormatter?.call(node, node.key) ?? '${node.key}:';
   }
 
   /// Gets the index of the focused search match.
@@ -455,13 +472,14 @@ class _RootNodeWidget extends StatelessWidget {
     final focusedSearchMatchIndex =
         context.select<DataExplorerStore, int?>(_getFocusedSearchMatchIndex);
 
-    return _HighlightedText(
+    return HighlightedText(
       text: text,
-      highlightedText: searchTerm,
+      highlightedRegExp: searchTerm,
       style: attributeKeyStyle,
       primaryMatchStyle: theme.focusedKeySearchNodeHighlightTextStyle,
       secondaryMatchStyle: theme.keySearchHighlightTextStyle,
       focusedSearchMatchIndex: focusedSearchMatchIndex,
+      highlightOnlyRegExpGroups: theme.highlightOnlyRegExpGroups,
     );
   }
 }
@@ -474,6 +492,7 @@ class _PropertyNodeWidget extends StatelessWidget {
   final TextStyle style;
   final TextStyle searchHighlightStyle;
   final TextStyle focusedSearchHighlightStyle;
+  final bool highlightOnlyRegExpGroups;
 
   const _PropertyNodeWidget({
     Key? key,
@@ -483,6 +502,7 @@ class _PropertyNodeWidget extends StatelessWidget {
     required this.style,
     required this.searchHighlightStyle,
     required this.focusedSearchHighlightStyle,
+    required this.highlightOnlyRegExpGroups,
   }) : super(key: key);
 
   /// Gets the index of the focused search match.
@@ -509,7 +529,8 @@ class _PropertyNodeWidget extends StatelessWidget {
       (store) => store.searchResults.isNotEmpty,
     );
 
-    final text = valueFormatter?.call(node.value) ?? node.value.toString();
+    final text =
+        valueFormatter?.call(node, node.value) ?? node.value.toString();
 
     if (!showHighlightedText) {
       return Text(text, style: style);
@@ -518,13 +539,14 @@ class _PropertyNodeWidget extends StatelessWidget {
     final focusedSearchMatchIndex =
         context.select<DataExplorerStore, int?>(_getFocusedSearchMatchIndex);
 
-    return _HighlightedText(
+    return HighlightedText(
       text: text,
-      highlightedText: searchTerm,
+      highlightedRegExp: searchTerm,
       style: style,
       primaryMatchStyle: focusedSearchHighlightStyle,
       secondaryMatchStyle: searchHighlightStyle,
       focusedSearchMatchIndex: focusedSearchMatchIndex,
+      highlightOnlyRegExpGroups: highlightOnlyRegExpGroups,
     );
   }
 }
@@ -599,11 +621,13 @@ class _Indentation extends StatelessWidget {
   }
 }
 
-/// Highlights found occurrences of [highlightedText] with [highlightedStyle]
+/// Highlights found occurrences of [highlightedRegExp] with [highlightedStyle]
 /// in [text].
-class _HighlightedText extends StatelessWidget {
+class HighlightedText extends StatelessWidget {
   final String text;
-  final String highlightedText;
+  final String highlightedRegExp;
+  final bool caseSensitive;
+  final bool highlightOnlyRegExpGroups;
 
   // The default style when the text or part of it is not highlighted.
   final TextStyle style;
@@ -617,10 +641,12 @@ class _HighlightedText extends StatelessWidget {
   // The index of the focused search match.
   final int? focusedSearchMatchIndex;
 
-  const _HighlightedText({
+  const HighlightedText({
     Key? key,
     required this.text,
-    required this.highlightedText,
+    required this.highlightedRegExp,
+    this.caseSensitive = false,
+    this.highlightOnlyRegExpGroups = false,
     required this.style,
     required this.primaryMatchStyle,
     required this.secondaryMatchStyle,
@@ -629,21 +655,54 @@ class _HighlightedText extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    final lowerCaseText = text.toLowerCase();
-    final lowerCaseQuery = highlightedText.toLowerCase();
-
-    if (highlightedText.isEmpty || !lowerCaseText.contains(lowerCaseQuery)) {
+    var matchingIndexes = highlightedRegExp.isEmpty
+        ? const Iterable<RegExpMatch>.empty()
+        : DataExplorerStore.getIndexesOfMatches(
+            highlightedRegExp,
+            text,
+            caseSensitive: caseSensitive,
+          );
+    if (matchingIndexes.isEmpty) {
       return Text(text, style: style);
     }
 
+    // It seems that positions of matching groups are not available for now
+    // (see https://github.com/dart-lang/sdk/issues/45486). We have to thus
+    // take a more complex approach if we only want to highlight group contents
+    // by first finding all matches, and then getting all of the group contents,
+    // and finding all matches anywhere in the string that could match these
+    // group contents. This will highlight potentially a lot more than just
+    // the actual group matches, but is an approximation we'll have to live with
+    // until the above dart:core enhancement is finished.
+    if (highlightOnlyRegExpGroups) {
+      final allGroups = <String>{};
+      for (final m in matchingIndexes) {
+        final groups = m
+            .groups(List<int>.generate(m.groupCount, (index) => index + 1))
+            .map((s) => s ?? '')
+            .where((s) => s.isNotEmpty);
+        allGroups.addAll(groups);
+      }
+      // for highlighting purposes, any substring that matches a known
+      // group match should get highlighted. place longer groups first so
+      // we always greedy match match longer expressions first.
+      final sortedGroups = allGroups.toList()
+        ..sort((a, b) => b.length.compareTo(a.length));
+      final newRegExp = sortedGroups.map(RegExp.escape).join('|');
+      matchingIndexes = DataExplorerStore.getIndexesOfMatches(
+        newRegExp,
+        text,
+        caseSensitive: caseSensitive,
+      );
+    }
+
     final spans = <TextSpan>[];
     var start = 0;
 
-    while (true) {
-      var index = lowerCaseText.indexOf(lowerCaseQuery, start);
-      index = index >= 0 ? index : text.length;
+    for (final m in matchingIndexes) {
+      final index = m.start;
 
-      if (start != index) {
+      if (start < index) {
         spans.add(
           TextSpan(
             text: text.substring(start, index),
@@ -658,13 +717,22 @@ class _HighlightedText extends StatelessWidget {
 
       spans.add(
         TextSpan(
-          text: text.substring(index, index + highlightedText.length),
+          text: text.substring(index, m.end),
           style: index == focusedSearchMatchIndex
               ? primaryMatchStyle
               : secondaryMatchStyle,
         ),
       );
-      start = index + highlightedText.length;
+      start = m.end;
+    }
+
+    if (start != text.length) {
+      spans.add(
+        TextSpan(
+          text: text.substring(start),
+          style: style,
+        ),
+      );
     }
 
     return Text.rich(
diff --git a/pubspec.yaml b/pubspec.yaml
index 8d979a8..864d4a9 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,6 +1,6 @@
 name: json_data_explorer
 description: A highly customizable widget to render and interact with JSON objects.
-version: 0.1.0
+version: 0.3.0
 repository: https://github.com/rows/json_data_explorer
 homepage: https://github.com/rows/json_data_explorer
 issue_tracker: https://github.com/rows/json_data_explorer/issues
@@ -12,15 +12,15 @@ environment:
 dependencies:
   flutter:
     sdk: flutter
-  scrollable_positioned_list: ^0.2.3
-  provider: ^6.0.2
+  scrollable_positioned_list: ^0.3.2
+  provider: ^6.0.3
 
 dev_dependencies:
   flutter_test:
     sdk: flutter
-  rows_lint: 0.1.0
+  rows_lint: ^0.1.1
   mocktail: ^0.3.0
-  golden_toolkit: ^0.13.0
+  golden_toolkit: ^0.15.0
 
 flutter:
   uses-material-design: true
\ No newline at end of file
diff --git a/test/golden/json_data_explorer_test.dart b/test/golden/json_data_explorer_test.dart
index 6f8d2d3..f4fa0f7 100644
--- a/test/golden/json_data_explorer_test.dart
+++ b/test/golden/json_data_explorer_test.dart
@@ -114,15 +114,15 @@ void main() {
       ..addScenario(
         'Name formatters',
         buildWidget(
-          rootNameFormatter: (dynamic name) => '$name',
-          propertyNameFormatter: (dynamic name) => '$name =',
-          valueFormatter: (dynamic value) => '"$value"',
+          rootNameFormatter: (dynamic node, dynamic name) => '$name',
+          propertyNameFormatter: (dynamic node, dynamic name) => '$name =',
+          valueFormatter: (dynamic node, dynamic value) => '"$value"',
         ),
       )
       ..addScenario(
         'Value style builder',
         buildWidget(
-          valueStyleBuilder: (dynamic value, style) {
+          valueStyleBuilder: (node, dynamic value, style) {
             final isInt = int.tryParse(value.toString());
             return PropertyOverrides(
               style: isInt != null