@jxsuite/studio 6.1.1 → 8.0.0
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.
- package/README.md +5 -5
- package/dist/chunks/2020-5wtzce5g.js +4 -0
- package/dist/chunks/{action-group-2mqby73c.js → action-group-4aan7g6g.js} +3 -3
- package/dist/chunks/adapter-5tphg7xe.js +4 -0
- package/dist/chunks/add-new-flow-psym14sn.js +4 -0
- package/dist/chunks/cf-account-picker-haqazq5r.js +4 -0
- package/dist/chunks/cf-settings-h2wrh6hq.js +4 -0
- package/dist/chunks/color-area-6rbra5dy.js +4 -0
- package/dist/chunks/color-field-gy56c94g.js +4 -0
- package/dist/chunks/color-m7r1vk54.js +4 -0
- package/dist/chunks/color-slider-sqqdyf6c.js +4 -0
- package/dist/chunks/combobox-72h32ejk.js +4 -0
- package/dist/chunks/components-r22xca09.js +4 -0
- package/dist/chunks/content-source-mmppmcyg.js +4 -0
- package/dist/chunks/convert-to-repeater-he70fc2b.js +4 -0
- package/dist/chunks/dialog-c4x05vyx.js +4 -0
- package/dist/chunks/editor-ccd9jwsa.js +4 -0
- package/dist/chunks/entry-commands-6qxz9e8p.js +4 -0
- package/dist/chunks/extension-sections-0egb88dm.js +4 -0
- package/dist/chunks/file-ops-qnab36ek.js +4 -0
- package/dist/chunks/files-rcn56qvm.js +4 -0
- package/dist/chunks/format-host-yxz0kjjs.js +4 -0
- package/dist/chunks/fs-events-d1qxq4z4.js +4 -0
- package/dist/chunks/grid-open-4z1g062t.js +4 -0
- package/dist/chunks/grid-view-qg1b5eqx.js +51 -0
- package/dist/chunks/icons-63bjtbs7.js +4 -0
- package/dist/chunks/iframe-entry-2zggzajv.js +5 -0
- package/dist/chunks/iframe-entry-76j8vwwx.js +5 -0
- package/dist/chunks/iframe-entry-99nr5zfb.js +5 -0
- package/dist/chunks/iframe-entry-pkgmcgp0.js +5 -0
- package/dist/chunks/iframe-host-ssv7kyj0.js +4 -0
- package/dist/chunks/{index-2veqmnhr.js → index-32eqzv1k.js} +3 -3
- package/dist/chunks/index-f0jn7nnp.js +4 -0
- package/dist/chunks/{javascript-8nbvs12s.js → javascript-pjp734q4.js} +3 -3
- package/dist/chunks/jsonMode-njkvsa02.js +13 -0
- package/dist/chunks/jx-validate-ttasrbh7.js +4 -0
- package/dist/chunks/layers-bgyr11tr.js +4 -0
- package/dist/chunks/layers-panel-7ez66zeq.js +4 -0
- package/dist/chunks/listbox-c421cf8c.js +4 -0
- package/dist/chunks/lit-html-v9gh687x.js +4 -0
- package/dist/chunks/media-library-sq468wts.js +4 -0
- package/dist/chunks/media-picker-shfe59pt.js +4 -0
- package/dist/chunks/menu-bdpzbfvs.js +4 -0
- package/dist/chunks/monaco-binding-vq1hgnn0.js +4 -0
- package/dist/chunks/monaco-buffer-3afgwvbp.js +4 -0
- package/dist/chunks/monaco-setup-xrd2cbkp.js +4 -0
- package/dist/chunks/notify-kzrjr3fv.js +4 -0
- package/dist/chunks/number-field-y62xq0ty.js +4 -0
- package/dist/chunks/parse-v77xtwv7.js +4 -0
- package/dist/chunks/platform-7v4rtd7m.js +4 -0
- package/dist/chunks/popover-b89sfej7.js +4 -0
- package/dist/chunks/publish-panel-2z9gw8ee.js +4 -0
- package/dist/chunks/publish-quick-qx2455cj.js +4 -0
- package/dist/chunks/publish-workspace-wrc1dgwx.js +4 -0
- package/dist/chunks/quick-search-n0t0eqxb.js +4 -0
- package/dist/chunks/references-9547rg5s.js +4 -0
- package/dist/chunks/right-panel-wsgnr9pn.js +4 -0
- package/dist/chunks/select-rzds841e.js +4 -0
- package/dist/chunks/source-control-kwktr9y6.js +4 -0
- package/dist/chunks/split-c9zfdgxf.js +4 -0
- package/dist/chunks/studio-03ct6yfv.js +9 -0
- package/dist/chunks/studio-07rcfje3.js +5 -0
- package/dist/chunks/studio-3e54qh39.js +7 -0
- package/dist/chunks/studio-3exxhz5g.js +5 -0
- package/dist/chunks/studio-3q40e3rj.js +5 -0
- package/dist/chunks/studio-3qeh9t0h.js +6 -0
- package/dist/chunks/studio-3qqjeep5.js +6 -0
- package/dist/chunks/studio-43sd2mam.js +4 -0
- package/dist/chunks/studio-4hp8pkyt.js +5 -0
- package/dist/chunks/studio-58xg6j2q.js +9 -0
- package/dist/chunks/studio-5g0rprdt.js +7 -0
- package/dist/chunks/studio-6m380wp7.js +5 -0
- package/dist/chunks/studio-80jyh1xh.js +4 -0
- package/dist/chunks/studio-83mk4bgg.js +9 -0
- package/dist/chunks/{studio-kggey1jt.js → studio-881z1xrx.js} +4 -4
- package/dist/chunks/studio-8m0y2qe7.js +5 -0
- package/dist/chunks/studio-8tr6cfrc.js +5 -0
- package/dist/chunks/studio-8vcvfs7r.js +6 -0
- package/dist/chunks/studio-9tmebf0s.js +5 -0
- package/dist/chunks/studio-a2bvaptz.js +5 -0
- package/dist/chunks/studio-b7y5mr30.js +5 -0
- package/dist/chunks/studio-bavb31r0.js +5 -0
- package/dist/chunks/studio-bbe4c90a.js +5 -0
- package/dist/chunks/studio-c05wdnt3.js +5 -0
- package/dist/chunks/studio-ccv2svc6.js +5 -0
- package/dist/chunks/studio-d5grtb9e.js +16 -0
- package/dist/chunks/studio-df0baea9.js +5 -0
- package/dist/chunks/studio-dgk4dqtx.js +7 -0
- package/dist/chunks/studio-e1g64q43.js +5 -0
- package/dist/chunks/studio-ed1hp6zf.js +6 -0
- package/dist/chunks/studio-ed3xx40h.js +7 -0
- package/dist/chunks/studio-f14e1zdj.js +5 -0
- package/dist/chunks/studio-fbhxxc5f.js +5 -0
- package/dist/chunks/studio-fyh0jc96.js +5 -0
- package/dist/chunks/studio-gcxk20n7.js +5 -0
- package/dist/chunks/studio-jaaz8ce9.js +5 -0
- package/dist/chunks/{studio-8grwxtqk.js → studio-jcnsdaep.js} +4 -4
- package/dist/chunks/studio-jsgf2q5k.js +7 -0
- package/dist/chunks/{studio-1q1sh2ax.js → studio-k3xxbagr.js} +3 -3
- package/dist/chunks/studio-k7evxqh4.js +5 -0
- package/dist/chunks/studio-kt3pjkwd.js +5 -0
- package/dist/chunks/studio-m8g16bgz.js +5 -0
- package/dist/chunks/studio-mn0476ag.js +5 -0
- package/dist/chunks/studio-mx1smyq0.js +5 -0
- package/dist/chunks/studio-n0c5h6mf.js +5 -0
- package/dist/chunks/{studio-rv11kfwa.js → studio-nh6d342n.js} +7 -7
- package/dist/chunks/studio-nqxqf8p2.js +4 -0
- package/dist/chunks/studio-p4nrkvqr.js +5 -0
- package/dist/chunks/{studio-h46vtzk8.js → studio-pbxa5fpc.js} +4 -4
- package/dist/chunks/studio-pgmvdv7z.js +7 -0
- package/dist/chunks/studio-ph84avkf.js +5 -0
- package/dist/chunks/{studio-wszjyh0j.js → studio-q0mvbaq6.js} +4 -4
- package/dist/chunks/studio-q0tccd43.js +5 -0
- package/dist/chunks/studio-q7rtx6zq.js +14 -0
- package/dist/chunks/{studio-pre0skq6.js → studio-qgh13jtz.js} +4 -4
- package/dist/chunks/studio-qmn1xc24.js +5 -0
- package/dist/chunks/studio-qwdqwkj0.js +5 -0
- package/dist/chunks/{studio-t6b6qe31.js → studio-r5qy30vx.js} +4 -4
- package/dist/chunks/studio-rj9eamf8.js +5 -0
- package/dist/chunks/studio-rptgsk96.js +5 -0
- package/dist/chunks/studio-rthyg42z.js +928 -0
- package/dist/chunks/studio-s19tp1xr.js +5 -0
- package/dist/chunks/studio-s32578pn.js +5 -0
- package/dist/chunks/studio-svb7yyve.js +5 -0
- package/dist/chunks/studio-syreb644.js +5 -0
- package/dist/chunks/studio-tf9jryb1.js +10 -0
- package/dist/chunks/studio-tfkznv39.js +5 -0
- package/dist/chunks/studio-tft6r105.js +5 -0
- package/dist/chunks/studio-txf2kysx.js +5 -0
- package/dist/chunks/studio-v8867ddv.js +12 -0
- package/dist/chunks/studio-vxq7fcvv.js +5 -0
- package/dist/chunks/studio-w4rz9hdk.js +5 -0
- package/dist/chunks/studio-wbcjbhy1.js +9 -0
- package/dist/chunks/studio-wc649wss.js +5 -0
- package/dist/chunks/studio-wys23mxz.js +5 -0
- package/dist/chunks/studio-x1x167jh.js +5 -0
- package/dist/chunks/studio-x9hsze61.js +5 -0
- package/dist/chunks/studio-y0n7yxea.js +9 -0
- package/dist/chunks/studio-y3ahhdpx.js +5 -0
- package/dist/chunks/studio-z97c48fk.js +5 -0
- package/dist/chunks/studio-zat62m0c.js +5 -0
- package/dist/chunks/studio-zcd2e7yz.js +5 -0
- package/dist/chunks/studio-zz9qe3rw.js +64 -0
- package/dist/chunks/swatch-group-ewh24n1f.js +4 -0
- package/dist/chunks/tabs-f5cw3tgp.js +4 -0
- package/dist/chunks/textfield-wewc8rq2.js +4 -0
- package/dist/chunks/toast-1gv0h53h.js +4 -0
- package/dist/chunks/toast-host-1yg5ar8d.js +4 -0
- package/dist/chunks/{toolbar-2nnzqhrj.js → toolbar-44cm5cta.js} +3 -3
- package/dist/chunks/tooltip-yypz28gq.js +4 -0
- package/dist/chunks/tree-9xa4hm7n.js +4 -0
- package/dist/chunks/tsMode-epyfz1hv.js +14 -0
- package/dist/chunks/workspace-dz7nxfhv.js +4 -0
- package/dist/chunks/workspace-home-xsd7hkjw.js +4 -0
- package/dist/chunks/ws-client-bv552sfx.js +4 -0
- package/dist/iframe-entry.js +20 -16
- package/dist/manifest.json +77 -63
- package/dist/studio.js +106 -107
- package/dist/workers/ts.worker.js +3 -3
- package/package.json +17 -16
- package/src/about/about-modal.ts +4 -3
- package/src/account/action-flow.ts +45 -38
- package/src/account-status.ts +7 -2
- package/src/browse/component-examples.ts +272 -0
- package/src/browse/library-commands.ts +64 -104
- package/src/browse/library-layouts.ts +270 -53
- package/src/browse/library-model.ts +194 -134
- package/src/browse/library-pane.ts +748 -440
- package/src/browse/library-preview.ts +161 -26
- package/src/browse/library-source.ts +308 -41
- package/src/browse/preview-root.ts +78 -0
- package/src/browse/preview-style.ts +155 -0
- package/src/canvas/asset-refs.ts +40 -10
- package/src/canvas/asset-resolve.ts +19 -23
- package/src/canvas/canvas-helpers.ts +7 -6
- package/src/canvas/canvas-live-render.ts +260 -77
- package/src/canvas/canvas-origin.ts +37 -12
- package/src/canvas/canvas-page-context.ts +112 -0
- package/src/canvas/canvas-patcher.ts +30 -30
- package/src/canvas/canvas-perf.ts +11 -5
- package/src/canvas/canvas-render.ts +175 -148
- package/src/canvas/canvas-surface.ts +5 -3
- package/src/canvas/canvas-utils.ts +147 -61
- package/src/canvas/dialog-path.ts +9 -4
- package/src/canvas/dialog-state.ts +1 -2
- package/src/canvas/diff-marks.ts +9 -16
- package/src/canvas/diff-toolbar.ts +8 -7
- package/src/canvas/edit-width-drag.ts +4 -1
- package/src/canvas/edit-width.ts +1 -3
- package/src/canvas/iframe-channel.ts +27 -13
- package/src/canvas/iframe-drop.ts +120 -12
- package/src/canvas/iframe-editable-root.ts +29 -16
- package/src/canvas/iframe-entry.ts +104 -75
- package/src/canvas/iframe-eval.ts +73 -49
- package/src/canvas/iframe-host.ts +269 -158
- package/src/canvas/iframe-inline-edit.ts +49 -30
- package/src/canvas/iframe-insert.ts +5 -4
- package/src/canvas/iframe-interaction.ts +30 -25
- package/src/canvas/iframe-keys.ts +11 -0
- package/src/canvas/iframe-overlay.ts +1 -1
- package/src/canvas/iframe-patch.ts +25 -21
- package/src/canvas/iframe-position.ts +12 -6
- package/src/canvas/iframe-protocol.ts +36 -21
- package/src/canvas/iframe-render.ts +84 -35
- package/src/canvas/iframe-slash.ts +24 -11
- package/src/canvas/iframe-subtree.ts +10 -3
- package/src/canvas/npm-modules.ts +22 -0
- package/src/canvas/path-mapping.ts +19 -12
- package/src/canvas/popover-path.ts +7 -3
- package/src/canvas/popover-state.ts +1 -2
- package/src/canvas/preview-navigate.ts +16 -0
- package/src/canvas/repeater-sources.ts +200 -0
- package/src/canvas/serialize-scope.ts +13 -5
- package/src/canvas/stage-scrollbar.ts +7 -2
- package/src/canvas/surface-registry.ts +27 -3
- package/src/collab/collab-commands.ts +7 -2
- package/src/collab/collab-session.ts +84 -45
- package/src/collab/collab-state.ts +4 -3
- package/src/collab/monaco-binding.ts +16 -13
- package/src/collab/monaco-cursors.ts +5 -2
- package/src/commands/app-commands.ts +16 -6
- package/src/commands/budget.ts +13 -11
- package/src/commands/command-args.ts +103 -71
- package/src/commands/context.ts +55 -28
- package/src/commands/defaults.ts +69 -45
- package/src/commands/keymap.ts +56 -43
- package/src/commands/levels.ts +14 -8
- package/src/commands/live-context.ts +9 -7
- package/src/commands/reference.ts +2 -1
- package/src/commands/registry.ts +22 -11
- package/src/commands/run-reported.ts +15 -10
- package/src/component-props.ts +46 -30
- package/src/content/collection-match.ts +11 -9
- package/src/content/collection-pages.ts +412 -0
- package/src/content/content-collections.ts +270 -0
- package/src/content/draft-state.ts +4 -3
- package/src/content/entry-commands.ts +54 -62
- package/src/content/entry-editor.ts +137 -32
- package/src/content/entry-fields.ts +25 -8
- package/src/content/entry-inspector.ts +225 -0
- package/src/content/entry-model.ts +38 -15
- package/src/content/entry-open.ts +80 -0
- package/src/content/entry-summaries.ts +440 -0
- package/src/content/file-record-ids.ts +80 -0
- package/src/content/file-records.ts +125 -0
- package/src/content/page-summaries.ts +129 -0
- package/src/content/record-entry.ts +201 -0
- package/src/content/writing-banner.ts +177 -0
- package/src/create/add-new-commands.ts +185 -0
- package/src/create/add-new-flow.ts +1116 -0
- package/src/create/add-new.ts +1014 -0
- package/src/create/element-name.ts +121 -0
- package/src/editor/canvas-context-menu.ts +1 -2
- package/src/editor/canvas-slash-bridge.ts +3 -6
- package/src/editor/content-slice.ts +16 -14
- package/src/editor/context-menu.ts +35 -22
- package/src/editor/convert-targets.ts +30 -35
- package/src/editor/convert-to-component.ts +78 -48
- package/src/editor/convert-to-repeater.ts +34 -22
- package/src/editor/editable-tags.ts +17 -2
- package/src/editor/file-drop-action.ts +29 -22
- package/src/editor/inline-edit-apply.ts +79 -17
- package/src/editor/inline-edit.ts +76 -50
- package/src/editor/inline-format.ts +59 -56
- package/src/editor/inline-link.ts +40 -15
- package/src/editor/insert-component.ts +91 -0
- package/src/editor/insert-zone-action.ts +14 -5
- package/src/editor/merge-tags.ts +58 -19
- package/src/editor/repeater-scope.ts +39 -28
- package/src/editor/shortcuts.ts +76 -42
- package/src/editor/slash-menu.ts +52 -14
- package/src/files/asset-versions.ts +4 -4
- package/src/files/components.ts +89 -20
- package/src/files/elements.ts +5 -6
- package/src/files/file-ops.ts +92 -41
- package/src/files/files.ts +248 -239
- package/src/files/fs-events.ts +18 -7
- package/src/files/media-meta.ts +97 -2
- package/src/files/media-paths.ts +3 -0
- package/src/files/media-types.ts +55 -0
- package/src/files/media-upload.ts +209 -52
- package/src/files/new-file-name.ts +156 -0
- package/src/files/serialize-document.ts +15 -5
- package/src/format/constraints.ts +1 -1
- package/src/format/convert-file.ts +27 -13
- package/src/format/format-choices.ts +2 -1
- package/src/format/format-host.ts +91 -50
- package/src/github/github-auth-status.ts +27 -3
- package/src/github/github-auth.ts +21 -11
- package/src/github/github-publish.ts +28 -23
- package/src/grid/cell-editors.ts +46 -31
- package/src/grid/cell-popovers.ts +5 -3
- package/src/grid/edit-buffer.ts +10 -9
- package/src/grid/grid-controller.ts +25 -23
- package/src/grid/grid-layout.ts +21 -5
- package/src/grid/grid-open.ts +3 -33
- package/src/grid/grid-panel.ts +65 -6
- package/src/grid/grid-reveal.ts +34 -0
- package/src/grid/grid-source.ts +9 -31
- package/src/grid/grid-view.ts +45 -23
- package/src/grid/redirects-grid.ts +14 -5
- package/src/grid/redirects.ts +16 -14
- package/src/grid/schema-columns.ts +25 -19
- package/src/grid/sources/connector-source.ts +47 -20
- package/src/grid/sources/content-source.ts +100 -31
- package/src/grid/sources/csv-file-source.ts +2 -2
- package/src/grid/tabulator-tables.d.ts +61 -34
- package/src/hosting/document.ts +2 -2
- package/src/hosting/stage.ts +7 -7
- package/src/i18n/i18n-commands.ts +3 -3
- package/src/media/media-commands.ts +181 -0
- package/src/media/media-library-model.ts +232 -0
- package/src/media/media-library.ts +1283 -0
- package/src/media/media-pane.ts +51 -20
- package/src/new-project/add-repo-modal.ts +20 -19
- package/src/new-project/import-tab.ts +10 -14
- package/src/new-project/location-fields.ts +2 -1
- package/src/new-project/new-project-modal.ts +12 -9
- package/src/packages/jxsuite-update.ts +4 -3
- package/src/packages/pull-package-sync.ts +26 -25
- package/src/page-params.ts +97 -68
- package/src/panels/activity-panel.ts +38 -14
- package/src/panels/ai-chat/attached-context.ts +13 -7
- package/src/panels/ai-chat/chat-markdown.ts +8 -8
- package/src/panels/ai-chat/chat-view.ts +80 -73
- package/src/panels/ai-chat/composer.ts +30 -33
- package/src/panels/ai-chat/sessions-view.ts +1 -2
- package/src/panels/ai-panel.ts +23 -20
- package/src/panels/block-action-bar.ts +57 -46
- package/src/panels/bottom-dock.ts +5 -3
- package/src/panels/canvas-dnd-bridge.ts +36 -26
- package/src/panels/chat-panel.ts +6 -6
- package/src/panels/collections-panel.ts +306 -0
- package/src/panels/component-preview.ts +35 -13
- package/src/panels/data-explorer.ts +63 -26
- package/src/panels/data-grid.ts +18 -11
- package/src/panels/dnd.ts +115 -138
- package/src/panels/editors.ts +52 -21
- package/src/panels/elements-panel.ts +19 -5
- package/src/panels/empty-state.ts +2 -2
- package/src/panels/events-panel.ts +84 -69
- package/src/panels/formula-workspace.ts +78 -54
- package/src/panels/frontmatter-fields.ts +33 -28
- package/src/panels/git-diff-open.ts +10 -6
- package/src/panels/head-panel.ts +488 -202
- package/src/panels/i18n-panel.ts +20 -12
- package/src/panels/imports-panel.ts +25 -47
- package/src/panels/jump-bar.ts +75 -31
- package/src/panels/layers-panel.ts +130 -101
- package/src/panels/left-panel.ts +25 -11
- package/src/panels/navigator-panels.ts +23 -11
- package/src/panels/pane-context.ts +140 -49
- package/src/panels/pane-grid.ts +55 -26
- package/src/panels/panel-registry.ts +36 -46
- package/src/panels/properties-panel.ts +182 -124
- package/src/panels/quick-search.ts +53 -31
- package/src/panels/right-panel.ts +201 -45
- package/src/panels/seo-modal.ts +54 -125
- package/src/panels/settings-menu.ts +63 -61
- package/src/panels/settings-pane.ts +268 -162
- package/src/panels/signals-panel.ts +156 -135
- package/src/panels/site-model.ts +367 -0
- package/src/panels/site-panel.ts +638 -0
- package/src/panels/statement-editor.ts +230 -140
- package/src/panels/style-panel.ts +253 -206
- package/src/panels/style-utils.ts +114 -51
- package/src/panels/stylebook-doc.ts +195 -60
- package/src/panels/stylebook-layers-panel.ts +48 -89
- package/src/panels/stylebook-panel.ts +60 -27
- package/src/panels/styles-panel.ts +327 -0
- package/src/panels/tab-drop.ts +27 -11
- package/src/panels/tab-strip.ts +143 -49
- package/src/platform-errors.ts +57 -43
- package/src/platform.ts +59 -31
- package/src/platforms/cloud.ts +373 -176
- package/src/platforms/default-platform.ts +14 -8
- package/src/platforms/devserver.ts +180 -142
- package/src/platforms/read-batcher.ts +16 -8
- package/src/project-list.ts +2 -1
- package/src/publish/deploy-checklist.ts +20 -9
- package/src/publish/pages-service.ts +66 -27
- package/src/publish/publish-changes.ts +491 -0
- package/src/publish/publish-commands.ts +88 -22
- package/src/publish/publish-ids.ts +49 -0
- package/src/publish/publish-panel.ts +10 -6
- package/src/publish/publish-quick.ts +148 -0
- package/src/publish/publish-workspace.ts +926 -0
- package/src/publish/source-control.ts +907 -0
- package/src/recent-projects.ts +15 -8
- package/src/resize-edges.ts +26 -36
- package/src/services/a11y-report.ts +26 -19
- package/src/services/agent-seed.ts +23 -11
- package/src/services/ai-ask.ts +9 -12
- package/src/services/ai-command-tools.ts +68 -62
- package/src/services/ai-import-tools.ts +80 -67
- package/src/services/ai-models.ts +17 -13
- package/src/services/ai-project-tools.ts +68 -32
- package/src/services/ai-session-store.ts +19 -10
- package/src/services/ai-system-prompt.ts +46 -20
- package/src/services/ai-tools.ts +71 -27
- package/src/services/ai-write-report.ts +20 -12
- package/src/services/ai-writes.ts +4 -4
- package/src/services/automation.ts +74 -51
- package/src/services/cem-export.ts +149 -58
- package/src/services/cf-settings.ts +4 -4
- package/src/services/clock.ts +6 -1
- package/src/services/code-services.ts +37 -31
- package/src/services/connection.ts +2 -2
- package/src/services/context-manager.ts +14 -10
- package/src/services/context-resolver.ts +20 -8
- package/src/services/data-service.ts +19 -15
- package/src/services/directory-picker.ts +20 -14
- package/src/services/document-assistant.ts +39 -19
- package/src/services/gated-registry.ts +10 -6
- package/src/services/harness/chat-reducer.ts +23 -1
- package/src/services/import-client.ts +91 -38
- package/src/services/import-seed.ts +8 -2
- package/src/services/jx-validate.ts +28 -31
- package/src/services/live-preview.ts +0 -0
- package/src/services/live-surfaces.ts +6 -3
- package/src/services/model-uri.ts +10 -2
- package/src/services/module-preloads.ts +12 -8
- package/src/services/monaco-buffer.ts +43 -24
- package/src/services/monaco-lazy.ts +7 -8
- package/src/services/monaco-setup.ts +23 -16
- package/src/services/notify.ts +44 -17
- package/src/services/popover-report.ts +25 -10
- package/src/services/preview-eval.ts +20 -8
- package/src/services/profile.ts +13 -6
- package/src/services/project-path.ts +13 -10
- package/src/services/references.ts +0 -0
- package/src/services/render-critic.ts +29 -9
- package/src/services/settings/definitions.ts +25 -14
- package/src/services/settings/kernel.ts +13 -4
- package/src/services/settings/write-queue.ts +9 -9
- package/src/services/token-lint.ts +12 -10
- package/src/services/tool-executor.ts +17 -5
- package/src/services/tool-outcomes.ts +44 -24
- package/src/services/trusted-types.ts +24 -7
- package/src/settings/contexts-section.ts +11 -8
- package/src/settings/contributed-section.ts +128 -70
- package/src/settings/css-vars-editor.ts +119 -32
- package/src/settings/defs-editor.ts +38 -35
- package/src/settings/developer-panels.ts +43 -0
- package/src/settings/extension-rows.ts +16 -8
- package/src/settings/extension-sections.ts +42 -14
- package/src/settings/extensions-section.ts +4 -1
- package/src/settings/general-settings.ts +101 -28
- package/src/settings/locales-section.ts +20 -12
- package/src/settings/preferences-dialog.ts +17 -7
- package/src/settings/preferences-keymap.ts +7 -5
- package/src/settings/preferences-sections.ts +11 -5
- package/src/settings/project-sections.ts +28 -11
- package/src/settings/schema-field-ui.ts +12 -1
- package/src/settings/section-registry.ts +116 -11
- package/src/settings/settings-dialog.ts +192 -0
- package/src/settings/settings-document.ts +186 -166
- package/src/shell/tree.ts +6 -5
- package/src/shell.ts +536 -477
- package/src/site-context.ts +101 -32
- package/src/state.ts +68 -57
- package/src/store.ts +54 -34
- package/src/studio.ts +219 -116
- package/src/style/live-preview.ts +1 -1
- package/src/style/project-styles.ts +40 -7
- package/src/style/stylebook-specimens.ts +89 -0
- package/src/style/stylebook-target.ts +26 -0
- package/src/style/tag-inheritance.ts +105 -0
- package/src/style/token-ref.ts +5 -4
- package/src/style/variables-focus.ts +61 -0
- package/src/style/variables-view.ts +235 -0
- package/src/surfaces/about.ts +18 -19
- package/src/surfaces/add-new.json +1482 -0
- package/src/surfaces/add-new.ts +325 -0
- package/src/surfaces/add-repo.ts +17 -19
- package/src/surfaces/ai-chat.ts +33 -38
- package/src/surfaces/ai-credentials-form.ts +6 -16
- package/src/surfaces/ai-managed-connect.ts +3 -4
- package/src/surfaces/ai-model-picker.ts +3 -5
- package/src/surfaces/block-action-bar.ts +22 -11
- package/src/surfaces/boot-failure.json +4 -3
- package/src/surfaces/boot-failure.ts +4 -5
- package/src/surfaces/bottom-dock.json +1 -1
- package/src/surfaces/bottom-dock.ts +9 -18
- package/src/surfaces/canvas-stage.json +30 -19
- package/src/surfaces/canvas-stage.ts +17 -16
- package/src/surfaces/cf-account-picker.ts +17 -18
- package/src/surfaces/collections-panel.json +615 -0
- package/src/surfaces/collections-panel.ts +168 -0
- package/src/surfaces/commandbar.json +241 -96
- package/src/surfaces/commandbar.ts +145 -105
- package/src/surfaces/convert-repeater.ts +17 -19
- package/src/surfaces/data-actions.ts +9 -7
- package/src/surfaces/dialog.ts +26 -28
- package/src/surfaces/diff-toolbar.ts +9 -7
- package/src/surfaces/empty-state.json +1 -1
- package/src/surfaces/empty-state.ts +7 -10
- package/src/surfaces/entry-editor.json +136 -0
- package/src/surfaces/entry-editor.ts +35 -11
- package/src/surfaces/entry-inspector.json +371 -0
- package/src/surfaces/entry-inspector.ts +143 -0
- package/src/surfaces/expression-editor.ts +11 -25
- package/src/surfaces/files-panel.ts +27 -14
- package/src/surfaces/formula-palette.ts +4 -5
- package/src/surfaces/github-auth.ts +16 -20
- package/src/surfaces/github-publish.ts +17 -22
- package/src/surfaces/grid-cell.ts +30 -31
- package/src/surfaces/grid-open.ts +10 -14
- package/src/surfaces/grid-panel.ts +13 -9
- package/src/surfaces/grid-replace.ts +13 -13
- package/src/surfaces/grid-views.ts +15 -18
- package/src/surfaces/inspector-dock.json +162 -14
- package/src/surfaces/inspector-dock.ts +59 -31
- package/src/surfaces/jump-bar.ts +5 -6
- package/src/surfaces/jxsuite-update.ts +16 -22
- package/src/surfaces/library-pane.json +670 -437
- package/src/surfaces/library-pane.ts +111 -77
- package/src/surfaces/logic-panel.ts +17 -30
- package/src/surfaces/logic-workspace.ts +13 -15
- package/src/surfaces/media-browser.json +62 -6
- package/src/surfaces/media-browser.ts +26 -11
- package/src/surfaces/media-field.json +1 -1
- package/src/surfaces/media-field.ts +4 -5
- package/src/surfaces/media-library.json +1586 -0
- package/src/surfaces/media-library.ts +282 -0
- package/src/surfaces/media-pane.ts +8 -7
- package/src/surfaces/media-pick-dialog.json +71 -0
- package/src/surfaces/media-pick-dialog.ts +132 -0
- package/src/surfaces/menu.ts +47 -23
- package/src/surfaces/navigator-dock.ts +11 -12
- package/src/surfaces/new-project.ts +27 -26
- package/src/surfaces/pane-context.json +144 -77
- package/src/surfaces/pane-context.ts +44 -25
- package/src/surfaces/pane-grid.ts +15 -10
- package/src/surfaces/panel-activity.ts +5 -6
- package/src/surfaces/panel-data.ts +3 -4
- package/src/surfaces/panel-deploy-checklist.json +12 -6
- package/src/surfaces/panel-deploy-checklist.ts +8 -7
- package/src/surfaces/panel-elements.ts +5 -7
- package/src/surfaces/panel-i18n.ts +5 -7
- package/src/surfaces/panel-imports.ts +6 -13
- package/src/surfaces/panel-outline.ts +25 -19
- package/src/surfaces/panel-page.json +72 -2
- package/src/surfaces/panel-page.ts +49 -25
- package/src/surfaces/panel-problems.ts +5 -7
- package/src/surfaces/panel-signals.json +1 -1
- package/src/surfaces/panel-signals.ts +25 -39
- package/src/surfaces/panel-stylebook-layers.json +71 -204
- package/src/surfaces/panel-stylebook-layers.ts +5 -29
- package/src/surfaces/preferences.json +120 -48
- package/src/surfaces/preferences.ts +29 -11
- package/src/surfaces/progress-modal.ts +15 -9
- package/src/surfaces/properties-panel.json +1 -1
- package/src/surfaces/properties-panel.ts +20 -42
- package/src/surfaces/publish-quick.json +428 -0
- package/src/surfaces/publish-quick.ts +165 -0
- package/src/surfaces/publish-workspace.json +2892 -0
- package/src/surfaces/publish-workspace.ts +391 -0
- package/src/surfaces/publish.ts +18 -18
- package/src/surfaces/push-plan.ts +7 -8
- package/src/surfaces/rail.json +1 -159
- package/src/surfaces/rail.ts +22 -79
- package/src/surfaces/reference-field.ts +11 -15
- package/src/surfaces/schema-builder.ts +16 -22
- package/src/surfaces/schema-form.json +10 -2
- package/src/surfaces/schema-form.ts +17 -25
- package/src/surfaces/secret-field.ts +9 -6
- package/src/surfaces/seo.json +1 -1
- package/src/surfaces/seo.ts +21 -15
- package/src/surfaces/settings-contexts.ts +9 -7
- package/src/surfaces/settings-contributed.json +5 -5
- package/src/surfaces/settings-contributed.ts +32 -12
- package/src/surfaces/settings-css-vars.json +44 -36
- package/src/surfaces/settings-css-vars.ts +82 -16
- package/src/surfaces/settings-defs.json +2 -2
- package/src/surfaces/settings-defs.ts +17 -21
- package/src/surfaces/settings-deploy.json +2 -15
- package/src/surfaces/settings-deploy.ts +9 -16
- package/src/surfaces/settings-dialog.json +51 -0
- package/src/surfaces/settings-dialog.ts +169 -0
- package/src/surfaces/settings-extensions.ts +5 -6
- package/src/surfaces/settings-head.ts +33 -39
- package/src/surfaces/settings-locales.ts +8 -20
- package/src/surfaces/settings-overview.json +3 -3
- package/src/surfaces/settings-overview.ts +14 -8
- package/src/surfaces/settings-packages.ts +8 -7
- package/src/surfaces/settings-pane.json +269 -46
- package/src/surfaces/settings-pane.ts +88 -36
- package/src/surfaces/settings-rawjson.ts +5 -6
- package/src/surfaces/shell.json +2 -1
- package/src/surfaces/shell.ts +1 -2
- package/src/surfaces/site-panel.json +1029 -0
- package/src/surfaces/site-panel.ts +233 -0
- package/src/surfaces/slash-menu.json +63 -21
- package/src/surfaces/slash-menu.ts +42 -9
- package/src/surfaces/statements.ts +13 -18
- package/src/surfaces/statusbar.ts +56 -29
- package/src/surfaces/style-panel.ts +18 -31
- package/src/surfaces/stylebook-chrome.ts +3 -5
- package/src/surfaces/styles-panel.json +735 -0
- package/src/surfaces/styles-panel.ts +195 -0
- package/src/surfaces/styles-variables.json +149 -0
- package/src/surfaces/styles-variables.ts +145 -0
- package/src/surfaces/tab-strip.json +4 -4
- package/src/surfaces/tab-strip.ts +14 -13
- package/src/surfaces/target-line.ts +6 -7
- package/src/surfaces/welcome.ts +6 -8
- package/src/surfaces/writing-banner.json +94 -0
- package/src/surfaces/writing-banner.ts +80 -0
- package/src/tabs/bare-repeaters.ts +101 -0
- package/src/tabs/patch-ops.ts +4 -3
- package/src/tabs/project-config.ts +32 -21
- package/src/tabs/selection.ts +36 -8
- package/src/tabs/tab.ts +99 -48
- package/src/tabs/transact.ts +98 -53
- package/src/types.ts +53 -43
- package/src/ui/ai-credentials-form.ts +10 -13
- package/src/ui/ai-model-picker.ts +3 -3
- package/src/ui/cf-account-picker.ts +7 -9
- package/src/ui/color-selector.ts +9 -5
- package/src/ui/dynamic-slot.ts +7 -8
- package/src/ui/expression-editor.ts +112 -105
- package/src/ui/form-controls.ts +104 -56
- package/src/ui/formula-catalog.ts +239 -230
- package/src/ui/formula-chips.ts +33 -18
- package/src/ui/layers.ts +57 -31
- package/src/ui/media-picker.ts +300 -47
- package/src/ui/popover-surface.ts +20 -12
- package/src/ui/progress-modal.ts +13 -8
- package/src/ui/regions.ts +14 -12
- package/src/ui/schema-form.ts +372 -97
- package/src/ui/surface.ts +71 -8
- package/src/ui/timing.ts +9 -0
- package/src/ui/value-source.ts +44 -31
- package/src/ui/virtual-window.ts +29 -3
- package/src/utils/base64.ts +6 -4
- package/src/utils/edit-display.ts +366 -111
- package/src/utils/element-selectors.ts +3 -2
- package/src/utils/google-fonts.ts +15 -14
- package/src/utils/inherited-style.ts +18 -20
- package/src/utils/link-target.ts +1 -1
- package/src/utils/local-day.ts +18 -0
- package/src/utils/preview-format.ts +25 -3
- package/src/utils/strip-events.ts +49 -22
- package/src/utils/studio-utils.ts +29 -14
- package/src/view.ts +2 -3
- package/src/workspace/pane-derive.ts +14 -16
- package/src/workspace/session.ts +81 -50
- package/src/workspace/tab-workspace.ts +137 -0
- package/src/workspace/workspace-back.ts +189 -0
- package/src/workspace/workspace-home.ts +133 -0
- package/src/workspace/workspace-ids.ts +129 -0
- package/src/workspace/workspace-switch.ts +372 -0
- package/src/workspace/workspace.ts +165 -47
- package/styles/shell-frame.css +30 -7
- package/styles/shell-frame.json +15 -3
- package/dist/chunks/2020-va2wwra4.js +0 -4
- package/dist/chunks/adapter-f93pafd7.js +0 -4
- package/dist/chunks/cf-account-picker-af1h381v.js +0 -4
- package/dist/chunks/cf-settings-125tdm0w.js +0 -4
- package/dist/chunks/color-area-jqhna8zy.js +0 -4
- package/dist/chunks/color-field-embq5ctg.js +0 -4
- package/dist/chunks/color-fpezvnkt.js +0 -4
- package/dist/chunks/color-slider-1qmw7wqy.js +0 -4
- package/dist/chunks/combobox-773hdzyj.js +0 -4
- package/dist/chunks/components-ax2f1fnn.js +0 -4
- package/dist/chunks/content-source-108aqtk1.js +0 -4
- package/dist/chunks/convert-to-repeater-ry5rmppr.js +0 -4
- package/dist/chunks/dialog-mtm9fq5t.js +0 -4
- package/dist/chunks/editor-pfgdmkb0.js +0 -4
- package/dist/chunks/entry-commands-t0k1q0kx.js +0 -4
- package/dist/chunks/extension-sections-6mwh2hf8.js +0 -4
- package/dist/chunks/file-ops-meej5mqf.js +0 -4
- package/dist/chunks/files-44vp8a2s.js +0 -4
- package/dist/chunks/format-host-7q1petb9.js +0 -4
- package/dist/chunks/fs-events-h2mwnxaz.js +0 -4
- package/dist/chunks/git-panel-zngbymvd.js +0 -4
- package/dist/chunks/grid-view-sx9vynsp.js +0 -51
- package/dist/chunks/icons-p44xhjf0.js +0 -4
- package/dist/chunks/iframe-entry-f7mqjwpm.js +0 -5
- package/dist/chunks/iframe-entry-haw7jkvz.js +0 -5
- package/dist/chunks/iframe-entry-z6qrytv3.js +0 -5
- package/dist/chunks/iframe-host-qk3ycq87.js +0 -4
- package/dist/chunks/index-wkyfjeqz.js +0 -4
- package/dist/chunks/jsonMode-s8kt9dsj.js +0 -13
- package/dist/chunks/jx-validate-psgd7vsx.js +0 -4
- package/dist/chunks/layers-panel-1kq2jyfk.js +0 -4
- package/dist/chunks/layers-xd92fg8d.js +0 -4
- package/dist/chunks/listbox-s8fkmnq8.js +0 -4
- package/dist/chunks/lit-html-ac4cwwr4.js +0 -4
- package/dist/chunks/media-picker-0svzkdyb.js +0 -4
- package/dist/chunks/menu-37d07z9m.js +0 -4
- package/dist/chunks/menu-5c55ea3d.js +0 -4
- package/dist/chunks/monaco-binding-z6r70pdt.js +0 -4
- package/dist/chunks/monaco-buffer-fz6gq5y7.js +0 -4
- package/dist/chunks/monaco-setup-q4axkgj7.js +0 -4
- package/dist/chunks/notify-0xgtn8q1.js +0 -4
- package/dist/chunks/number-field-gw7sq8rn.js +0 -4
- package/dist/chunks/parse-897fvqah.js +0 -4
- package/dist/chunks/platform-v1qctbe6.js +0 -4
- package/dist/chunks/popover-8htra4er.js +0 -4
- package/dist/chunks/publish-panel-ym1fmgq5.js +0 -4
- package/dist/chunks/quick-search-a54e6t5n.js +0 -4
- package/dist/chunks/references-gmqp82fs.js +0 -4
- package/dist/chunks/right-panel-v7t9awbk.js +0 -4
- package/dist/chunks/section-registry-vhyf3zsr.js +0 -4
- package/dist/chunks/select-n3v7gwas.js +0 -4
- package/dist/chunks/split-pff8f3ze.js +0 -4
- package/dist/chunks/studio-0bhx5hz6.js +0 -5
- package/dist/chunks/studio-0k1ddnyy.js +0 -6
- package/dist/chunks/studio-1b04pd0j.js +0 -64
- package/dist/chunks/studio-4dwbj6gb.js +0 -5
- package/dist/chunks/studio-4tanh828.js +0 -5
- package/dist/chunks/studio-55h4mstj.js +0 -4
- package/dist/chunks/studio-5tnvjkz4.js +0 -5
- package/dist/chunks/studio-60dqgb0c.js +0 -5
- package/dist/chunks/studio-61dp09qg.js +0 -9
- package/dist/chunks/studio-63epcrj2.js +0 -5
- package/dist/chunks/studio-74k29dy9.js +0 -5
- package/dist/chunks/studio-7vg6jcd3.js +0 -5
- package/dist/chunks/studio-7zx31knw.js +0 -9
- package/dist/chunks/studio-8bxk97km.js +0 -5
- package/dist/chunks/studio-933e30n4.js +0 -5
- package/dist/chunks/studio-98wnwhbg.js +0 -5
- package/dist/chunks/studio-9mccjwjh.js +0 -14
- package/dist/chunks/studio-a2ke1e4t.js +0 -5
- package/dist/chunks/studio-e3a2a6v3.js +0 -4
- package/dist/chunks/studio-e81q1vb9.js +0 -5
- package/dist/chunks/studio-epgrmqqm.js +0 -5
- package/dist/chunks/studio-ery42sqr.js +0 -4
- package/dist/chunks/studio-g1shz58g.js +0 -5
- package/dist/chunks/studio-gf0fz03c.js +0 -5
- package/dist/chunks/studio-gjtpe62q.js +0 -6
- package/dist/chunks/studio-gkskhg7m.js +0 -5
- package/dist/chunks/studio-gx4dnjzd.js +0 -5
- package/dist/chunks/studio-j03zga3y.js +0 -9
- package/dist/chunks/studio-jhpxeckz.js +0 -5
- package/dist/chunks/studio-k01r7ykp.js +0 -5
- package/dist/chunks/studio-kk7h30ne.js +0 -7
- package/dist/chunks/studio-kt1zqney.js +0 -5
- package/dist/chunks/studio-m3cwhvye.js +0 -5
- package/dist/chunks/studio-mcayt6hz.js +0 -5
- package/dist/chunks/studio-myjcghv3.js +0 -5
- package/dist/chunks/studio-n8bxwc3y.js +0 -4
- package/dist/chunks/studio-ngm8gvpb.js +0 -5
- package/dist/chunks/studio-nj5a01jy.js +0 -928
- package/dist/chunks/studio-pcgz8heb.js +0 -5
- package/dist/chunks/studio-qcsq0t15.js +0 -5
- package/dist/chunks/studio-qkstvve9.js +0 -5
- package/dist/chunks/studio-qv1pwd3c.js +0 -5
- package/dist/chunks/studio-rc6dvxjb.js +0 -7
- package/dist/chunks/studio-s8dmmz66.js +0 -5
- package/dist/chunks/studio-sp5xzbj4.js +0 -6
- package/dist/chunks/studio-szsjbj0g.js +0 -5
- package/dist/chunks/studio-t1xzawhr.js +0 -16
- package/dist/chunks/studio-tdcprbqb.js +0 -5
- package/dist/chunks/studio-vrmcjf9g.js +0 -7
- package/dist/chunks/studio-vyc0b4he.js +0 -10
- package/dist/chunks/studio-w0cgf6jg.js +0 -5
- package/dist/chunks/studio-w3hskzqk.js +0 -5
- package/dist/chunks/studio-xgrgcdyh.js +0 -9
- package/dist/chunks/studio-xz2yxfcb.js +0 -5
- package/dist/chunks/studio-y6n42tgs.js +0 -5
- package/dist/chunks/studio-yb560vxr.js +0 -5
- package/dist/chunks/studio-yn9f8vtx.js +0 -5
- package/dist/chunks/studio-yw05w8f0.js +0 -7
- package/dist/chunks/studio-z16sgavc.js +0 -5
- package/dist/chunks/studio-zey7zsdw.js +0 -12
- package/dist/chunks/studio-zyqn40w4.js +0 -5
- package/dist/chunks/swatch-group-8qh9a3bm.js +0 -4
- package/dist/chunks/tabs-jwthg40w.js +0 -4
- package/dist/chunks/textfield-spy3hc77.js +0 -4
- package/dist/chunks/toast-4d35zd64.js +0 -4
- package/dist/chunks/toast-host-8sjmfed3.js +0 -4
- package/dist/chunks/tooltip-fd6g9xy3.js +0 -4
- package/dist/chunks/tree-h19v1jfz.js +0 -4
- package/dist/chunks/tsMode-2frah6h1.js +0 -14
- package/dist/chunks/workspace-zt81g1ms.js +0 -4
- package/dist/chunks/ws-client-n2bh4hkf.js +0 -4
- package/src/panels/frontmatter-panel.ts +0 -701
- package/src/panels/git-panel.ts +0 -1120
- package/src/surfaces/doc-header.json +0 -776
- package/src/surfaces/doc-header.ts +0 -231
- package/src/surfaces/git-panel.json +0 -1880
- package/src/surfaces/git-panel.ts +0 -272
package/src/ui/schema-form.ts
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
* Schema-form — the reusable JSON-Schema → form engine, and the FLOW half of it.
|
|
4
4
|
*
|
|
5
5
|
* Maps schema property types to controls (enum → select, boolean → checkbox, number/integer →
|
|
6
|
-
* number field, `json-schema` format → JSON editor, array-of-objects → a row per item,
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* overrides.
|
|
6
|
+
* number field, `json-schema` format → JSON editor, array-of-objects → a row per item, an array of
|
|
7
|
+
* single values (text, numbers, pictures, entries) → a row per item too, other array/object → JSON
|
|
8
|
+
* text, default → text field); hosts commit edits through a single `onChange(patch)` callback;
|
|
9
|
+
* dynamic enum choices resolve through a {@link SchemaFormContext}. Custom controls register by
|
|
10
|
+
* name via {@link registerFormControl} and are consulted first for `ui` overrides.
|
|
11
11
|
*
|
|
12
12
|
* **This module decides; `surfaces/schema-form.json` draws.** Every question with an answer — which
|
|
13
13
|
* control a property gets, what its choices are, which rungs of the value ladder the position
|
|
@@ -39,7 +39,16 @@
|
|
|
39
39
|
* mount, updated rather than rebuilt on every repaint (specs/extensions.md §9.1).
|
|
40
40
|
*/
|
|
41
41
|
|
|
42
|
-
import {
|
|
42
|
+
import {
|
|
43
|
+
isBoolean,
|
|
44
|
+
isJsonObject,
|
|
45
|
+
isNonNullObject,
|
|
46
|
+
isNumber,
|
|
47
|
+
isRef,
|
|
48
|
+
isString,
|
|
49
|
+
} from "@jxsuite/schema/guards";
|
|
50
|
+
import type { ReadonlyJsonObject } from "@jxsuite/schema/project-schemas";
|
|
51
|
+
import type { JsonObject, JsonValue, JxDocumentValue } from "@jxsuite/schema/types";
|
|
43
52
|
import { createSchemaFormSurface } from "../surfaces/schema-form";
|
|
44
53
|
import { openMenu } from "../surfaces/menu";
|
|
45
54
|
import type { MenuHandle } from "../surfaces/menu";
|
|
@@ -58,15 +67,13 @@ import {
|
|
|
58
67
|
slotCaps,
|
|
59
68
|
} from "./value-source";
|
|
60
69
|
import type { SlotMode } from "./value-source";
|
|
61
|
-
import type { SignalOption } from "./dynamic-slot";
|
|
62
|
-
import type { JsonValue } from "../types";
|
|
63
70
|
import type {
|
|
64
71
|
SchemaFormCellView,
|
|
65
72
|
SchemaFormFieldView,
|
|
66
|
-
SchemaFormOption,
|
|
67
73
|
SchemaFormRowView,
|
|
68
74
|
SchemaFormSurface,
|
|
69
75
|
} from "../surfaces/schema-form";
|
|
76
|
+
import type { SurfaceOption } from "./surface";
|
|
70
77
|
|
|
71
78
|
/** A (possibly nested) JSON Schema node, covering both object and property level keys. */
|
|
72
79
|
export interface JsonSchema {
|
|
@@ -91,10 +98,19 @@ export interface JsonSchema {
|
|
|
91
98
|
$ref?: string;
|
|
92
99
|
}
|
|
93
100
|
|
|
101
|
+
/**
|
|
102
|
+
* What a form commits: the properties that changed, each with its new value. `undefined` removes
|
|
103
|
+
* the property — a cleared field is absent from the record, not stored as an empty string.
|
|
104
|
+
*/
|
|
105
|
+
export type SchemaFormPatch = Record<string, JsonValue | undefined>;
|
|
106
|
+
|
|
94
107
|
/** Host-provided context threaded to every control. */
|
|
95
108
|
export interface SchemaFormContext {
|
|
96
|
-
/**
|
|
97
|
-
|
|
109
|
+
/**
|
|
110
|
+
* Resolve a `#/$context/…` pointer (or legacy sentinel) against the host's project config. A
|
|
111
|
+
* config section is document data, so the answer is whatever the config holds there.
|
|
112
|
+
*/
|
|
113
|
+
resolvePointer: (pointer: string, scope?: JsonObject) => JxDocumentValue | JsonValue | undefined;
|
|
98
114
|
/** Route params available for `$ref` bindings (e.g. from the document path). */
|
|
99
115
|
params?: string[] | undefined;
|
|
100
116
|
/**
|
|
@@ -117,8 +133,8 @@ export interface SchemaFormContext {
|
|
|
117
133
|
export interface SchemaFormControlArgs {
|
|
118
134
|
key: string;
|
|
119
135
|
schema: JsonSchema;
|
|
120
|
-
value:
|
|
121
|
-
onChange: (next:
|
|
136
|
+
value: JsonValue | undefined;
|
|
137
|
+
onChange: (next: JsonValue | undefined) => void;
|
|
122
138
|
ctx: SchemaFormContext;
|
|
123
139
|
/**
|
|
124
140
|
* Ask the HOST to draw the form again, where it offered a way to.
|
|
@@ -153,14 +169,15 @@ export interface SchemaFormControlHandle {
|
|
|
153
169
|
|
|
154
170
|
/** Options for {@link mountSchemaForm}. */
|
|
155
171
|
export interface RenderFormOptions {
|
|
156
|
-
onChange: (patch:
|
|
172
|
+
onChange: (patch: SchemaFormPatch) => void;
|
|
157
173
|
context?: SchemaFormContext | undefined;
|
|
158
174
|
/**
|
|
159
175
|
* Per-field overrides from `$studio.settings.entry.ui`: a registered control name, and/or an
|
|
160
176
|
* `enum` source (choice list or `{ "$ref": "#/$context/<pointer>" }`) layered over the field
|
|
161
|
-
* schema — fragments stay valid JSON Schema while descriptors add dynamic choices.
|
|
177
|
+
* schema — fragments stay valid JSON Schema while descriptors add dynamic choices. `label` is the
|
|
178
|
+
* words the row is titled with in place of the property's key.
|
|
162
179
|
*/
|
|
163
|
-
ui?: Record<string, { control?: string; enum?: unknown }> | undefined;
|
|
180
|
+
ui?: Record<string, { control?: string; enum?: unknown; label?: string }> | undefined;
|
|
164
181
|
rerender?: (() => void) | undefined;
|
|
165
182
|
/**
|
|
166
183
|
* Externally-produced diagnostics, keyed by property name — §16.1's inline tier, sourced.
|
|
@@ -217,7 +234,9 @@ const CONTENT_REF = /^#\/content\/([^/]+)$/;
|
|
|
217
234
|
* reading of `$ref` is how the picker ends up existing three times with three behaviours.
|
|
218
235
|
*/
|
|
219
236
|
export function referenceTarget(schema: { $ref?: string } | undefined): string | null {
|
|
220
|
-
|
|
237
|
+
// The schema is project JSON, so the declared `string` is checked rather than trusted.
|
|
238
|
+
const ref = schema?.$ref;
|
|
239
|
+
const match = isString(ref) ? CONTENT_REF.exec(ref) : null;
|
|
221
240
|
return match ? match[1]! : null;
|
|
222
241
|
}
|
|
223
242
|
|
|
@@ -240,26 +259,30 @@ export const NULL_FORM_CONTEXT: SchemaFormContext = {
|
|
|
240
259
|
* and sentinel strings resolve through the context's pointer resolver, applying object →
|
|
241
260
|
* `Object.keys` and string[] → itself.
|
|
242
261
|
*
|
|
243
|
-
* @param {
|
|
262
|
+
* @param {JsonValue | undefined} enumDef
|
|
244
263
|
* @param {SchemaFormContext | undefined} ctx
|
|
245
|
-
* @param {
|
|
264
|
+
* @param {JsonObject} [scope] - Scope for `{@param}` substitution (the form value)
|
|
246
265
|
* @returns {string[] | undefined}
|
|
247
266
|
*/
|
|
248
267
|
export function resolveFormEnum(
|
|
249
|
-
enumDef:
|
|
268
|
+
enumDef: JsonValue | undefined,
|
|
250
269
|
ctx?: SchemaFormContext,
|
|
251
|
-
scope?:
|
|
270
|
+
scope?: JsonObject,
|
|
252
271
|
): string[] | undefined {
|
|
253
272
|
if (Array.isArray(enumDef)) {
|
|
273
|
+
/* SAFETY: a form `enum` is a list of string choices — every select this engine draws is fed by
|
|
274
|
+
a settings fragment or collection schema that declares `type: "string"` beside its enum, and
|
|
275
|
+
each choice is only ever interpolated as text (an option label and value), which is what a
|
|
276
|
+
stray number would be read as too. */
|
|
254
277
|
return enumDef as string[];
|
|
255
278
|
}
|
|
256
279
|
let pointer: string | undefined;
|
|
257
|
-
if (enumDef
|
|
258
|
-
const ref =
|
|
259
|
-
if (
|
|
280
|
+
if (isJsonObject(enumDef)) {
|
|
281
|
+
const ref = enumDef.$ref;
|
|
282
|
+
if (isString(ref)) {
|
|
260
283
|
pointer = ref;
|
|
261
284
|
}
|
|
262
|
-
} else if (
|
|
285
|
+
} else if (isString(enumDef)) {
|
|
263
286
|
// Legacy sentinel strings (e.g. "$contentTypes") resolve through the host's pointer resolver
|
|
264
287
|
pointer = enumDef;
|
|
265
288
|
}
|
|
@@ -270,7 +293,7 @@ export function resolveFormEnum(
|
|
|
270
293
|
if (Array.isArray(resolved)) {
|
|
271
294
|
return resolved.map(String);
|
|
272
295
|
}
|
|
273
|
-
if (resolved
|
|
296
|
+
if (isJsonObject(resolved)) {
|
|
274
297
|
return Object.keys(resolved);
|
|
275
298
|
}
|
|
276
299
|
return undefined;
|
|
@@ -296,7 +319,7 @@ function isBindableField(ps: JsonSchema): boolean {
|
|
|
296
319
|
}
|
|
297
320
|
|
|
298
321
|
/** Every pointer this form's host says a value may come from. */
|
|
299
|
-
function refSourcesFor(ctx: SchemaFormContext):
|
|
322
|
+
function refSourcesFor(ctx: SchemaFormContext): SurfaceOption[] {
|
|
300
323
|
return [
|
|
301
324
|
...(ctx.signals ?? []).map((name) => ({ label: name, value: `#/state/${name}` })),
|
|
302
325
|
...(ctx.params ?? []).map((name) => ({ label: `$params/${name}`, value: `#/$params/${name}` })),
|
|
@@ -307,11 +330,11 @@ function refSourcesFor(ctx: SchemaFormContext): SignalOption[] {
|
|
|
307
330
|
const NONE = "__none__";
|
|
308
331
|
|
|
309
332
|
/** A value as a control reads it: a string, and never `"[object Object]"`. */
|
|
310
|
-
function asText(value:
|
|
333
|
+
function asText(value: JsonValue | undefined): string {
|
|
311
334
|
if (value === undefined || value === null) {
|
|
312
335
|
return "";
|
|
313
336
|
}
|
|
314
|
-
return
|
|
337
|
+
return isNonNullObject(value) ? JSON.stringify(value, null, 2) : String(value);
|
|
315
338
|
}
|
|
316
339
|
|
|
317
340
|
// ─── Field validation (§16.1, inline tier) ───────────────────────────────────
|
|
@@ -328,13 +351,13 @@ function asText(value: unknown): string {
|
|
|
328
351
|
* cannot see, so type-checking the reference itself would refuse every correct binding.
|
|
329
352
|
*
|
|
330
353
|
* @param {JsonSchema} schema
|
|
331
|
-
* @param {
|
|
354
|
+
* @param {JsonValue | undefined} value
|
|
332
355
|
* @param {boolean} isRequired
|
|
333
356
|
* @returns {string}
|
|
334
357
|
*/
|
|
335
358
|
export function validateFieldValue(
|
|
336
359
|
schema: JsonSchema,
|
|
337
|
-
value:
|
|
360
|
+
value: JsonValue | undefined,
|
|
338
361
|
isRequired: boolean,
|
|
339
362
|
): string {
|
|
340
363
|
if (isRef(value)) {
|
|
@@ -348,7 +371,7 @@ export function validateFieldValue(
|
|
|
348
371
|
return `Choose one of: ${schema.enum.map(String).join(", ")}.`;
|
|
349
372
|
}
|
|
350
373
|
if (schema.type === "number" || schema.type === "integer") {
|
|
351
|
-
const n =
|
|
374
|
+
const n = isNumber(value) ? value : Number(value);
|
|
352
375
|
if (!Number.isFinite(n)) {
|
|
353
376
|
return "Enter a number.";
|
|
354
377
|
}
|
|
@@ -363,13 +386,14 @@ export function validateFieldValue(
|
|
|
363
386
|
}
|
|
364
387
|
return "";
|
|
365
388
|
}
|
|
366
|
-
if (schema.type === "boolean" &&
|
|
389
|
+
if (schema.type === "boolean" && !isBoolean(value)) {
|
|
367
390
|
return "Must be true or false.";
|
|
368
391
|
}
|
|
369
392
|
if (schema.type === "array" && !Array.isArray(value)) {
|
|
370
393
|
return "Must be a list.";
|
|
371
394
|
}
|
|
372
|
-
|
|
395
|
+
// `null` is "empty" and answered above, so only a primitive or an array can fail here.
|
|
396
|
+
if (schema.type === "object" && !isJsonObject(value)) {
|
|
373
397
|
return "Must be an object.";
|
|
374
398
|
}
|
|
375
399
|
return "";
|
|
@@ -392,17 +416,29 @@ interface FieldPlan {
|
|
|
392
416
|
/** Item-property schemas, for an array-of-objects field. */
|
|
393
417
|
itemProps?: Record<string, JsonSchema> | undefined;
|
|
394
418
|
/** The rows currently held, so a cell edit can rebuild the array. */
|
|
395
|
-
rows?:
|
|
419
|
+
rows?: JsonObject[] | undefined;
|
|
420
|
+
/**
|
|
421
|
+
* The field is an array of single values (`string[]`, a gallery of pictures), drawn as rows of
|
|
422
|
+
* one cell each: every row is `{ [SCALAR_CELL]: item }` here, and a commit writes the bare items
|
|
423
|
+
* back. One row editor for both shapes, rather than a list control beside the row control.
|
|
424
|
+
*/
|
|
425
|
+
scalarItems?: boolean | undefined;
|
|
396
426
|
/** The ladder, when this position offers one. */
|
|
397
|
-
ladder?: { fieldKey: string; mode: SlotMode; offered: SlotMode[]; sources:
|
|
427
|
+
ladder?: { fieldKey: string; mode: SlotMode; offered: SlotMode[]; sources: SurfaceOption[] };
|
|
398
428
|
/** A registered control owns the whole field; args are rebuilt for it on every repaint. */
|
|
399
429
|
control?: SchemaFormMountedControl | undefined;
|
|
430
|
+
/**
|
|
431
|
+
* The member of the property's OBJECT this row edits, when the row is one part of an object
|
|
432
|
+
* field: an image object's `alt` is a text row of its own, and its commit writes the whole object
|
|
433
|
+
* back with that one member changed.
|
|
434
|
+
*/
|
|
435
|
+
member?: string | undefined;
|
|
400
436
|
/**
|
|
401
437
|
* Registered controls owning one cell, keyed by the cell's host id.
|
|
402
438
|
*
|
|
403
439
|
* The same kind of entry a field's own control is, and reached by the same dispatch: a cell host
|
|
404
|
-
* is a host like any other.
|
|
405
|
-
* {@link deriveCell}).
|
|
440
|
+
* is a host like any other. A cell is given `reference` for an entry of another collection and
|
|
441
|
+
* `media` for a picture (see {@link deriveCell}).
|
|
406
442
|
*/
|
|
407
443
|
cellControls?: Map<
|
|
408
444
|
string,
|
|
@@ -427,11 +463,7 @@ const TEXT_COMMIT_MS = 400;
|
|
|
427
463
|
|
|
428
464
|
interface FormController {
|
|
429
465
|
readonly host: HTMLElement;
|
|
430
|
-
readonly update: (
|
|
431
|
-
schema: JsonSchema,
|
|
432
|
-
value: Record<string, unknown>,
|
|
433
|
-
opts: RenderFormOptions,
|
|
434
|
-
) => void;
|
|
466
|
+
readonly update: (schema: JsonSchema, value: JsonObject, opts: RenderFormOptions) => void;
|
|
435
467
|
readonly dispose: () => void;
|
|
436
468
|
}
|
|
437
469
|
|
|
@@ -452,14 +484,14 @@ export function resetSchemaForms(): void {
|
|
|
452
484
|
* @param {string} formKey Stable identity for this form — the same key updates the standing mount
|
|
453
485
|
* instead of building a second one, which is what keeps the caret in a field across a repaint.
|
|
454
486
|
* @param {JsonSchema} schema
|
|
455
|
-
* @param {
|
|
487
|
+
* @param {JsonObject} value The record being edited
|
|
456
488
|
* @param {RenderFormOptions} opts
|
|
457
489
|
* @returns {HTMLElement} The form's own host, for a lit caller to place in its tree
|
|
458
490
|
*/
|
|
459
491
|
export function mountSchemaForm(
|
|
460
492
|
formKey: string,
|
|
461
493
|
schema: JsonSchema,
|
|
462
|
-
value:
|
|
494
|
+
value: JsonObject,
|
|
463
495
|
opts: RenderFormOptions,
|
|
464
496
|
): HTMLElement {
|
|
465
497
|
sweep(formKey);
|
|
@@ -490,7 +522,7 @@ function sweep(exceptKey: string): void {
|
|
|
490
522
|
|
|
491
523
|
function createController(): FormController {
|
|
492
524
|
let schema: JsonSchema = {};
|
|
493
|
-
let value:
|
|
525
|
+
let value: JsonObject = {};
|
|
494
526
|
let opts: RenderFormOptions = { onChange: () => {} };
|
|
495
527
|
let ctx: SchemaFormContext = NULL_FORM_CONTEXT;
|
|
496
528
|
const plans = new Map<string, FieldPlan>();
|
|
@@ -515,7 +547,7 @@ function createController(): FormController {
|
|
|
515
547
|
run();
|
|
516
548
|
};
|
|
517
549
|
|
|
518
|
-
const patch = (prop: string, next:
|
|
550
|
+
const patch = (prop: string, next: JsonValue | undefined): void => {
|
|
519
551
|
opts.onChange({ [prop]: next });
|
|
520
552
|
};
|
|
521
553
|
|
|
@@ -546,7 +578,8 @@ function createController(): FormController {
|
|
|
546
578
|
}
|
|
547
579
|
case "json": {
|
|
548
580
|
try {
|
|
549
|
-
|
|
581
|
+
// SAFETY: `JSON.parse` with no reviver yields only JSON values, which is all a JsonValue is.
|
|
582
|
+
patch(plan.prop, JSON.parse(raw) as JsonValue);
|
|
550
583
|
} catch {
|
|
551
584
|
/* A half-typed object is not a refusal: the last parsable text is what the document
|
|
552
585
|
keeps, and the field goes on showing what is actually in it. */
|
|
@@ -554,14 +587,40 @@ function createController(): FormController {
|
|
|
554
587
|
return;
|
|
555
588
|
}
|
|
556
589
|
default: {
|
|
590
|
+
if (plan.member !== undefined) {
|
|
591
|
+
// One member of an object: the object goes back whole, with that member set or removed.
|
|
592
|
+
const held = value[plan.prop];
|
|
593
|
+
const base: JsonObject = isJsonObject(held) ? { ...held } : {};
|
|
594
|
+
if (raw) {
|
|
595
|
+
base[plan.member] = raw;
|
|
596
|
+
} else {
|
|
597
|
+
delete base[plan.member];
|
|
598
|
+
}
|
|
599
|
+
patch(plan.prop, Object.keys(base).length > 0 ? base : undefined);
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
557
602
|
patch(plan.prop, raw || undefined);
|
|
558
603
|
}
|
|
559
604
|
}
|
|
560
605
|
};
|
|
561
606
|
|
|
607
|
+
/**
|
|
608
|
+
* Write a row field's rows back as the array the document holds: the rows themselves for an array
|
|
609
|
+
* of objects, each row's one value for an array of single values. A row whose value was cleared
|
|
610
|
+
* stays as an empty string, so clearing the text of an item does not delete its row under the
|
|
611
|
+
* caret; Remove is how an item goes.
|
|
612
|
+
*/
|
|
613
|
+
const patchRows = (plan: FieldPlan, rows: JsonObject[]): void => {
|
|
614
|
+
let next: JsonValue | undefined;
|
|
615
|
+
if (rows.length > 0) {
|
|
616
|
+
next = plan.scalarItems ? rows.map((row) => row[SCALAR_CELL] ?? "") : rows;
|
|
617
|
+
}
|
|
618
|
+
patch(plan.prop, next);
|
|
619
|
+
};
|
|
620
|
+
|
|
562
621
|
/** The field's items, each shallow-copied so a cell edit never writes through to the document. */
|
|
563
|
-
const cellRows = (plan: FieldPlan):
|
|
564
|
-
const copies:
|
|
622
|
+
const cellRows = (plan: FieldPlan): JsonObject[] => {
|
|
623
|
+
const copies: JsonObject[] = [];
|
|
565
624
|
for (const item of plan.rows ?? []) {
|
|
566
625
|
copies.push({ ...item });
|
|
567
626
|
}
|
|
@@ -574,13 +633,15 @@ function createController(): FormController {
|
|
|
574
633
|
if (!plan?.itemProps) {
|
|
575
634
|
return;
|
|
576
635
|
}
|
|
577
|
-
const seed:
|
|
636
|
+
const seed: JsonObject = {};
|
|
578
637
|
for (const [k, v] of Object.entries(plan.itemProps)) {
|
|
579
638
|
if (v.default !== undefined) {
|
|
580
|
-
|
|
639
|
+
/* SAFETY: an item schema is read from a JSON schema document, so the `default` it
|
|
640
|
+
declares is a JSON value. */
|
|
641
|
+
seed[k] = v.default as JsonValue;
|
|
581
642
|
}
|
|
582
643
|
}
|
|
583
|
-
|
|
644
|
+
patchRows(plan, [...cellRows(plan), seed]);
|
|
584
645
|
opts.rerender?.();
|
|
585
646
|
},
|
|
586
647
|
commit: (key: string, raw: string) => {
|
|
@@ -622,13 +683,15 @@ function createController(): FormController {
|
|
|
622
683
|
return;
|
|
623
684
|
}
|
|
624
685
|
const idx = Number(row);
|
|
625
|
-
|
|
626
|
-
|
|
686
|
+
patchRows(
|
|
687
|
+
plan,
|
|
688
|
+
cellRows(plan).filter((_, i) => i !== idx),
|
|
689
|
+
);
|
|
627
690
|
opts.rerender?.();
|
|
628
691
|
},
|
|
629
692
|
};
|
|
630
693
|
|
|
631
|
-
function writeCell(field: string, row: string, cell: string, next:
|
|
694
|
+
function writeCell(field: string, row: string, cell: string, next: JsonValue | undefined): void {
|
|
632
695
|
const plan = plans.get(field);
|
|
633
696
|
if (!plan?.itemProps) {
|
|
634
697
|
return;
|
|
@@ -640,20 +703,23 @@ function createController(): FormController {
|
|
|
640
703
|
return;
|
|
641
704
|
}
|
|
642
705
|
const cellSchema = plan.itemProps[cell];
|
|
706
|
+
/* SAFETY: the one non-JSON member this row can gain is an `undefined` for a cleared cell, and
|
|
707
|
+
every host's `onChange` persists the patch as JSON (the entry, the def and the project config
|
|
708
|
+
are all serialized), where an undefined member is simply omitted. */
|
|
643
709
|
rows[idx] = {
|
|
644
710
|
...target,
|
|
645
711
|
[cell]: coerceCell(cellSchema, next, plan.refCells?.has(`${field}/${row}/${cell}`) ?? false),
|
|
646
|
-
};
|
|
647
|
-
|
|
712
|
+
} as JsonObject;
|
|
713
|
+
patchRows(plan, rows);
|
|
648
714
|
}
|
|
649
715
|
|
|
650
716
|
/** A cell's committed string, as the item property's declared type. */
|
|
651
717
|
function coerceCell(
|
|
652
718
|
cellSchema: JsonSchema | undefined,
|
|
653
|
-
next:
|
|
719
|
+
next: JsonValue | undefined,
|
|
654
720
|
isPointer: boolean,
|
|
655
|
-
):
|
|
656
|
-
if (
|
|
721
|
+
): JsonValue | undefined {
|
|
722
|
+
if (isBoolean(next)) {
|
|
657
723
|
return next;
|
|
658
724
|
}
|
|
659
725
|
const raw = String(next);
|
|
@@ -687,7 +753,7 @@ function createController(): FormController {
|
|
|
687
753
|
opener: anchor,
|
|
688
754
|
region: "value-source",
|
|
689
755
|
rows: offered.map((rung) => ({
|
|
690
|
-
checked:
|
|
756
|
+
checked: rung === mode ? ("true" as const) : ("false" as const),
|
|
691
757
|
destructive: false,
|
|
692
758
|
disabled: false,
|
|
693
759
|
dividerAbove: false,
|
|
@@ -695,12 +761,14 @@ function createController(): FormController {
|
|
|
695
761
|
title: VALUE_SOURCE_LABELS[rung],
|
|
696
762
|
})),
|
|
697
763
|
run: (id) => {
|
|
698
|
-
|
|
699
|
-
|
|
764
|
+
// The menu's rows are `offered`, so an id is always one of its rungs.
|
|
765
|
+
const next = offered.find((rung) => rung === id);
|
|
766
|
+
if (next === undefined || next === mode) {
|
|
700
767
|
return;
|
|
701
768
|
}
|
|
702
769
|
/* Leaving From data… seeds an empty Fixed value stash with the property's declared default,
|
|
703
770
|
so unbind-restores-default survives a detour through Mixed text. */
|
|
771
|
+
// SAFETY: the field schema is read from JSON, so the `default` it declares is a JSON value.
|
|
704
772
|
const literalDefault = plan.schema.default as JsonValue | undefined;
|
|
705
773
|
if (
|
|
706
774
|
mode === "ref" &&
|
|
@@ -715,7 +783,7 @@ function createController(): FormController {
|
|
|
715
783
|
fieldKey,
|
|
716
784
|
mode,
|
|
717
785
|
next,
|
|
718
|
-
cloneValue(value[plan.prop]
|
|
786
|
+
cloneValue(value[plan.prop]),
|
|
719
787
|
slotModeSeed(next, { extraSignals: sources, literalDefault, stateDefs: [] }),
|
|
720
788
|
),
|
|
721
789
|
);
|
|
@@ -853,32 +921,107 @@ function createController(): FormController {
|
|
|
853
921
|
/** Every row the document draws, and the plan that reads each one's gestures back. */
|
|
854
922
|
function deriveFields(
|
|
855
923
|
schema: JsonSchema,
|
|
856
|
-
value:
|
|
924
|
+
value: JsonObject,
|
|
857
925
|
opts: RenderFormOptions,
|
|
858
926
|
ctx: SchemaFormContext,
|
|
859
927
|
plans: Map<string, FieldPlan>,
|
|
860
928
|
): SchemaFormFieldView[] {
|
|
861
929
|
const required = new Set(schema.required);
|
|
862
930
|
const sources = refSourcesFor(ctx);
|
|
863
|
-
return Object.entries(schema.properties ?? {}).
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
931
|
+
return Object.entries(schema.properties ?? {}).flatMap(([prop, ps]) => {
|
|
932
|
+
const args = { ctx, opts, plans, required: required.has(prop), sources, value };
|
|
933
|
+
const field = deriveField(prop, ps, args);
|
|
934
|
+
// An image object's alt text is a row of its own, under the picture it describes.
|
|
935
|
+
return field.kind === "control" && isImageObjectSchema(ps) && ps.properties?.["alt"]
|
|
936
|
+
? [field, imageAltRow(prop, args)]
|
|
937
|
+
: [field];
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
/** The members an image object may have beside its `src`: what an `<img>` says about itself. */
|
|
942
|
+
const IMAGE_OBJECT_MEMBERS: ReadonlySet<string> = new Set([
|
|
943
|
+
"src",
|
|
944
|
+
"alt",
|
|
945
|
+
"width",
|
|
946
|
+
"height",
|
|
947
|
+
"title",
|
|
948
|
+
]);
|
|
949
|
+
|
|
950
|
+
/**
|
|
951
|
+
* Whether a property is an image OBJECT: `{ src, alt?, width?, height?, title? }`, the shape a site
|
|
952
|
+
* imported from a CMS keeps a picture in (`featuredImage: { src, width, height, alt }`). Such a
|
|
953
|
+
* field is a picture to pick, not a record to type JSON into.
|
|
954
|
+
*/
|
|
955
|
+
export function isImageObjectSchema(ps: JsonSchema): boolean {
|
|
956
|
+
const properties = ps.properties ?? {};
|
|
957
|
+
return (
|
|
958
|
+
ps.type === "object" &&
|
|
959
|
+
properties["src"]?.type === "string" &&
|
|
960
|
+
Object.keys(properties).every((name) => IMAGE_OBJECT_MEMBERS.has(name))
|
|
872
961
|
);
|
|
873
962
|
}
|
|
874
963
|
|
|
964
|
+
/** The media control over an image object's `src`, writing the object back around it. */
|
|
965
|
+
function imageObjectControl(media: SchemaFormMountedControl): SchemaFormMountedControl {
|
|
966
|
+
/** The args the media control sees: the `src`, and a commit that rebuilds the object. */
|
|
967
|
+
const srcArgs = (args: SchemaFormControlArgs): SchemaFormControlArgs => {
|
|
968
|
+
const held = isJsonObject(args.value) ? args.value : {};
|
|
969
|
+
const { src } = held;
|
|
970
|
+
return {
|
|
971
|
+
...args,
|
|
972
|
+
onChange: (next) => {
|
|
973
|
+
if (next === src) {
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
/* A different picture: its alt text and title still describe what the author meant, but the
|
|
977
|
+
old picture's width and height would be a lie about the new one, so they go. A cleared
|
|
978
|
+
field clears the whole object: an image with no picture is no image. */
|
|
979
|
+
const { height: _height, width: _width, ...kept } = held;
|
|
980
|
+
let rebuilt: JsonValue | undefined;
|
|
981
|
+
if (next !== undefined && next !== "") {
|
|
982
|
+
rebuilt = { ...kept, src: next };
|
|
983
|
+
}
|
|
984
|
+
args.onChange(rebuilt);
|
|
985
|
+
},
|
|
986
|
+
value: isString(src) ? src : undefined,
|
|
987
|
+
};
|
|
988
|
+
};
|
|
989
|
+
return {
|
|
990
|
+
mount(host, args) {
|
|
991
|
+
const handle = media.mount(host, srcArgs(args));
|
|
992
|
+
return {
|
|
993
|
+
dispose: () => handle.dispose(),
|
|
994
|
+
update: (next) => handle.update(srcArgs(next)),
|
|
995
|
+
};
|
|
996
|
+
},
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
/** The text row an image object's `alt` is edited in. */
|
|
1001
|
+
function imageAltRow(prop: string, args: DeriveArgs): SchemaFormFieldView {
|
|
1002
|
+
const key = `${prop}/alt`;
|
|
1003
|
+
const held = args.value[prop];
|
|
1004
|
+
const alt = isJsonObject(held) ? held["alt"] : undefined;
|
|
1005
|
+
const row = deriveField(key, { type: "string" }, { ...args, required: false });
|
|
1006
|
+
const plan = args.plans.get(key);
|
|
1007
|
+
if (plan) {
|
|
1008
|
+
plan.prop = prop;
|
|
1009
|
+
plan.member = "alt";
|
|
1010
|
+
}
|
|
1011
|
+
return {
|
|
1012
|
+
...row,
|
|
1013
|
+
label: `${args.opts.ui?.[prop]?.label ?? prop} alt text`,
|
|
1014
|
+
value: isString(alt) ? alt : "",
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
|
|
875
1018
|
interface DeriveArgs {
|
|
876
1019
|
ctx: SchemaFormContext;
|
|
877
1020
|
opts: RenderFormOptions;
|
|
878
1021
|
plans: Map<string, FieldPlan>;
|
|
879
1022
|
required: boolean;
|
|
880
|
-
sources:
|
|
881
|
-
value:
|
|
1023
|
+
sources: SurfaceOption[];
|
|
1024
|
+
value: JsonObject;
|
|
882
1025
|
}
|
|
883
1026
|
|
|
884
1027
|
function deriveField(prop: string, ps: JsonSchema, args: DeriveArgs): SchemaFormFieldView {
|
|
@@ -914,11 +1057,12 @@ function deriveField(prop: string, ps: JsonSchema, args: DeriveArgs): SchemaForm
|
|
|
914
1057
|
invalid: error !== "",
|
|
915
1058
|
key: prop,
|
|
916
1059
|
kind: "text",
|
|
917
|
-
label: prop,
|
|
1060
|
+
label: opts.ui?.[prop]?.label ?? prop,
|
|
918
1061
|
max: "",
|
|
919
1062
|
min: "",
|
|
920
1063
|
mono: false,
|
|
921
1064
|
multiline: false,
|
|
1065
|
+
inputType: "text",
|
|
922
1066
|
options: [],
|
|
923
1067
|
placeholder: "",
|
|
924
1068
|
prop: ps.name || prop,
|
|
@@ -938,7 +1082,10 @@ function deriveField(prop: string, ps: JsonSchema, args: DeriveArgs): SchemaForm
|
|
|
938
1082
|
const mode = effectiveSlotMode(fieldKey, current);
|
|
939
1083
|
/* A From data… rung with nothing to point at is a dead end — but this engine only ladders a
|
|
940
1084
|
field once the host has named a source, so the rung always has something in it. */
|
|
941
|
-
|
|
1085
|
+
/* SAFETY: `JsonSchema` names only the keywords this engine acts on; the node itself is a schema
|
|
1086
|
+
read from JSON (a settings fragment, a collection or plugin schema), so it and every keyword
|
|
1087
|
+
it carries beyond the named ones are JSON, which is all the ladder's walk reads it as. */
|
|
1088
|
+
const offered = slotCaps({ schema: configFieldSchema(ps as ReadonlyJsonObject) });
|
|
942
1089
|
plan.ladder = { fieldKey, mode, offered, sources };
|
|
943
1090
|
row.hasChip = true;
|
|
944
1091
|
row.sourceLabel = VALUE_SOURCE_LABELS[mode];
|
|
@@ -1002,7 +1149,33 @@ function deriveField(prop: string, ps: JsonSchema, args: DeriveArgs): SchemaForm
|
|
|
1002
1149
|
}
|
|
1003
1150
|
}
|
|
1004
1151
|
|
|
1005
|
-
|
|
1152
|
+
/* An image field (`format: "image"`) is the registered `media` control: the same field, Upload and
|
|
1153
|
+
Browse the page's Icon and OG Image rows draw (`surfaces/media-field.json`), so a collection
|
|
1154
|
+
entry's picture is picked from the project's media rather than typed as a path. Unregistered
|
|
1155
|
+
(a bare-Bun import of this engine), it falls through to the text control below. */
|
|
1156
|
+
if (ps.format === "image") {
|
|
1157
|
+
const media = controlRegistry.get("media");
|
|
1158
|
+
if (media) {
|
|
1159
|
+
plan.commit = "none";
|
|
1160
|
+
plan.control = media;
|
|
1161
|
+
row.kind = "control";
|
|
1162
|
+
return row;
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
/* An image OBJECT (`{ src, alt, width, height }`) is the same media field over its `src`; its
|
|
1166
|
+
`alt` is a text row of its own (`deriveFields`). Unregistered, it falls through to JSON. */
|
|
1167
|
+
const imageMedia = isImageObjectSchema(ps) ? controlRegistry.get("media") : undefined;
|
|
1168
|
+
if (imageMedia) {
|
|
1169
|
+
plan.commit = "none";
|
|
1170
|
+
plan.control = imageObjectControl(imageMedia);
|
|
1171
|
+
row.kind = "control";
|
|
1172
|
+
return row;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
/* SAFETY: both sources are read from JSON — the descriptor's `$studio.settings.entry.ui` and the
|
|
1176
|
+
field schema — so whichever `enum` is present is a JSON value. */
|
|
1177
|
+
const enumSource = (opts.ui?.[prop]?.enum ?? ps.enum) as JsonValue | undefined;
|
|
1178
|
+
const enumValues = resolveFormEnum(enumSource, ctx, value);
|
|
1006
1179
|
if (enumValues) {
|
|
1007
1180
|
plan.commit = "select";
|
|
1008
1181
|
plan.debounceMs = 0;
|
|
@@ -1041,13 +1214,16 @@ function deriveField(prop: string, ps: JsonSchema, args: DeriveArgs): SchemaForm
|
|
|
1041
1214
|
row.kind = "json";
|
|
1042
1215
|
row.value = current !== undefined ? JSON.stringify(current, null, 2) : "";
|
|
1043
1216
|
row.placeholder = ps.description ?? "JSON Schema defining the data shape…";
|
|
1044
|
-
row.chips =
|
|
1217
|
+
row.chips = schemaPropertyChips(current);
|
|
1045
1218
|
row.hasChips = row.chips.length > 0;
|
|
1046
1219
|
return row;
|
|
1047
1220
|
}
|
|
1048
1221
|
if (ps.type === "array" && ps.items?.type === "object" && ps.items.properties) {
|
|
1049
1222
|
return arrayOfObjects(prop, ps.items.properties, args, plan, row);
|
|
1050
1223
|
}
|
|
1224
|
+
if (ps.type === "array" && isSingleValueSchema(ps.items)) {
|
|
1225
|
+
return arrayOfSingleValues(prop, ps.items, args, plan, row);
|
|
1226
|
+
}
|
|
1051
1227
|
/* A union with an object or a list in it (`alerts` is an object of types or `false`) is JSON too:
|
|
1052
1228
|
a text box would store what is typed as a string, which is none of the members. */
|
|
1053
1229
|
const types = Array.isArray(ps.type) ? ps.type : [ps.type];
|
|
@@ -1062,21 +1238,41 @@ function deriveField(prop: string, ps: JsonSchema, args: DeriveArgs): SchemaForm
|
|
|
1062
1238
|
row.kind = "text";
|
|
1063
1239
|
row.value = asText(current);
|
|
1064
1240
|
row.placeholder = ps.default !== undefined ? String(ps.default) : (ps.examples?.[0] ?? "");
|
|
1241
|
+
row.inputType = TEXT_INPUT_TYPES.get(ps.format ?? "") ?? "text";
|
|
1242
|
+
/* A date input shows nothing for a value it cannot read, so a timestamp is shown as its day. YAML
|
|
1243
|
+
reads an unquoted date as a Date, which reaches here as its quoted JSON. */
|
|
1244
|
+
const day = /^"?(\d{4}-\d{2}-\d{2})/.exec(row.value);
|
|
1245
|
+
if (row.inputType === "date" && day) {
|
|
1246
|
+
row.value = day[1] ?? row.value;
|
|
1247
|
+
}
|
|
1065
1248
|
return row;
|
|
1066
1249
|
}
|
|
1067
1250
|
|
|
1251
|
+
/**
|
|
1252
|
+
* The native input a string format is typed in. `date` is the one that changes what can be typed: a
|
|
1253
|
+
* date picker, and a value that is always `YYYY-MM-DD`, the `full-date` JSON Schema's `date` names.
|
|
1254
|
+
* `email` and `uri` only change the keyboard a phone shows, and the value stays the text typed.
|
|
1255
|
+
*/
|
|
1256
|
+
const TEXT_INPUT_TYPES: ReadonlyMap<string, string> = new Map([
|
|
1257
|
+
["date", "date"],
|
|
1258
|
+
["email", "email"],
|
|
1259
|
+
["uri", "url"],
|
|
1260
|
+
]);
|
|
1261
|
+
|
|
1068
1262
|
/** The property chips above a JSON-Schema field: what shape is in there right now. */
|
|
1069
|
-
function
|
|
1070
|
-
|
|
1263
|
+
function schemaPropertyChips(current: JsonValue | undefined): { key: string; label: string }[] {
|
|
1264
|
+
// An array names no `properties`, so it has no chips either.
|
|
1265
|
+
if (!isJsonObject(current)) {
|
|
1071
1266
|
return [];
|
|
1072
1267
|
}
|
|
1073
|
-
const
|
|
1074
|
-
if (
|
|
1268
|
+
const { properties } = current;
|
|
1269
|
+
if (current.$ref || properties === null || !isNonNullObject(properties)) {
|
|
1075
1270
|
return [];
|
|
1076
1271
|
}
|
|
1077
|
-
return Object.entries(
|
|
1078
|
-
|
|
1079
|
-
|
|
1272
|
+
return Object.entries(properties).map(([key, spec]) => ({
|
|
1273
|
+
key,
|
|
1274
|
+
label: `${key}: ${String((isJsonObject(spec) ? spec.type : undefined) ?? "any")}`,
|
|
1275
|
+
}));
|
|
1080
1276
|
}
|
|
1081
1277
|
|
|
1082
1278
|
/** An array whose items have a declared shape: one row per item, one cell per property. */
|
|
@@ -1088,7 +1284,10 @@ function arrayOfObjects(
|
|
|
1088
1284
|
row: SchemaFormFieldView,
|
|
1089
1285
|
): SchemaFormFieldView {
|
|
1090
1286
|
const held = args.value[prop];
|
|
1091
|
-
|
|
1287
|
+
/* SAFETY: the caller dispatches here only for a property whose schema declares
|
|
1288
|
+
`items.type: "object"`, so a conforming record holds an object per item; a malformed item is
|
|
1289
|
+
still drawn as the row it should be, as it always was, rather than silently dropped. */
|
|
1290
|
+
const rows = Array.isArray(held) ? (held as JsonObject[]) : [];
|
|
1092
1291
|
plan.commit = "none";
|
|
1093
1292
|
plan.itemProps = itemProps;
|
|
1094
1293
|
plan.rows = rows;
|
|
@@ -1099,10 +1298,73 @@ function arrayOfObjects(
|
|
|
1099
1298
|
return row;
|
|
1100
1299
|
}
|
|
1101
1300
|
|
|
1301
|
+
/** The one cell key a row of an array of single values is drawn under. */
|
|
1302
|
+
const SCALAR_CELL = "value";
|
|
1303
|
+
|
|
1304
|
+
/**
|
|
1305
|
+
* Whether an array's items are single values a cell can edit: text, a number, a yes/no, one of an
|
|
1306
|
+
* enum, or an entry of another collection. A picture is text with `format: "image"`, so it is one.
|
|
1307
|
+
*/
|
|
1308
|
+
function isSingleValueSchema(items: JsonSchema | undefined): items is JsonSchema {
|
|
1309
|
+
if (items === undefined || items.format === "json-schema") {
|
|
1310
|
+
return false;
|
|
1311
|
+
}
|
|
1312
|
+
if (referenceTarget(items) !== null || Array.isArray(items.enum)) {
|
|
1313
|
+
return true;
|
|
1314
|
+
}
|
|
1315
|
+
return (
|
|
1316
|
+
items.type === "string" ||
|
|
1317
|
+
items.type === "number" ||
|
|
1318
|
+
items.type === "integer" ||
|
|
1319
|
+
items.type === "boolean"
|
|
1320
|
+
);
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
/**
|
|
1324
|
+
* An array of single values (`categories: string[]`, a gallery's picture paths): one row per item,
|
|
1325
|
+
* each a single cell drawn by the item schema, with the same remove and add an array of objects
|
|
1326
|
+
* gets. A value that is not yet a list (a CSV cell holding one category) is shown as its one item,
|
|
1327
|
+
* and becomes a list the moment it is edited; the field's own check still says it is not one.
|
|
1328
|
+
*/
|
|
1329
|
+
function arrayOfSingleValues(
|
|
1330
|
+
prop: string,
|
|
1331
|
+
items: JsonSchema,
|
|
1332
|
+
args: DeriveArgs,
|
|
1333
|
+
plan: FieldPlan,
|
|
1334
|
+
row: SchemaFormFieldView,
|
|
1335
|
+
): SchemaFormFieldView {
|
|
1336
|
+
const held = args.value[prop];
|
|
1337
|
+
let list: JsonValue[] = [];
|
|
1338
|
+
if (Array.isArray(held)) {
|
|
1339
|
+
list = held;
|
|
1340
|
+
} else if (held !== undefined && held !== null && held !== "") {
|
|
1341
|
+
list = [held];
|
|
1342
|
+
}
|
|
1343
|
+
const rows: JsonObject[] = list.map((item) => ({ [SCALAR_CELL]: item }));
|
|
1344
|
+
const itemProps = { [SCALAR_CELL]: items };
|
|
1345
|
+
const label = args.opts.ui?.[prop]?.label ?? prop;
|
|
1346
|
+
plan.commit = "none";
|
|
1347
|
+
plan.itemProps = itemProps;
|
|
1348
|
+
plan.rows = rows;
|
|
1349
|
+
plan.scalarItems = true;
|
|
1350
|
+
plan.cellControls = new Map();
|
|
1351
|
+
plan.refCells = new Set();
|
|
1352
|
+
row.kind = "rows";
|
|
1353
|
+
row.rows = rows.map((item, idx) => {
|
|
1354
|
+
const drawn = deriveRow(prop, String(idx), item, itemProps, args, plan);
|
|
1355
|
+
// A row has one cell, so the cell is named by its place in the list rather than by `value`.
|
|
1356
|
+
for (const cell of drawn.cells) {
|
|
1357
|
+
cell.label = `${label} ${idx + 1}`;
|
|
1358
|
+
}
|
|
1359
|
+
return drawn;
|
|
1360
|
+
});
|
|
1361
|
+
return row;
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1102
1364
|
function deriveRow(
|
|
1103
1365
|
prop: string,
|
|
1104
1366
|
key: string,
|
|
1105
|
-
item:
|
|
1367
|
+
item: JsonObject,
|
|
1106
1368
|
itemProps: Record<string, JsonSchema>,
|
|
1107
1369
|
args: DeriveArgs,
|
|
1108
1370
|
plan: FieldPlan,
|
|
@@ -1122,7 +1384,7 @@ function deriveCell(
|
|
|
1122
1384
|
rowKey: string,
|
|
1123
1385
|
cellKey: string,
|
|
1124
1386
|
cellSchema: JsonSchema,
|
|
1125
|
-
held:
|
|
1387
|
+
held: JsonValue | undefined,
|
|
1126
1388
|
args: DeriveArgs,
|
|
1127
1389
|
plan: FieldPlan,
|
|
1128
1390
|
): SchemaFormCellView {
|
|
@@ -1144,10 +1406,10 @@ function deriveCell(
|
|
|
1144
1406
|
return cell;
|
|
1145
1407
|
}
|
|
1146
1408
|
if (referenceTarget(cellSchema) !== null) {
|
|
1147
|
-
/* A CELL's control is mounted into a host of the cell's own, exactly as a field's is: the
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1409
|
+
/* A CELL's control is mounted into a host of the cell's own, exactly as a field's is: the
|
|
1410
|
+
reference picker is a document now (`surfaces/reference-field.json`), as the media field
|
|
1411
|
+
below is. What the cell host must never become is a second writer of a node the document
|
|
1412
|
+
already owns. */
|
|
1151
1413
|
const reference = controlRegistry.get("reference");
|
|
1152
1414
|
if (reference) {
|
|
1153
1415
|
cell.kind = "control";
|
|
@@ -1160,13 +1422,26 @@ function deriveCell(
|
|
|
1160
1422
|
return cell;
|
|
1161
1423
|
}
|
|
1162
1424
|
}
|
|
1163
|
-
|
|
1425
|
+
/* A picture cell (`format: "image"`, a gallery's item) is the media control a picture FIELD
|
|
1426
|
+
gets, mounted into the cell's host by the same dispatch. */
|
|
1427
|
+
const media = cellSchema.format === "image" ? controlRegistry.get("media") : undefined;
|
|
1428
|
+
if (media) {
|
|
1429
|
+
cell.kind = "control";
|
|
1430
|
+
plan.cellControls?.set(id, { control: media, key: cellKey, row: rowKey, schema: cellSchema });
|
|
1431
|
+
return cell;
|
|
1432
|
+
}
|
|
1433
|
+
// SAFETY: an item property's schema is read from JSON, so its `enum` is a JSON value.
|
|
1434
|
+
const enumValues = resolveFormEnum(
|
|
1435
|
+
cellSchema.enum as JsonValue | undefined,
|
|
1436
|
+
args.ctx,
|
|
1437
|
+
args.value,
|
|
1438
|
+
);
|
|
1164
1439
|
if (enumValues) {
|
|
1165
1440
|
cell.kind = "select";
|
|
1166
1441
|
cell.value = held !== undefined ? String(held) : NONE;
|
|
1167
1442
|
cell.options = [
|
|
1168
1443
|
{ label: "—", value: NONE },
|
|
1169
|
-
...enumValues.map((v: string):
|
|
1444
|
+
...enumValues.map((v: string): SurfaceOption => ({ label: v, value: v })),
|
|
1170
1445
|
];
|
|
1171
1446
|
return cell;
|
|
1172
1447
|
}
|