@script-it/json-views-react 0.1.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +301 -0
  3. package/THIRD_PARTY_NOTICES.txt +4917 -0
  4. package/dist/browser-device.d.ts +21 -0
  5. package/dist/browser-device.js +22 -0
  6. package/dist/file-content-edit-state.d.ts +10 -0
  7. package/dist/file-content-edit-state.js +1 -0
  8. package/dist/file-content-view.d.ts +8 -0
  9. package/dist/file-content-view.js +7 -0
  10. package/dist/index.d.ts +11 -0
  11. package/dist/index.js +8 -0
  12. package/dist/json/copy-json.d.ts +15 -0
  13. package/dist/json/copy-json.js +52 -0
  14. package/dist/json/html-preview.d.ts +5 -0
  15. package/dist/json/html-preview.js +12 -0
  16. package/dist/json/html-view/native-control.d.ts +44 -0
  17. package/dist/json/html-view/native-control.js +96 -0
  18. package/dist/json/html-view/settings.d.ts +2 -0
  19. package/dist/json/html-view/settings.js +58 -0
  20. package/dist/json/html-view/template.d.ts +12 -0
  21. package/dist/json/html-view/template.js +55 -0
  22. package/dist/json/html-view.d.ts +9 -0
  23. package/dist/json/html-view.js +163 -0
  24. package/dist/json/json-content.d.ts +61 -0
  25. package/dist/json/json-content.js +433 -0
  26. package/dist/json/json-source-editor.d.ts +36 -0
  27. package/dist/json/json-source-editor.js +203 -0
  28. package/dist/json/json-view.d.ts +3 -0
  29. package/dist/json/json-view.js +322 -0
  30. package/dist/json/kanban-view.d.ts +20 -0
  31. package/dist/json/kanban-view.js +328 -0
  32. package/dist/json/long-text.d.ts +3 -0
  33. package/dist/json/long-text.js +6 -0
  34. package/dist/json/record-view.d.ts +38 -0
  35. package/dist/json/record-view.js +85 -0
  36. package/dist/json/row-mutations.d.ts +7 -0
  37. package/dist/json/row-mutations.js +93 -0
  38. package/dist/json/schema-value.d.ts +57 -0
  39. package/dist/json/schema-value.js +294 -0
  40. package/dist/json/source-default.d.ts +1 -0
  41. package/dist/json/source-default.js +1 -0
  42. package/dist/json/source-edit-capabilities.d.ts +8 -0
  43. package/dist/json/source-edit-capabilities.js +22 -0
  44. package/dist/json/source-literals.d.ts +15 -0
  45. package/dist/json/source-literals.js +24 -0
  46. package/dist/json/source-recovery.d.ts +32 -0
  47. package/dist/json/source-recovery.js +38 -0
  48. package/dist/json/source-selection.d.ts +3 -0
  49. package/dist/json/source-selection.js +16 -0
  50. package/dist/json/table-view.d.ts +20 -0
  51. package/dist/json/table-view.js +300 -0
  52. package/dist/json/use-kanban-card-drag.d.ts +13 -0
  53. package/dist/json/use-kanban-card-drag.js +255 -0
  54. package/dist/json/use-kanban-column-drag.d.ts +14 -0
  55. package/dist/json/use-kanban-column-drag.js +179 -0
  56. package/dist/json/view-diagnostics.d.ts +4 -0
  57. package/dist/json/view-diagnostics.js +26 -0
  58. package/dist/json/view-model.d.ts +27 -0
  59. package/dist/json/view-model.js +180 -0
  60. package/dist/json/view-save-status.d.ts +12 -0
  61. package/dist/json/view-save-status.js +38 -0
  62. package/dist/json/view-settings.d.ts +36 -0
  63. package/dist/json/view-settings.js +661 -0
  64. package/dist/json/view-types.d.ts +47 -0
  65. package/dist/json/view-types.js +4 -0
  66. package/dist/json/viewers/general/general-json-view.d.ts +46 -0
  67. package/dist/json/viewers/general/general-json-view.js +163 -0
  68. package/dist/json/viewers/general/json-flatten.d.ts +33 -0
  69. package/dist/json/viewers/general/json-flatten.js +51 -0
  70. package/dist/json/viewers/general/json-node-view.d.ts +43 -0
  71. package/dist/json/viewers/general/json-node-view.js +215 -0
  72. package/dist/lib/clipboard.d.ts +1 -0
  73. package/dist/lib/clipboard.js +24 -0
  74. package/dist/lib/cn.d.ts +2 -0
  75. package/dist/lib/cn.js +5 -0
  76. package/dist/lib/file-data.d.ts +7 -0
  77. package/dist/lib/file-data.js +5 -0
  78. package/dist/lib/floating-portal-selectors.d.ts +4 -0
  79. package/dist/lib/floating-portal-selectors.js +4 -0
  80. package/dist/lib/time.d.ts +24 -0
  81. package/dist/lib/time.js +60 -0
  82. package/dist/lib/ui-size.d.ts +28 -0
  83. package/dist/lib/ui-size.js +39 -0
  84. package/dist/lib/use-anchored-position.d.ts +5 -0
  85. package/dist/lib/use-anchored-position.js +65 -0
  86. package/dist/lib/use-dismiss.d.ts +31 -0
  87. package/dist/lib/use-dismiss.js +65 -0
  88. package/dist/primitives/boolean-toggle.d.ts +10 -0
  89. package/dist/primitives/boolean-toggle.js +11 -0
  90. package/dist/primitives/button.d.ts +11 -0
  91. package/dist/primitives/button.js +34 -0
  92. package/dist/primitives/checkbox.d.ts +4 -0
  93. package/dist/primitives/checkbox.js +8 -0
  94. package/dist/primitives/choice-control.d.ts +29 -0
  95. package/dist/primitives/choice-control.js +131 -0
  96. package/dist/primitives/dialog.d.ts +29 -0
  97. package/dist/primitives/dialog.js +25 -0
  98. package/dist/primitives/dropdown-menu.d.ts +37 -0
  99. package/dist/primitives/dropdown-menu.js +39 -0
  100. package/dist/primitives/inline-feedback-action.d.ts +13 -0
  101. package/dist/primitives/inline-feedback-action.js +6 -0
  102. package/dist/primitives/menu-select.d.ts +12 -0
  103. package/dist/primitives/menu-select.js +7 -0
  104. package/dist/primitives/multi-select.d.ts +18 -0
  105. package/dist/primitives/multi-select.js +7 -0
  106. package/dist/primitives/number-input.d.ts +10 -0
  107. package/dist/primitives/number-input.js +8 -0
  108. package/dist/primitives/option-pill.d.ts +13 -0
  109. package/dist/primitives/option-pill.js +50 -0
  110. package/dist/primitives/tabs.d.ts +7 -0
  111. package/dist/primitives/tabs.js +12 -0
  112. package/dist/primitives/textarea.d.ts +4 -0
  113. package/dist/primitives/textarea.js +8 -0
  114. package/dist/primitives/tooltip.d.ts +31 -0
  115. package/dist/primitives/tooltip.js +37 -0
  116. package/dist/standalone.css +113 -0
  117. package/dist/structured-data/atomic-value-editor.d.ts +33 -0
  118. package/dist/structured-data/atomic-value-editor.js +195 -0
  119. package/dist/structured-data/atomic-value.d.ts +3 -0
  120. package/dist/structured-data/atomic-value.js +6 -0
  121. package/dist/structured-data/column-controls.d.ts +21 -0
  122. package/dist/structured-data/column-controls.js +180 -0
  123. package/dist/structured-data/copy-json-action.d.ts +10 -0
  124. package/dist/structured-data/copy-json-action.js +87 -0
  125. package/dist/structured-data/delete-row-action.d.ts +10 -0
  126. package/dist/structured-data/delete-row-action.js +37 -0
  127. package/dist/structured-data/edit-base.d.ts +3 -0
  128. package/dist/structured-data/edit-base.js +8 -0
  129. package/dist/structured-data/filter-value.d.ts +2 -0
  130. package/dist/structured-data/filter-value.js +28 -0
  131. package/dist/structured-data/row-selection-icon.d.ts +9 -0
  132. package/dist/structured-data/row-selection-icon.js +6 -0
  133. package/dist/structured-data/structured-cell-fill-context.d.ts +6 -0
  134. package/dist/structured-data/structured-cell-fill-context.js +7 -0
  135. package/dist/structured-data/structured-data-content.d.ts +1 -0
  136. package/dist/structured-data/structured-data-content.js +1 -0
  137. package/dist/structured-data/table-types.d.ts +108 -0
  138. package/dist/structured-data/table-types.js +1 -0
  139. package/dist/structured-data/use-metadata-persistence.d.ts +15 -0
  140. package/dist/structured-data/use-metadata-persistence.js +18 -0
  141. package/dist/structured-data/use-optimistic-text-document.d.ts +22 -0
  142. package/dist/structured-data/use-optimistic-text-document.js +30 -0
  143. package/dist/structured-data/value-cell.d.ts +15 -0
  144. package/dist/structured-data/value-cell.js +81 -0
  145. package/dist/styles.css +2 -0
  146. package/dist/surface.d.ts +14 -0
  147. package/dist/surface.js +73 -0
  148. package/dist/tabular-data-view.d.ts +5 -0
  149. package/dist/tabular-data-view.js +493 -0
  150. package/dist/viewer-state.d.ts +72 -0
  151. package/dist/viewer-state.js +79 -0
  152. package/dist/widget-registry.d.ts +60 -0
  153. package/dist/widget-registry.js +126 -0
  154. package/dist/widgets/boolean-widget.d.ts +2 -0
  155. package/dist/widgets/boolean-widget.js +15 -0
  156. package/dist/widgets/date-picker-widget.d.ts +18 -0
  157. package/dist/widgets/date-picker-widget.js +265 -0
  158. package/dist/widgets/dropdown-widget.d.ts +10 -0
  159. package/dist/widgets/dropdown-widget.js +19 -0
  160. package/dist/widgets/multi-select-widget.d.ts +11 -0
  161. package/dist/widgets/multi-select-widget.js +46 -0
  162. package/dist/widgets/number-widget.d.ts +2 -0
  163. package/dist/widgets/number-widget.js +19 -0
  164. package/dist/widgets/read-only-field.d.ts +36 -0
  165. package/dist/widgets/read-only-field.js +39 -0
  166. package/dist/widgets/text-widget.d.ts +2 -0
  167. package/dist/widgets/text-widget.js +19 -0
  168. package/dist/widgets/textarea-widget.d.ts +2 -0
  169. package/dist/widgets/textarea-widget.js +34 -0
  170. package/dist/widgets/types.d.ts +53 -0
  171. package/dist/widgets/types.js +1 -0
  172. package/package.json +83 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Script.it
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,301 @@
1
+ # @script-it/json-views-react
2
+
3
+ An extensible React JSON inspector and editor. Requires React 18 or 19.
4
+
5
+ ```tsx
6
+ import { JSONContent } from '@script-it/json-views-react'
7
+ import '@script-it/json-views-react/styles.css'
8
+
9
+ <JSONContent content={source} documentId={documentId} edit={{
10
+ isEditing: false,
11
+ editContent: source,
12
+ onEditChange: setSource,
13
+ onCommitContent: save,
14
+ }} />
15
+ ```
16
+
17
+ Omit the edit callback for a read-only viewer. Use a stable document identity that includes the account/workspace/file. Save callbacks return real persistence completion; errors preserve the draft. To keep visual state across mounts, cache `presentationState` from `onPresentationStateChange` separately from the JSON and pass it back for that document.
18
+
19
+ The embedded frame provides Table and Source tabs, using table and `{ }` icons. Source sits directly below the shared view bar; there is no separate source toggle. Its Format action (when formatting is available) and save state use the shared toolbar. View options remains available in Source for the previously selected structured view, without leaving Source. JSON source is syntax-highlighted. Read-only embeds can inspect the exact source; editable embeds debounce valid source changes through the same save callback and retain invalid or failed drafts for repair. Hosts that already own their raw-source lifecycle can control the mode with `sourceVisible` / `onSourceVisibleChange` and mirror drafts through `edit.onEditChange`.
20
+
21
+ Use `CSVContent` for CSV or `StructuredDataContent` with a Core format adapter. Array-root JSON and CSV keep metadata in session state; provide `onRequestMetadataPersistence` to let the host confirm and create an object-root JSON document before embedded metadata is written.
22
+
23
+ Library styles are scoped to each viewer. Theme with `theme="light"` / `theme="dark"`, or inherit an ancestor's `data-json-views-theme` or `.dark`. Public `--json-views-*` variables allow host token mapping. The optional `standalone.css` only themes a host with the `json-views-app` class.
24
+
25
+ Mobile layout follows the browser's mobile identity, with a user-agent fallback, independently of window width. Hosts can read `useJsonViewsDevice()` or wrap viewers in `<JsonViewsDeviceProvider device="mobile">` (or `"desktop"`) to override it, including in previews. The same components and portaled controls share this setting.
26
+
27
+ See [React integration](#react-integration) below for custom types, object widgets, external metadata, and persistence. MIT.
28
+
29
+ ### Minimal editable embed
30
+
31
+ ```tsx
32
+ <JSONContent documentId={file.id} content={file.source} revision={file.revision} onSave={save} />
33
+ ```
34
+
35
+ `save(nextSource, context)` must resolve after persistence and may return the accepted source and revision. Omit `onSave` for read-only data; use `isSaving` for host-controlled locking. Legacy `edit.onCommitContent` integrations continue to work. See the integration guide for external annotation persistence, independent registries, and scoped styling.
36
+
37
+ The Table tab was previously labeled Root. This is a display-name change: `presentationState.activeView: "root"` still selects Table, and `"source"` selects Source. JSON root paths such as `$` are unchanged. The initial source-default heuristic never overrides a supplied `sourceVisible` value or a saved selection.
38
+
39
+ ## Embedded HTML views
40
+
41
+ Use `display: "html"` with `path`, a non-empty `html` string and optional `css` string inside `$jsonviews.views`. The same-file template supports source-bound values, existing field editors, native controls and repeated arrays. See [HTML views](#html-views) below for the format, bindings, isolation, and examples.
42
+
43
+ ## React integration
44
+
45
+ Install the matching core and React package versions, import `@script-it/json-views-react/styles.css`, and render `JSONContent` with the original JSON string. React 18 and 19 are supported. Package imports are ESM and declarations support NodeNext.
46
+
47
+ ```tsx
48
+ import { JSONContent } from '@script-it/json-views-react'
49
+ import type { JsonDocumentSave } from '@script-it/json-views-core'
50
+ import '@script-it/json-views-react/styles.css'
51
+
52
+ export function FileViewer(props: {
53
+ id: string
54
+ path: string
55
+ content: string
56
+ revision: string
57
+ save: JsonDocumentSave
58
+ }) {
59
+ return <JSONContent
60
+ documentId={props.id}
61
+ path={props.path}
62
+ content={props.content}
63
+ revision={props.revision}
64
+ theme="inherit"
65
+ onSave={props.save}
66
+ />
67
+ }
68
+ ```
69
+
70
+ ### Identity and persistence
71
+
72
+ `documentId` is stable for one document and changes when switching files, workspaces, or accounts. Use an opaque resource ID or an unambiguous encoding such as `JSON.stringify([accountId, workspaceId, fileId])`. `path` is a display label and fallback identity; two files with the same name must not share an explicit ID.
73
+
74
+ The save callback receives `(nextSource, { documentId, baseContent, baseRevision })`. Capture the storage destination from that context, apply revision checks in your backend, and resolve with `{ content: acceptedSource, revision: acceptedRevision }` only after persistence succeeds. Reject failures. Do not swallow errors or resolve a save when a request is merely queued. A legacy one-argument callback returning `Promise<void>` remains supported, but revision-aware acknowledgements distinguish delayed host renders from new external changes.
75
+
76
+ ```ts
77
+ const save: JsonDocumentSave = async (content, context) => {
78
+ return storage.compareAndSwap({
79
+ id: context.documentId,
80
+ expectedRevision: context.baseRevision,
81
+ content,
82
+ })
83
+ }
84
+ ```
85
+
86
+ `storage.compareAndSwap` above represents your host API, not a package dependency. Keep the original source authoritative in host state. Publish returned acknowledgements to that state; the viewer preserves newer in-flight drafts. A failed edit remains visible with retry, discard, and source recovery. An external change while dirty requires explicit reload/discard after preserving the draft.
87
+
88
+ `JsonDocumentSession` is also exported by core for hosts with a source editor. Its `edit` and `commit` preserve arbitrary source text by default, an optional `validateContent` callback can enforce a format, and `receive` accepts host snapshots. Subscribe with `useSyncExternalStore`; use one session per document identity. See `apps/web/src/App.tsx` for the complete source/visual editor flow.
89
+
90
+ The host owns persistence. Supplying `onSave` enables visual edits; omitting it creates a read-only viewer. `isSaving` can additionally lock edits while the host is saving. The older `edit.onCommitContent` and `edit.isSaving` adapter remains supported; the top-level props take precedence.
91
+
92
+ The embedded frame provides Table and Source tabs, using table and `{ }` icons. Source sits directly below the shared view bar; there is no separate source toggle. Its Format action (when formatting is available) and save state use the shared toolbar. View options remains available in Source for the previously selected structured view, without leaving Source. JSON source is syntax-highlighted. It shows exact JSON or CSV source in read-only embeds and debounces valid source edits through the same save callback in editable embeds. Invalid and failed drafts stay visible. A host with an existing source session can pass `sourceVisible` and `onSourceVisibleChange`, mirror each draft through `edit.onEditChange`, and retain ownership of its save policy.
93
+
94
+ Presentation state is a separate, serializable host contract. Pass a cached `presentationState` and store each value emitted by `onPresentationStateChange` under the same stable document identity:
95
+
96
+ Load the cache before mounting the viewer. It seeds presentation state on mount and whenever `documentId` changes; updates for the same mounted document come from user interaction.
97
+
98
+ ```tsx
99
+ <JSONContent
100
+ documentId={props.id}
101
+ content={props.content}
102
+ presentationState={uiStateByDocument[props.id]}
103
+ onPresentationStateChange={(state) => cacheUiState(props.id, state)}
104
+ />
105
+ ```
106
+
107
+ It includes the active view, per-view search, table widths/scroll, transient inferred-table sort and hidden columns, and general JSON navigation/scroll/Markdown display. It deliberately excludes selections, dialogs, confirmations, drags, and unfinished editors. Never insert it into `$jsonviews`: declared columns, filters, sorts, grouping, labels, and display modes already belong to metadata and remain authoritative.
108
+
109
+ `JSONContent` automatically infers typed widgets and useful nested collection views when `$jsonviews` is absent. The inferred metadata is transient. If a user saves inferred view settings, the normal commit callback receives JSON with the inferred schema and views materialized under `$jsonviews`. An explicit `$jsonviews` always disables inference.
110
+
111
+ ### Register a custom type and widget
112
+
113
+ ```tsx
114
+ import {
115
+ JsonViewsProvider,
116
+ createDefaultWidgetRegistry,
117
+ } from '@script-it/json-views-react'
118
+ import { createDefaultTypeRegistry } from '@script-it/json-views-core'
119
+
120
+ const types = createDefaultTypeRegistry().register({
121
+ name: 'rating',
122
+ validate(value, descriptor) {
123
+ const maximum = Number(descriptor.maximum ?? 5)
124
+ return typeof value === 'number' && value >= 1 && value <= maximum
125
+ ? undefined
126
+ : `Choose a rating from 1 to ${maximum}`
127
+ },
128
+ })
129
+
130
+ const widgets = createDefaultWidgetRegistry().register(
131
+ 'rating',
132
+ ({ label, value, disabled, onCommit }) => (
133
+ <input
134
+ aria-label={label}
135
+ disabled={disabled}
136
+ type="range"
137
+ min="1"
138
+ max="5"
139
+ value={typeof value === 'number' ? value : 1}
140
+ onChange={(event) => onCommit(Number(event.target.value))}
141
+ />
142
+ ),
143
+ )
144
+
145
+ export function App() {
146
+ return (
147
+ <JsonViewsProvider types={types} widgets={widgets}>
148
+ {/* JSONContent instances use both registries. */}
149
+ </JsonViewsProvider>
150
+ )
151
+ }
152
+ ```
153
+
154
+ Custom descriptor properties are preserved and passed to both validation and editor widgets. Add `validateDescriptor` for type-specific annotation shape checks. A type's optional `parse` maps persisted JSON into an editor model, and `serialize` maps the committed model back into JSON. The canonical validator runs on serialized JSON before source patching and saving; thrown errors remain visible in the editor. Omitted conversions are identity.
155
+
156
+ Editors receive `value`, `stringValue`, `descriptor`, `disabled`, `error`, and `onChange`/`onCommit`/`onCancel`. `onChange` updates the local draft; `onCommit(value)` submits a candidate, while `onCommit()` submits the current draft. Respect `disabled` while a save is pending. An optional display receives the canonical JSON value, `compact`, `onOpen`, and the host Markdown renderer. Object and array values use the same widget contract, including at the current/root location, with an Expand action for structural inspection.
157
+
158
+ Core `filterOperators`, `matchesFilter`, and `compare` supply custom filtering and ordering. Comparators must define a consistent order. Register implementations before compiling annotations; use registry methods to replace or unregister them. Registries notify mounted viewers of changes. Create a provider-scoped registry or clone when behavior must differ across instances. See the [complete extension contract](annotation-spec.md#extension-lifecycle) and the [working custom-object consumer](../examples/consumer/src/main.tsx).
159
+
160
+ Registrations are additive. Replacing only a type's editor preserves its existing display widget:
161
+
162
+ ```tsx
163
+ const widgets = createDefaultWidgetRegistry().register('date', {
164
+ editor: CustomDateEditor,
165
+ })
166
+ ```
167
+
168
+ For optional dates, `JSONContent` sets Clear to `null` by default. Pass `clearBehavior="remove"` when the host should remove the property instead.
169
+
170
+ Built-in `select` and `multi-select` widgets are creatable. Descriptor `options` provide the initial suggestions, while new string values remain valid and are inferred back into the effective option list wherever the same schema declaration applies.
171
+
172
+ Use descriptor `optionColors` to assign a palette color per option. With writable annotations, the menu shows a palette button beside each option and persists the selected color through the annotation host callback.
173
+
174
+ ### External annotations
175
+
176
+ Pass `metadata={{ version: 1, schema: ..., views: ... }}` to annotate any JSON root without inserting `$jsonviews`. This overrides embedded annotations and inference. Supply `onMetadataChange` to persist view settings separately; otherwise the external annotations are read-only. The host must return the accepted metadata through props. Removing `metadata` restores embedded annotations or inference. These saves do not request conversion or change the source. For transient annotations owned by the viewer, set `metadataPersistence="session"`; their lifetime is the mounted document session. Creating or deleting a property together with its annotation is offered for embedded object-root documents, where both can be committed atomically. Hosts using external annotations should coordinate such schema/data changes in their own storage transaction. Malformed and future versions produce visible diagnostics and cannot be silently replaced by version 1 settings.
177
+
178
+ JSON Views descriptors are not JSON Schema. The packaged `@script-it/json-views-core/schema.json` validates annotation structure; the compiler additionally validates paths, registered types, patterns, and document values. See the [normative specification](annotation-spec.md).
179
+
180
+ ### Styling, portals, and accessibility
181
+
182
+ `JSONContent` includes `JsonViewsSurface` and scopes its styles and presentation state to that instance. State is in-memory unless the host supplies the presentation-state callbacks above. Navigation and hidden columns are never stored in a module-global cache. Each `JsonViewsProvider` also owns independent default registries. No host Tailwind installation is required. Use `theme="light"` or `theme="dark"` for explicit sibling themes; `inherit` reads the nearest host `data-json-views-theme`, `.light`, or `.dark`, falling back to light. It does not independently choose an OS theme.
183
+
184
+ Override semantic tokens on a containing element with `--json-views-background`, `--json-views-foreground`, `--json-views-card`, `--json-views-border`, `--json-views-muted`, `--json-views-accent`, `--json-views-primary`, `--json-views-ring`, their foreground companions, and `--json-views-font-family`. Popups receive the same theme and tokens in a per-instance portal root. Use `portalContainer` for a host modal/overlay boundary; the supplied element should belong to the same document. `JsonViewsSurface` is exported for compositions using lower-level components.
185
+
186
+ ```css
187
+ .product-json-viewer {
188
+ --json-views-background: var(--background);
189
+ --json-views-foreground: var(--foreground);
190
+ --json-views-card: var(--card);
191
+ --json-views-card-foreground: var(--card-foreground);
192
+ --json-views-border: var(--border);
193
+ --json-views-muted: var(--muted);
194
+ --json-views-muted-foreground: var(--muted-foreground);
195
+ --json-views-accent: var(--accent);
196
+ --json-views-accent-foreground: var(--accent-foreground);
197
+ --json-views-primary: var(--primary);
198
+ --json-views-primary-foreground: var(--primary-foreground);
199
+ --json-views-ring: var(--ring);
200
+ }
201
+ ```
202
+
203
+ `standalone.css` is optional and styles only a `.json-views-app` host. Embedded integrations normally import only `styles.css`. To fill a panel, give its parent a bounded height and pass `fillHeight`; the default table uses an inline height cap. Provide accessible labels for custom widgets and keyboard equivalents for any custom pointer interaction. Built-in column resizing supports arrow keys. Focus a Kanban card’s grip and use left/right arrows to change columns or up/down arrows to reorder; reduced-motion preferences are respected.
204
+
205
+ ### Exact source and recovery
206
+
207
+ Keep the original string, including when uploading invalid JSON. The renderer reports duplicate keys and numbers that JavaScript cannot represent exactly. Structured inspection displays affected numbers using their exact source literals; replacement of an affected value or ancestor is disabled, while unrelated edits preserve those tokens. Ambiguous duplicate-key descendants require source editing. The recovery editor submits explicit source text and preserves it on failure. A custom exact-number editor should work on source tokens rather than rounded inspection values.
208
+
209
+ ## Date type
210
+
211
+ The `date` type stores a calendar date, a floating date-time, or a timezone-aware date-time:
212
+
213
+ ```json
214
+ {
215
+ "$.tasks[*].due": {
216
+ "type": "date",
217
+ "title": "Due",
218
+ "description": "When this task is due",
219
+ "required": false,
220
+ "minimum": "2026-01-01",
221
+ "maximum": "2027-12-31",
222
+ "defaultIncludeTime": false,
223
+ "placeholder": "Select a date"
224
+ }
225
+ }
226
+ ```
227
+
228
+ Accepted values are `null`, `YYYY-MM-DD`, and ISO 8601 date-times with seconds. A date-time may be floating with no timezone, or include `Z` or a `±HH:MM` offset. Fractional seconds are accepted. Unix timestamps, locale-formatted strings, and empty strings are rejected.
229
+
230
+ ### Editor behavior
231
+
232
+ Clicking a date opens its calendar immediately. Day, time, and timezone changes remain in a draft until Save; Cancel and Escape discard the draft. Enabling time requires a valid hour and minute and saves seconds. Floating date-times remain timezone-less unless the user chooses a timezone. Changing between timezone offsets preserves the instant; adding or removing a timezone preserves the displayed wall-clock time. Disabling time warns before saving the calendar date alone.
233
+
234
+ Clear is disabled for required fields. Optional dates default to `null`; a host can remove the property instead:
235
+
236
+ ```tsx
237
+ <JSONContent clearBehavior="remove" {...props} />
238
+ ```
239
+
240
+ Date-only bounds and filter operands compare the written calendar day. Timestamp operands use a consistent reference timeline: zoned values use their instant, while date-only and floating values use their written fields as a UTC ordering reference. Equal references break ties by kind (date, floating, zoned). This does not assign a timezone to floating values. Missing and invalid values remain last in either sort direction. Filters support `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `isEmpty`, and `isNotEmpty`. See the [normative date semantics](annotation-spec.md#dates-sort-order-and-calendar-predicates).
241
+
242
+ ### Replace only the widget
243
+
244
+ Parsing, validation, serialization, comparison, and filtering live in `@script-it/json-views-core`. The display and editor are registered separately in `@script-it/json-views-react`, so a consumer can replace the editor without replacing the core behavior or built-in display:
245
+
246
+ ```tsx
247
+ const widgets = createDefaultWidgetRegistry().register('date', {
248
+ editor: CustomDateEditor,
249
+ })
250
+ ```
251
+
252
+ The built-in calendar supports Enter or Space to open, Escape to cancel, arrow keys to move by day or week, Page Up and Page Down to move by month, and Home and End to move to the start or end of a week.
253
+
254
+ ## HTML views
255
+
256
+ A view with `display: "html"` contains its HTML and CSS in the same `$jsonviews.views` declaration as the rest of the document. Data is stored once. Editing a binding changes the source field through the existing save callback and source-preserving adapter.
257
+
258
+ ```json
259
+ {
260
+ "name": "Launch",
261
+ "progress": 65,
262
+ "tasks": [{ "id": "t1", "title": "Welcome screen", "done": false }],
263
+ "$jsonviews": {
264
+ "version": 1,
265
+ "schema": { "$.progress": { "type": "number", "minimum": 0, "maximum": 100 } },
266
+ "views": [{
267
+ "id": "launch",
268
+ "name": "Launch page",
269
+ "path": "$",
270
+ "display": "html",
271
+ "html": "<main><h1><jv-field bind=\"$.name\"></jv-field></h1><label for=\"progress\">Progress</label><input id=\"progress\" type=\"range\" min=\"0\" max=\"100\" jv-bind=\"$.progress\"><jv-repeat source=\"$.tasks\" as=\"task\" key=\"id\"><p><input type=\"checkbox\" jv-bind=\"task.done\" aria-label=\"Done\"><jv-field bind=\"task.title\"></jv-field></p></jv-repeat></main>",
272
+ "css": "main { max-width: 720px; margin: auto; padding: 32px; } h1 { font: 36px Georgia, serif; }"
273
+ }]
274
+ }
275
+ }
276
+ ```
277
+
278
+ ### Bindings
279
+
280
+ - `jv-value` element: display a scalar value without editing.
281
+ - `jv-field` element: display a scalar value and open the existing schema/registry field editor when clicked. Shared editors appear in the product layer, outside the authored CSS.
282
+ - `jv-bind` on input or textarea: edit the source value. Supported inputs are text, number, range, and checkbox. Range commits at pointer/keyboard completion; text and number commit on blur or Enter; textarea uses blur or Ctrl/Cmd+Enter. Escape cancels. A failed native edit can be retried by completing the interaction again.
283
+ - `jv-value` attribute on progress/meter: bind the numeric value property.
284
+ - `jv-attr-<attribute>`: read a scalar into an allowed attribute: title, aria-label, aria-valuenow, aria-valuetext, x/y/cx/cy/x1/x2/y1/y2/width/height/r/rx/ry/fill/stroke/opacity. URLs are not permitted in attributes.
285
+ - `jv-repeat source="$.tasks" as="task" key="id"`: repeat over an array. Aliases use ordinary path suffixes (`task.title`, `task['odd.key']`); nested repeats can reference outer aliases. Keys must be unique strings or finite numbers. Unkeyed arrays can display/edit against an unchanged acknowledged document, but have no stable row identity across reorder.
286
+
287
+ Use explicit closing tags for custom elements (`<jv-field ...></jv-field>`); self-closing XML syntax is unsupported. `$` always references the document root. Bindings must stay within the declaration's `path` and cannot access `$jsonviews`. Bindings do not contain arithmetic, functions, wildcards or JavaScript expressions. Computed values and custom JavaScript are not supported.
288
+
289
+ Missing bindings and unsupported object/array fields show an error indication. Use repeats for collections. Edits that began before another accepted source change are refused rather than redirected to a different record. Exact numeric source spelling and unsafe edit restrictions are retained.
290
+
291
+ ### Authoring and portability
292
+
293
+ Create an HTML view directly in the JSON source by adding a view with `display: "html"`, a non-empty `html` template, and optional `css`. Once declared, **View options** can edit the HTML/CSS, Preview, and Apply. Apply saves both strings atomically. Cancel discards the working preview. Existing Source, Download, browser persistence, and sharing carry the template with the data. Embedded HTML works in the reusable React viewer without web-app-specific registration; omit the save callback for read-only use.
294
+
295
+ Layout/CSS runs in a sandboxed frame. Authored scripts, forms, navigation, external resources, event handlers, inline style attributes, and unsafe embedding/SVG elements are removed. Common structural HTML, tables, allowed controls, inline SVG shapes/text, and bounded raster data-URI images are supported. CSS is inserted as style text and external loads are blocked. Unsupported markup produces renderer warnings; raw source remains available.
296
+
297
+ The frame inherits `--jv-background`, `--jv-foreground`, `--jv-muted`, `--jv-border`, and `--jv-accent` tokens and the viewer's color scheme. Templates can use `light-dark()` for their own palette.
298
+
299
+ Limits: HTML and CSS 256 KiB each, template 5,000 nodes / depth 64, expansion 10,000 nodes. Large arbitrary layouts are not virtualized. HTML views do not use built-in columns/filter/sort/group configuration; use another ordinary view to browse the same collection as a table or board.
300
+
301
+ Examples are bundled as `Examples/HTML Launch.json`, `Examples/HTML Room Plan.json`, and `Examples/HTML Product Sheet.json`.