@jxsuite/studio 4.1.0 → 5.1.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 +1 -1
- package/data/elements-meta.json +5 -15
- package/data/html-meta.json +51 -6
- package/dist/chunks/{2020-n6tfwnda.js → 2020-g79n01v9.js} +14 -14
- package/dist/chunks/action-group-sp6tna8v.js +118 -0
- package/dist/chunks/adapter-nj7tnjfn.js +13 -0
- package/dist/chunks/cf-account-picker-qwkm9zrf.js +495 -0
- package/dist/chunks/cf-settings-71bv5zn9.js +20 -0
- package/dist/chunks/color-area-v17df7b8.js +154 -0
- package/dist/chunks/color-field-anwybnkn.js +208 -0
- package/dist/chunks/color-slider-44ez96fc.js +79 -0
- package/dist/chunks/color-xgg3kfw7.js +35 -0
- package/dist/chunks/combobox-5e7jev23.js +236 -0
- package/dist/chunks/components-vj72gqtq.js +20 -0
- package/dist/chunks/content-source-5mex4x1h.js +46 -0
- package/dist/chunks/convert-to-repeater-n64h8znc.js +639 -0
- package/dist/chunks/dialog-s890b918.js +85 -0
- package/dist/chunks/editor-3qhbg51x.js +35 -0
- package/dist/chunks/entry-commands-234hjnqw.js +42 -0
- package/dist/chunks/extension-sections-3hpcambp.js +651 -0
- package/dist/chunks/file-ops-8yhd83xr.js +47 -0
- package/dist/chunks/files-hnxqxtsg.js +72 -0
- package/dist/chunks/format-host-779sfp2n.js +40 -0
- package/dist/chunks/fs-events-b88z7g4h.js +18 -0
- package/dist/chunks/git-panel-xwj8fpab.js +49 -0
- package/dist/chunks/icons-yy10wk9r.js +402 -0
- package/dist/chunks/iframe-entry-15g3n6xv.js +114 -0
- package/dist/chunks/iframe-entry-b27vq81n.js +203 -0
- package/dist/chunks/iframe-entry-nbyxee1y.js +154 -0
- package/dist/chunks/iframe-host-syr0f9sb.js +115 -0
- package/dist/chunks/{index-wcnt3r9g.js → index-mdmyx6s2.js} +11 -11
- package/dist/chunks/index-n4j7qxyr.js +109 -0
- package/dist/chunks/{javascript-q8kpmfah.js → javascript-xvrfrpkb.js} +8 -9
- package/dist/chunks/{jsonMode-dkr040ga.js → jsonMode-tyx2kj6p.js} +526 -527
- package/dist/chunks/jx-validate-j0wxk72w.js +15 -0
- package/dist/chunks/layers-ff81wthn.js +30 -0
- package/dist/chunks/layers-panel-pnnghw12.js +40 -0
- package/dist/chunks/listbox-6v2ac08j.js +44 -0
- package/dist/chunks/lit-html-m244w9nh.js +17 -0
- package/dist/chunks/media-picker-cqhhxhhe.js +33 -0
- package/dist/chunks/menu-9p2w46gj.js +15 -0
- package/dist/chunks/menu-q99sy9rf.js +285 -0
- package/dist/chunks/{monaco-binding-rj6xpx9y.js → monaco-binding-sw6en4dw.js} +12 -15
- package/dist/chunks/monaco-buffer-bhy3hb6m.js +24 -0
- package/dist/chunks/{monaco-setup-vx9bhp1d.js → monaco-setup-8r62fj6y.js} +27 -37
- package/dist/chunks/notify-628t0anh.js +21 -0
- package/dist/chunks/number-field-5cndyft8.js +72 -0
- package/dist/chunks/parse-0zx14y15.js +10 -0
- package/dist/chunks/platform-ts9cg1a4.js +15 -0
- package/dist/chunks/popover-47z3ytva.js +25 -0
- package/dist/chunks/publish-panel-5zt840sy.js +27 -0
- package/dist/chunks/quick-search-3748564r.js +45 -0
- package/dist/chunks/references-qvvxej7h.js +26 -0
- package/dist/chunks/right-panel-eccz92qt.js +45 -0
- package/dist/chunks/section-registry-5pq32t0q.js +25 -0
- package/dist/chunks/select-2r16ez41.js +49 -0
- package/dist/chunks/split-g7nm1rgb.js +191 -0
- package/dist/chunks/studio-0hqbpbd0.js +62 -0
- package/dist/chunks/{studio-kryvsw6j.js → studio-1k325pwf.js} +48 -48
- package/dist/chunks/{studio-b55y6ewx.js → studio-1knfqgy7.js} +58 -58
- package/dist/chunks/{studio-2b946z26.js → studio-1z1kcjv4.js} +3628 -6095
- package/dist/chunks/studio-3773086z.js +111 -0
- package/dist/chunks/{studio-4r1pv6zf.js → studio-38emantv.js} +22 -5
- package/dist/chunks/{studio-x813zp48.js → studio-3rz5md06.js} +30 -30
- package/dist/chunks/{studio-ynxwkrzd.js → studio-4wbb9ct0.js} +1953 -1785
- package/dist/chunks/studio-5te5tqng.js +81 -0
- package/dist/chunks/studio-5wt9nvhd.js +4763 -0
- package/dist/chunks/{studio-p4ngc046.js → studio-6ewqv390.js} +9 -9
- package/dist/chunks/{studio-9ab8gqsm.js → studio-6nzaktvy.js} +7 -7
- package/dist/chunks/studio-6xa21ej9.js +28 -0
- package/dist/chunks/{studio-5ehhackm.js → studio-727xh0e3.js} +19 -19
- package/dist/chunks/{studio-gcye7qka.js → studio-7e11jpa9.js} +21 -21
- package/dist/chunks/{studio-q7v10cg2.js → studio-7r42c6ca.js} +17 -7
- package/dist/chunks/studio-7xnmgv66.js +13615 -0
- package/dist/chunks/studio-8scdew0e.js +258 -0
- package/dist/chunks/{studio-fbf92ckf.js → studio-93zafjcm.js} +275 -163
- package/dist/chunks/{studio-dg7zyt5x.js → studio-98b3z07n.js} +1 -1
- package/dist/chunks/studio-a79k90n9.js +1162 -0
- package/dist/chunks/studio-aj37eer3.js +422 -0
- package/dist/chunks/studio-b0k5tqgp.js +2432 -0
- package/dist/chunks/studio-b4s9eykd.js +2811 -0
- package/dist/chunks/{studio-f3wqjtk5.js → studio-d0300636.js} +5076 -18
- package/dist/chunks/{studio-b9b6t1dg.js → studio-e1f24eff.js} +5664 -5666
- package/dist/chunks/studio-e1pgga3k.js +839 -0
- package/dist/chunks/{studio-g1wdche0.js → studio-esrxgwh6.js} +80 -80
- package/dist/chunks/{studio-q43a5y9h.js → studio-g3jevyjt.js} +16 -16
- package/dist/chunks/studio-gb13095a.js +2806 -0
- package/dist/chunks/{studio-3n4ndrzh.js → studio-h4nvnjvz.js} +303 -303
- package/dist/chunks/{studio-dm19q43r.js → studio-hhvs4sdg.js} +10 -10
- package/dist/chunks/{studio-x4qkdat5.js → studio-js8nv53n.js} +1749 -2481
- package/dist/chunks/{studio-qjy2z9bv.js → studio-jxp12rnj.js} +32 -137
- package/dist/chunks/{studio-trxrabqf.js → studio-k75n7vhp.js} +527 -283
- package/dist/chunks/{studio-q9jd1a60.js → studio-kcgff4r0.js} +15 -18
- package/dist/chunks/studio-kkqkcp3d.js +17668 -0
- package/dist/chunks/studio-mhhyamxf.js +763 -0
- package/dist/chunks/{studio-cft9r4r7.js → studio-nq5yf5mp.js} +22 -22
- package/dist/chunks/{studio-685s0ytq.js → studio-nqgawfks.js} +572 -572
- package/dist/chunks/studio-nzzpvzx9.js +185 -0
- package/dist/chunks/{studio-xrapxwwc.js → studio-p1dw8wfw.js} +55 -29
- package/dist/chunks/{studio-3bqpr0zh.js → studio-pxbgypmg.js} +299 -110
- package/dist/chunks/studio-q0a3yk4n.js +33 -0
- package/dist/chunks/{studio-wcy8trfx.js → studio-q3x06vmj.js} +23 -22
- package/dist/chunks/{studio-6yw4y3my.js → studio-qhhm6hbt.js} +8 -8
- package/dist/chunks/studio-qk4bfsq2.js +362 -0
- package/dist/chunks/{studio-msd847zn.js → studio-qk90fgqm.js} +361 -90
- package/dist/chunks/{studio-jkftxyvz.js → studio-r6nvacqs.js} +5861 -2154
- package/dist/chunks/{studio-53n8bp04.js → studio-r6xmmtfh.js} +1 -1
- package/dist/chunks/{studio-tvgyw9xw.js → studio-rnyf3f8e.js} +83 -250
- package/dist/chunks/studio-rs76k67r.js +12 -0
- package/dist/chunks/studio-sbjyc8bt.js +954 -0
- package/dist/chunks/{studio-1wagb550.js → studio-syg2tadj.js} +11 -11
- package/dist/chunks/{studio-t5wqwzdh.js → studio-tjjk7jvk.js} +1 -1
- package/dist/chunks/studio-tqgryxwg.js +1171 -0
- package/dist/chunks/studio-try13h8c.js +8448 -0
- package/dist/chunks/studio-vfq8qpjt.js +1219 -0
- package/dist/chunks/studio-vhv5tgxm.js +51 -0
- package/dist/chunks/{studio-ahn3pdgx.js → studio-vw7mzp6z.js} +52 -38
- package/dist/chunks/studio-wqpngpsk.js +174 -0
- package/dist/chunks/studio-wqz6dggm.js +1356 -0
- package/dist/chunks/{studio-92ab2jqe.js → studio-y9zedyjn.js} +14 -14
- package/dist/chunks/studio-yxa81f45.js +12 -0
- package/dist/chunks/studio-zdn2g6dt.js +148 -0
- package/dist/chunks/swatch-group-t0n6h2rn.js +139 -0
- package/dist/chunks/tabs-wbp1cdws.js +156 -0
- package/dist/chunks/textfield-abk6qyee.js +73 -0
- package/dist/chunks/toast-780pvfbv.js +27 -0
- package/dist/chunks/toast-host-3sqv46sn.js +116 -0
- package/dist/chunks/toolbar-xbh5bhtr.js +194 -0
- package/dist/chunks/tooltip-eptb614w.js +369 -0
- package/dist/chunks/tree-a73p0pqm.js +367 -0
- package/dist/chunks/{tsMode-76kr53q0.js → tsMode-jy4m86rv.js} +83 -85
- package/dist/chunks/workspace-t55jdjbx.js +69 -0
- package/dist/chunks/{ws-client-6shc5rze.js → ws-client-ny4zc51r.js} +20 -22
- package/dist/iframe-entry.js +1873 -673
- package/dist/studio.js +52757 -36535
- package/dist/workers/editor.worker.js +1040 -1085
- package/dist/workers/json.worker.js +1946 -1991
- package/dist/workers/ts.worker.js +4960 -5005
- package/index.html +1 -0
- package/package.json +18 -52
- package/src/about/about-modal.ts +72 -123
- package/src/browse/library-commands.ts +20 -49
- package/src/browse/library-layouts.ts +87 -218
- package/src/browse/library-pane.ts +406 -438
- package/src/canvas/canvas-helpers.ts +0 -49
- package/src/canvas/canvas-live-render.ts +25 -2
- package/src/canvas/canvas-render.ts +666 -231
- package/src/canvas/canvas-surface.ts +0 -1
- package/src/canvas/canvas-utils.ts +333 -113
- package/src/canvas/dialog-path.ts +76 -0
- package/src/canvas/dialog-state.ts +67 -0
- package/src/canvas/diff-marks.ts +252 -0
- package/src/canvas/diff-toolbar.ts +349 -0
- package/src/canvas/diff-view.ts +123 -0
- package/src/canvas/edit-width-drag.ts +187 -0
- package/src/canvas/edit-width.ts +150 -0
- package/src/canvas/iframe-entry.ts +125 -8
- package/src/canvas/iframe-host.ts +271 -15
- package/src/canvas/iframe-interaction.ts +133 -13
- package/src/canvas/iframe-patch.ts +7 -1
- package/src/canvas/iframe-position.ts +5 -6
- package/src/canvas/iframe-protocol.ts +106 -5
- package/src/canvas/iframe-render.ts +373 -5
- package/src/canvas/iframe-slash.ts +2 -2
- package/src/canvas/iframe-subtree.ts +5 -8
- package/src/canvas/path-mapping.ts +25 -3
- package/src/canvas/popover-path.ts +106 -0
- package/src/canvas/popover-state.ts +155 -0
- package/src/canvas/surface-registry.ts +21 -10
- package/src/collab/presence-chips.ts +51 -73
- package/src/commands/app-commands.ts +10 -0
- package/src/commands/budget.ts +12 -0
- package/src/commands/command-args.ts +404 -6
- package/src/commands/defaults.ts +44 -14
- package/src/commands/live-context.ts +11 -5
- package/src/commands/reference.ts +12 -2
- package/src/commands/registry.ts +177 -10
- package/src/commands/run-reported.ts +120 -0
- package/src/content/entry-commands.ts +16 -23
- package/src/content/entry-editor.ts +144 -101
- package/src/editor/canvas-slash-bridge.ts +2 -2
- package/src/editor/context-menu.ts +49 -200
- package/src/editor/convert-to-component.ts +42 -82
- package/src/editor/convert-to-repeater.ts +132 -173
- package/src/editor/inline-edit.ts +11 -0
- package/src/editor/shortcuts.ts +76 -49
- package/src/editor/slash-menu.ts +150 -157
- package/src/files/components.ts +39 -0
- package/src/files/file-ops.ts +117 -11
- package/src/files/files.ts +944 -660
- package/src/files/json-layout.ts +474 -0
- package/src/files/serialize-document.ts +8 -1
- package/src/format/convert-file.ts +16 -10
- package/src/format/format-host.ts +50 -1
- package/src/github/github-auth.ts +71 -29
- package/src/github/github-publish.ts +57 -65
- package/src/grid/cell-editors.ts +46 -4
- package/src/grid/cell-popovers.ts +65 -62
- package/src/grid/grid-open.ts +86 -64
- package/src/grid/grid-panel.ts +390 -469
- package/src/grid/redirects-grid.ts +40 -44
- package/src/hosting/layout.ts +2 -0
- package/src/i18n/i18n-commands.ts +137 -62
- package/src/media/media-pane.ts +83 -133
- package/src/new-project/add-repo-modal.ts +125 -176
- package/src/new-project/import-tab.ts +181 -252
- package/src/new-project/location-fields.ts +93 -160
- package/src/new-project/new-project-modal.ts +376 -367
- package/src/packages/jxsuite-update.ts +76 -18
- package/src/packages/pull-package-sync.ts +5 -1
- package/src/panels/activity-panel.ts +137 -104
- package/src/panels/ai-chat/chat-markdown.ts +15 -13
- package/src/panels/ai-chat/chat-view.ts +307 -472
- package/src/panels/ai-chat/composer.ts +175 -163
- package/src/panels/ai-chat/sessions-view.ts +31 -62
- package/src/panels/ai-panel.ts +213 -185
- package/src/panels/block-action-bar.ts +444 -645
- package/src/panels/bottom-dock.ts +112 -47
- package/src/panels/chat-panel.ts +19 -14
- package/src/panels/data-explorer.ts +141 -102
- package/src/panels/data-grid.ts +183 -150
- package/src/panels/dnd.ts +75 -20
- package/src/panels/editors.ts +26 -19
- package/src/panels/elements-panel.ts +243 -186
- package/src/panels/empty-state.ts +26 -42
- package/src/panels/events-panel.ts +950 -729
- package/src/panels/formula-workspace.ts +434 -207
- package/src/panels/frontmatter-fields.ts +185 -153
- package/src/panels/frontmatter-panel.ts +411 -187
- package/src/panels/git-diff-open.ts +148 -0
- package/src/panels/git-panel.ts +679 -605
- package/src/panels/head-panel.ts +617 -435
- package/src/panels/i18n-panel.ts +148 -135
- package/src/panels/imports-panel.ts +388 -414
- package/src/panels/jump-bar.ts +175 -147
- package/src/panels/layers-panel.ts +785 -671
- package/src/panels/left-panel.ts +166 -94
- package/src/panels/navigator-panels.ts +1 -1
- package/src/panels/pane-context.ts +529 -677
- package/src/panels/pane-grid.ts +265 -241
- package/src/panels/panel-registry.ts +15 -4
- package/src/panels/problems-panel.ts +181 -99
- package/src/panels/properties-panel.ts +1525 -966
- package/src/panels/provenance.ts +31 -113
- package/src/panels/quick-search.ts +223 -184
- package/src/panels/right-panel.ts +135 -169
- package/src/panels/seo-modal.ts +346 -167
- package/src/panels/settings-menu.ts +114 -661
- package/src/panels/settings-pane.ts +102 -86
- package/src/panels/signals-panel.ts +1318 -1172
- package/src/panels/statement-editor.ts +556 -428
- package/src/panels/style-panel.ts +1367 -630
- package/src/panels/style-utils.ts +36 -32
- package/src/panels/stylebook-doc.ts +30 -73
- package/src/panels/stylebook-layers-panel.ts +202 -109
- package/src/panels/stylebook-panel.ts +124 -88
- package/src/panels/tab-strip.ts +386 -288
- package/src/platforms/cloud.ts +106 -16
- package/src/platforms/devserver.ts +13 -0
- package/src/publish/deploy-checklist.ts +124 -78
- package/src/publish/publish-commands.ts +4 -12
- package/src/publish/publish-panel.ts +134 -328
- package/src/services/a11y-report.ts +100 -43
- package/src/services/ai-command-tools.ts +538 -0
- package/src/services/ai-project-tools.ts +8 -2
- package/src/services/ai-session-store.ts +6 -0
- package/src/services/ai-system-prompt.ts +112 -24
- package/src/services/ai-tools.ts +36 -212
- package/src/services/ai-write-report.ts +174 -0
- package/src/services/document-assistant.ts +59 -8
- package/src/services/idle.ts +16 -14
- package/src/services/live-surfaces.ts +237 -0
- package/src/services/model-uri.ts +75 -0
- package/src/services/notify.ts +63 -6
- package/src/services/popover-report.ts +325 -0
- package/src/services/surface-registry.ts +56 -0
- package/src/services/tool-executor.ts +6 -0
- package/src/settings/contexts-section.ts +301 -299
- package/src/settings/contributed-section.ts +361 -222
- package/src/settings/css-vars-editor.ts +320 -325
- package/src/settings/defs-editor.ts +572 -535
- package/src/settings/dependencies-editor.ts +79 -107
- package/src/settings/extension-commands.ts +379 -0
- package/src/settings/extension-rows.ts +113 -0
- package/src/settings/extensions-section.ts +287 -0
- package/src/settings/general-settings.ts +130 -181
- package/src/settings/head-editor.ts +10 -305
- package/src/settings/locales-section.ts +146 -196
- package/src/settings/preferences-accounts.ts +1 -1
- package/src/settings/preferences-dialog.ts +350 -330
- package/src/settings/project-sections.ts +108 -181
- package/src/settings/schema-field-ui.ts +11 -399
- package/src/settings/section-registry.ts +8 -1
- package/src/settings/settings-document.ts +31 -29
- package/src/shell/tree.ts +42 -87
- package/src/shell.ts +48 -36
- package/src/studio.ts +104 -69
- package/src/style/token-ref.ts +0 -39
- package/src/surfaces/about.json +263 -0
- package/src/surfaces/about.ts +163 -0
- package/src/surfaces/add-repo.json +486 -0
- package/src/surfaces/add-repo.ts +236 -0
- package/src/surfaces/ai-chat.json +2185 -0
- package/src/surfaces/ai-chat.ts +549 -0
- package/src/surfaces/ai-credentials-form.json +332 -0
- package/src/surfaces/ai-credentials-form.ts +151 -0
- package/src/surfaces/ai-managed-connect.json +108 -0
- package/src/surfaces/ai-managed-connect.ts +123 -0
- package/src/surfaces/ai-model-picker.json +114 -0
- package/src/surfaces/ai-model-picker.ts +139 -0
- package/src/surfaces/block-action-bar.json +640 -0
- package/src/surfaces/block-action-bar.ts +368 -0
- package/src/surfaces/bottom-dock.json +154 -0
- package/src/surfaces/bottom-dock.ts +125 -0
- package/src/surfaces/canvas-stage.json +444 -0
- package/src/surfaces/canvas-stage.ts +253 -0
- package/src/surfaces/cf-account-picker.json +300 -0
- package/src/surfaces/cf-account-picker.ts +214 -0
- package/src/surfaces/commandbar.json +985 -0
- package/src/{panels/toolbar.ts → surfaces/commandbar.ts} +417 -391
- package/src/surfaces/convert-repeater.json +309 -0
- package/src/surfaces/convert-repeater.ts +276 -0
- package/src/surfaces/data-actions.json +157 -0
- package/src/surfaces/data-actions.ts +115 -0
- package/src/surfaces/dialog.json +289 -0
- package/src/surfaces/dialog.ts +343 -0
- package/src/surfaces/diff-toolbar.json +266 -0
- package/src/surfaces/diff-toolbar.ts +139 -0
- package/src/surfaces/doc-header.json +739 -0
- package/src/surfaces/doc-header.ts +223 -0
- package/src/surfaces/empty-state.json +152 -0
- package/src/surfaces/empty-state.ts +173 -0
- package/src/surfaces/entry-editor.json +281 -0
- package/src/surfaces/entry-editor.ts +191 -0
- package/src/surfaces/expression-editor.json +977 -0
- package/src/surfaces/expression-editor.ts +266 -0
- package/src/surfaces/files-panel.json +592 -0
- package/src/surfaces/files-panel.ts +301 -0
- package/src/surfaces/formula-palette.json +364 -0
- package/src/surfaces/formula-palette.ts +340 -0
- package/src/surfaces/git-panel.json +1822 -0
- package/src/surfaces/git-panel.ts +272 -0
- package/src/surfaces/github-auth.json +112 -0
- package/src/surfaces/github-auth.ts +139 -0
- package/src/surfaces/github-publish.json +206 -0
- package/src/surfaces/github-publish.ts +169 -0
- package/src/surfaces/grid-cell.json +218 -0
- package/src/surfaces/grid-cell.ts +120 -0
- package/src/surfaces/grid-open.json +147 -0
- package/src/surfaces/grid-open.ts +87 -0
- package/src/surfaces/grid-panel.json +706 -0
- package/src/surfaces/grid-panel.ts +206 -0
- package/src/surfaces/grid-replace.json +179 -0
- package/src/surfaces/grid-replace.ts +89 -0
- package/src/surfaces/grid-views.json +446 -0
- package/src/surfaces/grid-views.ts +172 -0
- package/src/surfaces/inspector-dock.json +187 -0
- package/src/surfaces/inspector-dock.ts +161 -0
- package/src/surfaces/jump-bar.json +243 -0
- package/src/surfaces/jump-bar.ts +129 -0
- package/src/surfaces/jxsuite-update.json +146 -0
- package/src/surfaces/jxsuite-update.ts +149 -0
- package/src/surfaces/library-pane.json +1509 -0
- package/src/surfaces/library-pane.ts +363 -0
- package/src/surfaces/logic-panel.json +1949 -0
- package/src/surfaces/logic-panel.ts +369 -0
- package/src/surfaces/logic-workspace.json +685 -0
- package/src/surfaces/logic-workspace.ts +238 -0
- package/src/surfaces/media-browser.json +303 -0
- package/src/surfaces/media-browser.ts +145 -0
- package/src/surfaces/media-field.json +163 -0
- package/src/surfaces/media-field.ts +116 -0
- package/src/surfaces/media-pane.json +513 -0
- package/src/surfaces/media-pane.ts +197 -0
- package/src/surfaces/menu.json +232 -0
- package/src/surfaces/menu.ts +375 -0
- package/src/surfaces/navigator-dock.json +113 -0
- package/src/surfaces/navigator-dock.ts +130 -0
- package/src/surfaces/new-project.json +1654 -0
- package/src/surfaces/new-project.ts +411 -0
- package/src/surfaces/palette.json +535 -0
- package/src/surfaces/pane-context.json +1497 -0
- package/src/surfaces/pane-context.ts +513 -0
- package/src/surfaces/pane-grid.json +188 -0
- package/src/surfaces/pane-grid.ts +250 -0
- package/src/surfaces/panel-activity.json +563 -0
- package/src/surfaces/panel-activity.ts +187 -0
- package/src/surfaces/panel-data.json +141 -0
- package/src/surfaces/panel-data.ts +156 -0
- package/src/surfaces/panel-deploy-checklist.json +283 -0
- package/src/surfaces/panel-deploy-checklist.ts +175 -0
- package/src/surfaces/panel-elements.json +367 -0
- package/src/surfaces/panel-elements.ts +175 -0
- package/src/surfaces/panel-i18n.json +569 -0
- package/src/surfaces/panel-i18n.ts +212 -0
- package/src/surfaces/panel-imports.json +780 -0
- package/src/surfaces/panel-imports.ts +209 -0
- package/src/surfaces/panel-outline.json +734 -0
- package/src/surfaces/panel-outline.ts +326 -0
- package/src/surfaces/panel-page.json +1017 -0
- package/src/surfaces/panel-page.ts +240 -0
- package/src/surfaces/panel-problems.json +497 -0
- package/src/surfaces/panel-problems.ts +157 -0
- package/src/surfaces/panel-signals.json +1973 -0
- package/src/surfaces/panel-signals.ts +436 -0
- package/src/surfaces/panel-stylebook-layers.json +317 -0
- package/src/surfaces/panel-stylebook-layers.ts +155 -0
- package/src/surfaces/preferences.json +850 -0
- package/src/surfaces/preferences.ts +332 -0
- package/src/surfaces/progress-modal.json +110 -0
- package/src/surfaces/progress-modal.ts +150 -0
- package/src/surfaces/properties-panel.json +1578 -0
- package/src/surfaces/properties-panel.ts +337 -0
- package/src/surfaces/publish.json +945 -0
- package/src/surfaces/publish.ts +392 -0
- package/src/surfaces/push-plan.json +110 -0
- package/src/surfaces/push-plan.ts +87 -0
- package/src/surfaces/rail.json +255 -0
- package/src/surfaces/rail.ts +206 -0
- package/src/surfaces/reference-field.json +191 -0
- package/src/surfaces/reference-field.ts +168 -0
- package/src/surfaces/schema-builder.json +1011 -0
- package/src/surfaces/schema-builder.ts +181 -0
- package/src/surfaces/schema-form.json +1013 -0
- package/src/surfaces/schema-form.ts +287 -0
- package/src/surfaces/secret-field.json +60 -0
- package/src/surfaces/secret-field.ts +146 -0
- package/src/surfaces/seo.json +932 -0
- package/src/surfaces/seo.ts +334 -0
- package/src/surfaces/settings-contexts.json +538 -0
- package/src/surfaces/settings-contexts.ts +196 -0
- package/src/surfaces/settings-contributed.json +489 -0
- package/src/surfaces/settings-contributed.ts +199 -0
- package/src/surfaces/settings-css-vars.json +820 -0
- package/src/surfaces/settings-css-vars.ts +199 -0
- package/src/surfaces/settings-defs.json +1363 -0
- package/src/surfaces/settings-defs.ts +232 -0
- package/src/surfaces/settings-deploy.json +108 -0
- package/src/surfaces/settings-deploy.ts +135 -0
- package/src/surfaces/settings-extensions.json +486 -0
- package/src/surfaces/settings-extensions.ts +201 -0
- package/src/surfaces/settings-head.json +779 -0
- package/src/surfaces/settings-head.ts +397 -0
- package/src/surfaces/settings-locales.json +420 -0
- package/src/surfaces/settings-locales.ts +137 -0
- package/src/surfaces/settings-overview.json +423 -0
- package/src/surfaces/settings-overview.ts +218 -0
- package/src/surfaces/settings-packages.json +549 -0
- package/src/surfaces/settings-packages.ts +158 -0
- package/src/surfaces/settings-pane.json +147 -0
- package/src/surfaces/settings-pane.ts +150 -0
- package/src/surfaces/settings-rawjson.json +104 -0
- package/src/surfaces/settings-rawjson.ts +107 -0
- package/src/surfaces/shell.json +99 -0
- package/src/surfaces/shell.ts +30 -0
- package/src/surfaces/slash-menu.json +180 -0
- package/src/surfaces/slash-menu.ts +272 -0
- package/src/surfaces/statements.json +607 -0
- package/src/surfaces/statements.ts +238 -0
- package/src/surfaces/statusbar.json +94 -0
- package/src/{panels → surfaces}/statusbar.ts +164 -90
- package/src/surfaces/style-panel.json +1643 -0
- package/src/surfaces/style-panel.ts +338 -0
- package/src/surfaces/stylebook-chrome.json +122 -0
- package/src/surfaces/stylebook-chrome.ts +122 -0
- package/src/surfaces/tab-strip.json +448 -0
- package/src/surfaces/tab-strip.ts +200 -0
- package/src/surfaces/target-line.json +419 -0
- package/src/surfaces/target-line.ts +349 -0
- package/src/surfaces/toasts.json +189 -0
- package/src/surfaces/welcome.json +571 -0
- package/src/surfaces/welcome.ts +338 -0
- package/src/tabs/project-config.ts +111 -21
- package/src/tabs/tab.ts +52 -1
- package/src/types.ts +22 -0
- package/src/ui/ai-credentials-form.ts +104 -100
- package/src/ui/ai-managed-connect.ts +54 -36
- package/src/ui/ai-model-picker.ts +75 -47
- package/src/ui/cf-account-picker.ts +76 -141
- package/src/ui/color-selector.ts +80 -301
- package/src/ui/dynamic-slot.ts +43 -283
- package/src/ui/expression-editor.ts +797 -576
- package/src/ui/field-input.ts +8 -181
- package/src/ui/form-controls.ts +838 -366
- package/src/ui/formula-chips.ts +80 -85
- package/src/ui/kit.ts +33 -0
- package/src/ui/layers.ts +296 -593
- package/src/ui/media-picker.ts +203 -284
- package/src/ui/panel-resize.ts +251 -64
- package/src/ui/popover-surface.ts +153 -0
- package/src/ui/progress-modal.ts +22 -60
- package/src/ui/regions.ts +13 -7
- package/src/ui/schema-form.ts +906 -440
- package/src/ui/surface.ts +122 -0
- package/src/ui/unit-selector.ts +15 -131
- package/src/ui/value-source.ts +14 -0
- package/src/ui/virtual-window.ts +88 -9
- package/src/utils/canvas-media.ts +72 -0
- package/src/utils/edit-display.ts +7 -2
- package/src/utils/element-selectors.ts +69 -0
- package/src/utils/link-target.ts +2 -2
- package/src/utils/studio-utils.ts +0 -10
- package/src/view.ts +15 -0
- package/src/workspace/pane-derive.ts +36 -14
- package/src/workspace/workspace.ts +75 -6
- package/styles/canvas.css +72 -467
- package/styles/forced-colors.css +48 -82
- package/styles/forced-colors.json +41 -0
- package/styles/inspector.css +2 -1301
- package/styles/overlays.css +96 -2823
- package/styles/panels.css +6 -1423
- package/styles/shell-frame.css +272 -0
- package/styles/shell-frame.json +227 -0
- package/styles/shell.css +0 -1568
- package/styles/tokens.css +65 -136
- package/styles/tokens.json +84 -0
- package/dist/chunks/Overlay.dev-etqjwyf9.js +0 -23
- package/dist/chunks/adapter-jkcyhgtr.js +0 -14
- package/dist/chunks/cf-account-picker-en86v7x7.js +0 -163
- package/dist/chunks/cf-settings-45fgyx8y.js +0 -21
- package/dist/chunks/components-zmx10t62.js +0 -21
- package/dist/chunks/content-source-48xg86wj.js +0 -49
- package/dist/chunks/convert-to-repeater-43tfn7cs.js +0 -268
- package/dist/chunks/editor-cptq5jp1.js +0 -37
- package/dist/chunks/entry-commands-e63f1xc0.js +0 -46
- package/dist/chunks/extension-sections-txv08xxg.js +0 -311
- package/dist/chunks/file-ops-wbh4hhsx.js +0 -48
- package/dist/chunks/files-3kfkwhk4.js +0 -93
- package/dist/chunks/flow-qbfgrpeh.js +0 -634
- package/dist/chunks/focus-trap.esm-9t2drp0g.js +0 -1025
- package/dist/chunks/focus-visible-6eg70z18.js +0 -167
- package/dist/chunks/format-host-w16q81tf.js +0 -43
- package/dist/chunks/fs-events-hrrg328g.js +0 -21
- package/dist/chunks/git-panel-532whr1y.js +0 -59
- package/dist/chunks/index-zqxmqxmh.js +0 -109
- package/dist/chunks/jx-validate-8er6gbxy.js +0 -19
- package/dist/chunks/layers-panel-07hm7g5j.js +0 -67
- package/dist/chunks/layers-q75tj2mf.js +0 -45
- package/dist/chunks/lit-html-j055mkh8.js +0 -22
- package/dist/chunks/monaco-buffer-dqw0xxpx.js +0 -25
- package/dist/chunks/notify-yjm79n4s.js +0 -30
- package/dist/chunks/parse-24d0gay3.js +0 -19
- package/dist/chunks/platform-ba1j1gbh.js +0 -16
- package/dist/chunks/publish-panel-v9st96ja.js +0 -29
- package/dist/chunks/quick-search-zccjp6gv.js +0 -66
- package/dist/chunks/references-3qx8fkyp.js +0 -29
- package/dist/chunks/right-panel-vm3b6kwy.js +0 -46
- package/dist/chunks/section-registry-sx1ykvkw.js +0 -30
- package/dist/chunks/sp-overlay.dev-qgvhnxf4.js +0 -18
- package/dist/chunks/sp-popover.dev-n2etmqdp.js +0 -9
- package/dist/chunks/sp-progress-circle.dev-hryhj0es.js +0 -15
- package/dist/chunks/sp-tooltip.dev-4sastdnw.js +0 -15
- package/dist/chunks/sp-tray.dev-tkbscm06.js +0 -199
- package/dist/chunks/studio-0a5qrh01.js +0 -3
- package/dist/chunks/studio-0bq6eh7v.js +0 -12
- package/dist/chunks/studio-18j3bmcz.js +0 -14
- package/dist/chunks/studio-1ngbejft.js +0 -181
- package/dist/chunks/studio-4b5z1x43.js +0 -424
- package/dist/chunks/studio-4n2b5gzb.js +0 -357
- package/dist/chunks/studio-4rzyrtjz.js +0 -426
- package/dist/chunks/studio-5fx3g9jg.js +0 -4325
- package/dist/chunks/studio-5myknm0a.js +0 -1903
- package/dist/chunks/studio-7afd21nw.js +0 -67
- package/dist/chunks/studio-7nte2hj4.js +0 -613
- package/dist/chunks/studio-8jpzqk5s.js +0 -128
- package/dist/chunks/studio-8q517dfr.js +0 -24
- package/dist/chunks/studio-950k0dx6.js +0 -6939
- package/dist/chunks/studio-9n4mrmnb.js +0 -184
- package/dist/chunks/studio-a0tswy0w.js +0 -58
- package/dist/chunks/studio-ahnzd9rg.js +0 -23
- package/dist/chunks/studio-bgxt93ga.js +0 -62
- package/dist/chunks/studio-cmb03bg5.js +0 -13890
- package/dist/chunks/studio-d1ks410h.js +0 -175
- package/dist/chunks/studio-ef49pnzx.js +0 -387
- package/dist/chunks/studio-eqq1w638.js +0 -113
- package/dist/chunks/studio-ew0djmhg.js +0 -341
- package/dist/chunks/studio-gfgb8pe6.js +0 -1652
- package/dist/chunks/studio-h4rxscfc.js +0 -10
- package/dist/chunks/studio-jc994dnr.js +0 -1066
- package/dist/chunks/studio-kyh9avvv.js +0 -28
- package/dist/chunks/studio-m3hwn7y1.js +0 -120
- package/dist/chunks/studio-mtfnaqs9.js +0 -659
- package/dist/chunks/studio-n6j9t10q.js +0 -318
- package/dist/chunks/studio-n8b9wqwy.js +0 -291
- package/dist/chunks/studio-qcy05dp8.js +0 -1743
- package/dist/chunks/studio-qq6jym04.js +0 -53
- package/dist/chunks/studio-rdx8jcxy.js +0 -83
- package/dist/chunks/studio-rn9ta2mz.js +0 -111
- package/dist/chunks/studio-t7qb9db8.js +0 -665
- package/dist/chunks/studio-tmc5emrw.js +0 -8
- package/dist/chunks/studio-z6nw2ek2.js +0 -18
- package/dist/chunks/studio-zczps70q.js +0 -378
- package/dist/chunks/studio-zqazsct2.js +0 -1823
- package/dist/chunks/workspace-fjx52ryv.js +0 -89
- package/src/panels/activity-bar.ts +0 -251
- package/src/panels/panel-scheduler.ts +0 -219
- package/src/panels/style-inputs.ts +0 -237
- package/src/panels/target-line.ts +0 -274
- package/src/panels/welcome-screen.ts +0 -346
- package/src/ui/button-group.ts +0 -102
- package/src/ui/choice-field.ts +0 -72
- package/src/ui/field-row.ts +0 -249
- package/src/ui/formula-palette.ts +0 -213
- package/src/ui/icons.ts +0 -52
- package/src/ui/jx-theme.ts +0 -130
- package/src/ui/spectrum.ts +0 -340
- package/src/ui/value-selector.ts +0 -174
- package/src/ui/widgets.ts +0 -172
- /package/dist/chunks/{javascript-s56a8dpk.css → javascript-4abzjcy9.css} +0 -0
- /package/dist/chunks/{jsonMode-zr9sw0c7.css → jsonMode-vvmpmk34.css} +0 -0
- /package/dist/chunks/{monaco-setup-pav68602.css → monaco-setup-8vbyqnhr.css} +0 -0
- /package/dist/chunks/{tsMode-ef187txx.css → tsMode-hnxxb73h.css} +0 -0
package/src/ui/form-controls.ts
CHANGED
|
@@ -1,32 +1,82 @@
|
|
|
1
1
|
/// <reference lib="dom" />
|
|
2
2
|
/**
|
|
3
|
-
* Built-in schema-form controls (specs/extensions.md §9.1): "schema-builder" (visual
|
|
4
|
-
* field editor
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
3
|
+
* Built-in schema-form controls (specs/extensions.md §9.1): "schema-builder" (the visual
|
|
4
|
+
* JSON-Schema field editor), "secret" (value committed via the host's secret store, never
|
|
5
|
+
* project.json) and "reference" (an entry of another content collection).
|
|
6
|
+
*
|
|
7
|
+
* Three, not the four the spec still lists: `"binding"` was the signal/route-param picker, and P5
|
|
8
|
+
* replaced it with the value-source ladder every scalar field now carries (`ui/dynamic-slot.ts`,
|
|
9
|
+
* `ui/value-source.ts`). Nothing has registered it since, so a descriptor naming it falls through
|
|
10
|
+
* to the control its type would have had anyway.
|
|
11
|
+
*
|
|
12
|
+
* **All three are Jx documents, and the registry has one shape again.** Each is registered as a
|
|
13
|
+
* MOUNT (`ui/schema-form.ts`'s {@link SchemaFormMountedControl}): the form's own document announces
|
|
14
|
+
* an empty `[part="control-host"]` per field, a document clears the host it is given, and the flow
|
|
15
|
+
* for each control lives here while its markup lives beside it in `surfaces/schema-builder.json`,
|
|
16
|
+
* `surfaces/secret-field.json` and `surfaces/reference-field.json`. The template half of the
|
|
17
|
+
* registry went with the last of them — this module was its only producer, and `ui/schema-form.ts`
|
|
18
|
+
* imported lit for nothing else.
|
|
8
19
|
*
|
|
9
20
|
* Imported once for side effects from studio startup.
|
|
10
21
|
*/
|
|
11
22
|
|
|
12
|
-
import { html, nothing } from "lit-html";
|
|
13
|
-
import { repeat } from "lit-html/directives/repeat.js";
|
|
14
|
-
import { until } from "lit-html/directives/until.js";
|
|
15
23
|
import { errorMessage } from "@jxsuite/schema/parse";
|
|
16
24
|
import { referenceTarget, registerFormControl } from "./schema-form";
|
|
17
25
|
import {
|
|
18
|
-
|
|
26
|
+
FIELD_TYPES,
|
|
27
|
+
FORMAT_OPTIONS,
|
|
19
28
|
detectFieldFormat,
|
|
20
|
-
|
|
29
|
+
detectFieldType,
|
|
21
30
|
schemaForType,
|
|
22
31
|
} from "../settings/schema-field-ui";
|
|
23
|
-
import {
|
|
24
|
-
|
|
25
|
-
import
|
|
26
|
-
import
|
|
32
|
+
import { mountSchemaBuilderSurface } from "../surfaces/schema-builder";
|
|
33
|
+
import { mountReferenceFieldSurface } from "../surfaces/reference-field";
|
|
34
|
+
import { mountSecretFieldSurface } from "../surfaces/secret-field";
|
|
35
|
+
import { camelToLabel, toCamelCase } from "../utils/studio-utils";
|
|
36
|
+
|
|
37
|
+
import type { SchemaProperty } from "../settings/schema-field-ui";
|
|
38
|
+
import type {
|
|
39
|
+
NestedSchemaFieldView,
|
|
40
|
+
SchemaBuilderView,
|
|
41
|
+
SchemaFieldChoice,
|
|
42
|
+
SchemaFieldView,
|
|
43
|
+
} from "../surfaces/schema-builder";
|
|
44
|
+
import type { ReferenceChoice, ReferenceFieldView } from "../surfaces/reference-field";
|
|
45
|
+
import type {
|
|
46
|
+
SchemaFormContext,
|
|
47
|
+
SchemaFormControlArgs,
|
|
48
|
+
SchemaFormControlHandle,
|
|
49
|
+
SchemaFormMountedControl,
|
|
50
|
+
} from "./schema-form";
|
|
27
51
|
|
|
28
52
|
// ─── Schema-builder control ──────────────────────────────────────────────────
|
|
29
53
|
|
|
54
|
+
/**
|
|
55
|
+
* The visual JSON-Schema field editor: `surfaces/schema-builder.json` draws it, and everything
|
|
56
|
+
* below decides it.
|
|
57
|
+
*
|
|
58
|
+
* It is a MOUNTED control (`ui/schema-form.ts`'s {@link SchemaFormMountedControl}) rather than a
|
|
59
|
+
* template one, which is the whole of this conversion: the form's own document announces an empty
|
|
60
|
+
* `[part="control-host"]` per field, a Jx document clears the host it is given, and the two facts
|
|
61
|
+
* together are what let the last lit consumer of the registry become a document. Three things the
|
|
62
|
+
* move settled, and each was a defect rather than a translation:
|
|
63
|
+
*
|
|
64
|
+
* - **The add rows no longer read themselves.** The nested add row found its own name field and its
|
|
65
|
+
* own picker with `closest()` and `querySelector` at click time — a node found by selector is
|
|
66
|
+
* real only until the next render — so what the reader had typed lived in the DOM and nowhere
|
|
67
|
+
* else, and an outside repaint took it away. Each object field carries its draft in
|
|
68
|
+
* {@link BuilderUi.drafts} now.
|
|
69
|
+
* - **A refused rename snaps back because the value moves, not because a handler writes to an
|
|
70
|
+
* input.** The lit card assigned `target.value = fieldName` from inside its change handler. A
|
|
71
|
+
* document's binding only writes when the scope value CHANGES, and after a refusal the scope
|
|
72
|
+
* still holds what the schema says — so every rename says what the control now holds first
|
|
73
|
+
* ({@link say}), and the snap-back is then a real move.
|
|
74
|
+
* - **A vanished field is a no-op, not an empty commit.** Every handler used to clone the value, run
|
|
75
|
+
* a mutation that returned early, and commit the clone anyway — so an edit to a card whose field
|
|
76
|
+
* had gone underneath it wrote the schema back minus nothing, and the host recorded an edit that
|
|
77
|
+
* was not one. A refusal now commits nothing at all.
|
|
78
|
+
*/
|
|
79
|
+
|
|
30
80
|
/** The JSON-Schema object shape the schema-builder edits. */
|
|
31
81
|
interface BuilderSchema {
|
|
32
82
|
type?: string;
|
|
@@ -34,293 +84,622 @@ interface BuilderSchema {
|
|
|
34
84
|
required?: string[];
|
|
35
85
|
}
|
|
36
86
|
|
|
37
|
-
|
|
38
|
-
|
|
87
|
+
/** A draft with its three lists filled in, which is what a mutation is handed. */
|
|
88
|
+
interface BuilderDraft extends BuilderSchema {
|
|
89
|
+
properties: Record<string, SchemaProperty>;
|
|
90
|
+
required: string[];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** One object field's unsubmitted nested field. */
|
|
94
|
+
interface NestedDraft {
|
|
39
95
|
name: string;
|
|
40
|
-
required: boolean;
|
|
41
96
|
type: string;
|
|
42
97
|
}
|
|
43
98
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
99
|
+
/**
|
|
100
|
+
* What the reader is part-way through, per mounted control.
|
|
101
|
+
*
|
|
102
|
+
* Per mount rather than per module, which is what the host element buys: two panes may each be
|
|
103
|
+
* showing a content type's schema, and the module-level `Map` keyed on `${fieldKeyPrefix}.${key}`
|
|
104
|
+
* this replaced gave the second one the first one's open add form.
|
|
105
|
+
*/
|
|
106
|
+
interface BuilderUi {
|
|
107
|
+
addOpen: boolean;
|
|
108
|
+
add: { format: string; name: string; required: boolean; type: string };
|
|
109
|
+
/** Each object field's nested add row, by the field's name in the schema. */
|
|
110
|
+
drafts: Map<string, NestedDraft>;
|
|
111
|
+
/**
|
|
112
|
+
* The ONE control whose value has parted from the schema, and what it holds.
|
|
113
|
+
*
|
|
114
|
+
* One at a time is the whole truth of it: a `change` commits one control, and the decision about
|
|
115
|
+
* it is synchronous, so there is never a second control in flight. See {@link say}.
|
|
116
|
+
*/
|
|
117
|
+
echo: { id: string; value: string | boolean } | null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** The field types, as picker rows. One array, made once: an equal write re-runs no binding. */
|
|
121
|
+
const TYPE_ROWS: SchemaFieldChoice[] = FIELD_TYPES.map((type) => ({ label: type, value: type }));
|
|
122
|
+
|
|
123
|
+
/** The formats, with the empty one named: a blank row says nothing about what choosing it does. */
|
|
124
|
+
const FORMAT_ROWS: SchemaFieldChoice[] = FORMAT_OPTIONS.map((format) => ({
|
|
125
|
+
label: format || "(none)",
|
|
126
|
+
value: format,
|
|
127
|
+
}));
|
|
128
|
+
|
|
129
|
+
/** What a new field starts as. */
|
|
130
|
+
const BLANK_FIELD = { format: "", name: "", required: false, type: "string" };
|
|
50
131
|
|
|
51
|
-
/**
|
|
52
|
-
|
|
53
|
-
|
|
132
|
+
/** The last target list, so an unchanged one is the same array and re-runs no binding. */
|
|
133
|
+
let targetRows: SchemaFieldChoice[] = [];
|
|
134
|
+
let targetNames = " ";
|
|
54
135
|
|
|
55
|
-
/**
|
|
136
|
+
/**
|
|
137
|
+
* Forget the memoised reference-target rows (test hook).
|
|
138
|
+
*
|
|
139
|
+
* The builder's own ephemeral state — which add form is open, what each add row holds — lives with
|
|
140
|
+
* the mount now and goes when the control does, so this is what is left of a module-level reset.
|
|
141
|
+
*/
|
|
56
142
|
export function resetFormControlUiState(): void {
|
|
57
|
-
|
|
58
|
-
|
|
143
|
+
targetRows = [];
|
|
144
|
+
targetNames = " ";
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* The content types a `reference` field can point at, through the host's context.
|
|
149
|
+
*
|
|
150
|
+
* The same list `settings/defs-editor.ts` reads straight off the live config: this one goes through
|
|
151
|
+
* `#/$context/content` because a form control is given a context and never the project.
|
|
152
|
+
*/
|
|
153
|
+
function targetsFor(ctx: SchemaFormContext): SchemaFieldChoice[] {
|
|
154
|
+
const content = ctx.resolvePointer("#/$context/content");
|
|
155
|
+
const names =
|
|
156
|
+
content && typeof content === "object" && !Array.isArray(content) ? Object.keys(content) : [];
|
|
157
|
+
const id = names.join(" ");
|
|
158
|
+
if (id !== targetNames) {
|
|
159
|
+
targetNames = id;
|
|
160
|
+
targetRows = names.map((name) => ({ label: name, value: name }));
|
|
161
|
+
}
|
|
162
|
+
return targetRows;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Whether a type carries a format at all — only `string` and `array` do. */
|
|
166
|
+
function takesFormat(type: string): boolean {
|
|
167
|
+
return type === "string" || type === "array";
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** The value as a schema object: anything else is an object with no fields in it yet. */
|
|
171
|
+
function readSchema(value: unknown): BuilderSchema {
|
|
172
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
173
|
+
? (value as BuilderSchema)
|
|
174
|
+
: { properties: {}, required: [], type: "object" };
|
|
59
175
|
}
|
|
60
176
|
|
|
61
177
|
/** Working clone of the schema value — JSON round-trip, as values may be reactive proxies. */
|
|
62
|
-
function cloneSchema(value: unknown):
|
|
63
|
-
const base =
|
|
64
|
-
value && typeof value === "object" && !Array.isArray(value)
|
|
65
|
-
? value
|
|
66
|
-
: { properties: {}, required: [], type: "object" };
|
|
178
|
+
function cloneSchema(value: unknown): BuilderDraft {
|
|
67
179
|
// oxlint-disable-next-line unicorn/prefer-structured-clone
|
|
68
|
-
|
|
180
|
+
const draft = JSON.parse(JSON.stringify(readSchema(value))) as BuilderSchema;
|
|
181
|
+
draft.type ??= "object";
|
|
182
|
+
draft.properties ??= {};
|
|
183
|
+
draft.required ??= [];
|
|
184
|
+
return draft as BuilderDraft;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** The id of a field's name control. */
|
|
188
|
+
function nameId(key: string): string {
|
|
189
|
+
return `name:${key}`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** The id of a field's required switch. */
|
|
193
|
+
function requiredId(key: string): string {
|
|
194
|
+
return `required:${key}`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** A child is addressed by both halves, because two objects may hold the same child name. */
|
|
198
|
+
function nestedId(parent: string, key: string): string {
|
|
199
|
+
return `${parent} ${key}`;
|
|
69
200
|
}
|
|
70
201
|
|
|
71
|
-
/**
|
|
72
|
-
function
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
202
|
+
/** What a control shows: what it was last told it holds, or what the schema says. */
|
|
203
|
+
function shown<T extends string | boolean>(ui: BuilderUi, id: string, held: T): T {
|
|
204
|
+
return ui.echo?.id === id ? (ui.echo.value as T) : held;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** One child of an object field, as the document draws it. */
|
|
208
|
+
function nestedView(
|
|
209
|
+
ui: BuilderUi,
|
|
210
|
+
parent: string,
|
|
211
|
+
key: string,
|
|
212
|
+
schema: SchemaProperty,
|
|
213
|
+
required: boolean,
|
|
214
|
+
): NestedSchemaFieldView {
|
|
215
|
+
const id = nestedId(parent, key);
|
|
216
|
+
const type = detectFieldType(schema);
|
|
217
|
+
return {
|
|
218
|
+
format: detectFieldFormat(schema),
|
|
219
|
+
hasFormat: takesFormat(type),
|
|
220
|
+
key,
|
|
221
|
+
name: shown(ui, nameId(id), key),
|
|
222
|
+
parent,
|
|
223
|
+
required: shown(ui, requiredId(id), required),
|
|
224
|
+
type,
|
|
89
225
|
};
|
|
226
|
+
}
|
|
90
227
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
228
|
+
/** One field of the object, as the document draws it. */
|
|
229
|
+
function fieldView(
|
|
230
|
+
ui: BuilderUi,
|
|
231
|
+
key: string,
|
|
232
|
+
schema: SchemaProperty,
|
|
233
|
+
required: boolean,
|
|
234
|
+
hasTargets: boolean,
|
|
235
|
+
): SchemaFieldView {
|
|
236
|
+
const type = detectFieldType(schema);
|
|
237
|
+
const nested = type === "object";
|
|
238
|
+
const draft = ui.drafts.get(key);
|
|
239
|
+
const childRequired = schema.required ?? [];
|
|
240
|
+
return {
|
|
241
|
+
children: nested
|
|
242
|
+
? Object.entries(schema.properties ?? {}).map(([child, sub]) =>
|
|
243
|
+
nestedView(ui, key, child, sub, childRequired.includes(child)),
|
|
244
|
+
)
|
|
245
|
+
: [],
|
|
246
|
+
draftName: draft?.name ?? "",
|
|
247
|
+
draftType: draft?.type ?? "string",
|
|
248
|
+
/* A reference with nothing to point at draws no picker: a control whose list is empty offers
|
|
249
|
+
the reader no answer, which is why the lit card gated on the same thing. */
|
|
250
|
+
extra: type === "reference" && hasTargets ? "reference" : nested ? "nested" : "none",
|
|
251
|
+
format: detectFieldFormat(schema),
|
|
252
|
+
hasFormat: takesFormat(type),
|
|
253
|
+
key,
|
|
254
|
+
label: camelToLabel(key),
|
|
255
|
+
name: shown(ui, nameId(key), key),
|
|
256
|
+
refTarget: schema.$ref ? schema.$ref.replace(/^#\/[^/]+\//, "") : "",
|
|
257
|
+
required: shown(ui, requiredId(key), required),
|
|
258
|
+
type,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** A property map with one key renamed, rebuilt in place so a rename never reorders the list. */
|
|
263
|
+
function renameKey<T>(properties: Record<string, T>, from: string, to: string): void {
|
|
264
|
+
const next: Record<string, T> = {};
|
|
265
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
266
|
+
next[key === from ? to : key] = value;
|
|
267
|
+
delete properties[key];
|
|
268
|
+
}
|
|
269
|
+
Object.assign(properties, next);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** Add or remove a name from a `required` list, made on first use. */
|
|
273
|
+
function markRequired(
|
|
274
|
+
holder: { required?: string[] | undefined },
|
|
275
|
+
key: string,
|
|
276
|
+
required: boolean,
|
|
277
|
+
): void {
|
|
278
|
+
holder.required ??= [];
|
|
279
|
+
const at = holder.required.indexOf(key);
|
|
280
|
+
if (required && at === -1) {
|
|
281
|
+
holder.required.push(key);
|
|
282
|
+
}
|
|
283
|
+
if (!required && at !== -1) {
|
|
284
|
+
holder.required.splice(at, 1);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Mount the schema-builder into the host the form drew for it.
|
|
290
|
+
*
|
|
291
|
+
* @param {HTMLElement} host - The field's `[part="control-host"]`
|
|
292
|
+
* @param {SchemaFormControlArgs} args - The value, the context and the commit hook
|
|
293
|
+
* @returns {SchemaFormControlHandle}
|
|
294
|
+
*/
|
|
295
|
+
function mountSchemaBuilder(
|
|
296
|
+
host: HTMLElement,
|
|
297
|
+
args: SchemaFormControlArgs,
|
|
298
|
+
): SchemaFormControlHandle {
|
|
299
|
+
let latest = args;
|
|
300
|
+
/**
|
|
301
|
+
* The schema this control last committed, until the host hands one back.
|
|
302
|
+
*
|
|
303
|
+
* Every host that draws this control redraws on a commit, and the redraw arrives as an `update`
|
|
304
|
+
* carrying the new value — but the frontmatter renderer passes no `rerender` at all, and a host
|
|
305
|
+
* that never came back would leave the cards showing the schema as it was before the edit. The
|
|
306
|
+
* commit is what is shown until the value itself moves.
|
|
307
|
+
*/
|
|
308
|
+
let committed: BuilderSchema | null = null;
|
|
309
|
+
const ui: BuilderUi = {
|
|
310
|
+
add: { ...BLANK_FIELD },
|
|
311
|
+
addOpen: false,
|
|
312
|
+
drafts: new Map(),
|
|
313
|
+
echo: null,
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
/** What the cards are drawn from. */
|
|
317
|
+
const source = (): BuilderSchema => committed ?? readSchema(latest.value);
|
|
318
|
+
|
|
319
|
+
const project = (): SchemaBuilderView => {
|
|
320
|
+
const schema = source();
|
|
321
|
+
const required = schema.required ?? [];
|
|
322
|
+
const rows = targetsFor(latest.ctx);
|
|
323
|
+
return {
|
|
324
|
+
addFormat: ui.add.format,
|
|
325
|
+
addHasFormat: takesFormat(ui.add.type),
|
|
326
|
+
addName: ui.add.name,
|
|
327
|
+
addRequired: ui.add.required,
|
|
328
|
+
addState: ui.addOpen ? "open" : "closed",
|
|
329
|
+
addType: ui.add.type,
|
|
330
|
+
fields: Object.entries(schema.properties ?? {}).map(([key, property]) =>
|
|
331
|
+
fieldView(ui, key, property, required.includes(key), rows.length > 0),
|
|
332
|
+
),
|
|
333
|
+
formatOptions: FORMAT_ROWS,
|
|
334
|
+
targets: rows,
|
|
335
|
+
typeOptions: TYPE_ROWS,
|
|
336
|
+
};
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
const redraw = (): void => surface.update(project());
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Say what a control now holds, before anything is decided about it.
|
|
343
|
+
*
|
|
344
|
+
* This is what `live()` did for the lit card. A document's binding writes only when the SCOPE
|
|
345
|
+
* value changes, and after a refusal the scope still holds what the schema says — so without this
|
|
346
|
+
* nothing is written back and the refused text stays in the field.
|
|
347
|
+
*/
|
|
348
|
+
const say = (id: string, value: string | boolean): void => {
|
|
349
|
+
ui.echo = { id, value };
|
|
350
|
+
redraw();
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
/** A refusal: every control goes back to what the schema says, and nothing is committed. */
|
|
354
|
+
const refuse = (): void => {
|
|
355
|
+
ui.echo = null;
|
|
356
|
+
redraw();
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Edit the schema and commit the result.
|
|
361
|
+
*
|
|
362
|
+
* The draft is a clone, so a mutation that answers `false` costs nothing: it is a REFUSAL, and
|
|
363
|
+
* the host hears nothing at all. A mutation that answers `true` is what the field now holds, and
|
|
364
|
+
* it is shown from this moment rather than when the host gets round to redrawing.
|
|
365
|
+
*/
|
|
366
|
+
const write = (mutate: (draft: BuilderDraft) => boolean): void => {
|
|
367
|
+
const draft = cloneSchema(source());
|
|
368
|
+
if (!mutate(draft)) {
|
|
369
|
+
refuse();
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
committed = draft;
|
|
373
|
+
ui.echo = null;
|
|
374
|
+
latest.onChange(draft);
|
|
375
|
+
redraw();
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
/** The parent object a nested edit is about, or `undefined` when it has gone underneath. */
|
|
379
|
+
const parentOf = (draft: BuilderDraft, parent: string): SchemaProperty | undefined =>
|
|
380
|
+
draft.properties[parent];
|
|
381
|
+
|
|
382
|
+
/** One object field's add-row draft, made on first use. */
|
|
383
|
+
const draftOf = (parent: string): NestedDraft => {
|
|
384
|
+
let draft = ui.drafts.get(parent);
|
|
385
|
+
if (!draft) {
|
|
386
|
+
draft = { name: "", type: "string" };
|
|
387
|
+
ui.drafts.set(parent, draft);
|
|
388
|
+
}
|
|
389
|
+
return draft;
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
const surface = mountSchemaBuilderSurface(host, {
|
|
393
|
+
addNested(parent) {
|
|
394
|
+
const draft = draftOf(parent);
|
|
395
|
+
const name = toCamelCase(draft.name.trim());
|
|
396
|
+
if (!name) {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
write((next) => {
|
|
400
|
+
const holder = parentOf(next, parent);
|
|
401
|
+
if (!holder) {
|
|
402
|
+
return false;
|
|
120
403
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
404
|
+
holder.properties ??= {};
|
|
405
|
+
holder.properties[name] = schemaForType(draft.type || "string");
|
|
406
|
+
/* The name clears and the type does not: adding three strings in a row is the ordinary
|
|
407
|
+
case, and re-choosing the type each time would be the editor asking what it just heard. */
|
|
408
|
+
draft.name = "";
|
|
409
|
+
return true;
|
|
410
|
+
});
|
|
411
|
+
},
|
|
412
|
+
cancelAdd() {
|
|
413
|
+
ui.addOpen = false;
|
|
414
|
+
ui.add = { ...BLANK_FIELD };
|
|
415
|
+
redraw();
|
|
416
|
+
},
|
|
417
|
+
confirmAdd() {
|
|
418
|
+
const name = toCamelCase(ui.add.name.trim());
|
|
419
|
+
if (!name) {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
const { format, required, type } = ui.add;
|
|
423
|
+
ui.addOpen = false;
|
|
424
|
+
ui.add = { ...BLANK_FIELD };
|
|
425
|
+
write((next) => {
|
|
426
|
+
next.properties[name] = schemaForType(type, format || undefined);
|
|
427
|
+
if (required) {
|
|
428
|
+
markRequired(next, name, true);
|
|
129
429
|
}
|
|
130
|
-
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
430
|
+
return true;
|
|
431
|
+
});
|
|
432
|
+
},
|
|
433
|
+
editAddFormat(value) {
|
|
434
|
+
ui.add.format = value;
|
|
435
|
+
redraw();
|
|
436
|
+
},
|
|
437
|
+
editAddName(value) {
|
|
438
|
+
ui.add.name = value;
|
|
439
|
+
redraw();
|
|
440
|
+
},
|
|
441
|
+
editAddRequired(required) {
|
|
442
|
+
ui.add.required = required;
|
|
443
|
+
redraw();
|
|
444
|
+
},
|
|
445
|
+
editAddType(value) {
|
|
446
|
+
ui.add.type = value;
|
|
447
|
+
redraw();
|
|
448
|
+
},
|
|
449
|
+
editDraftName(parent, value) {
|
|
450
|
+
draftOf(parent).name = value;
|
|
451
|
+
redraw();
|
|
452
|
+
},
|
|
453
|
+
editDraftType(parent, value) {
|
|
454
|
+
draftOf(parent).type = value;
|
|
455
|
+
redraw();
|
|
456
|
+
},
|
|
457
|
+
openAdd() {
|
|
458
|
+
ui.addOpen = true;
|
|
459
|
+
ui.add = { ...BLANK_FIELD };
|
|
460
|
+
redraw();
|
|
461
|
+
},
|
|
462
|
+
removeField(key) {
|
|
463
|
+
write((next) => {
|
|
464
|
+
if (!next.properties[key]) {
|
|
465
|
+
return false;
|
|
138
466
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
467
|
+
delete next.properties[key];
|
|
468
|
+
next.required = next.required.filter((name) => name !== key);
|
|
469
|
+
return true;
|
|
470
|
+
});
|
|
471
|
+
},
|
|
472
|
+
removeNested(parent, key) {
|
|
473
|
+
write((next) => {
|
|
474
|
+
const holder = parentOf(next, parent);
|
|
475
|
+
if (!holder?.properties?.[key]) {
|
|
476
|
+
return false;
|
|
147
477
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
update((draft) => {
|
|
152
|
-
const prop = draft.properties?.[name];
|
|
153
|
-
if (!prop) {
|
|
154
|
-
return;
|
|
478
|
+
delete holder.properties[key];
|
|
479
|
+
if (holder.required) {
|
|
480
|
+
holder.required = holder.required.filter((name) => name !== key);
|
|
155
481
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
onDeleteNested: (parentName, childName) =>
|
|
166
|
-
update((draft) => {
|
|
167
|
-
const parent = draft.properties?.[parentName];
|
|
168
|
-
if (!parent?.properties) {
|
|
169
|
-
return;
|
|
482
|
+
return true;
|
|
483
|
+
});
|
|
484
|
+
},
|
|
485
|
+
renameField(key, value) {
|
|
486
|
+
say(nameId(key), value);
|
|
487
|
+
const name = toCamelCase(value.trim());
|
|
488
|
+
write((next) => {
|
|
489
|
+
if (!next.properties[key] || !name || name === key || next.properties[name]) {
|
|
490
|
+
return false;
|
|
170
491
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
492
|
+
renameKey(next.properties, key, name);
|
|
493
|
+
next.required = next.required.map((entry) => (entry === key ? name : entry));
|
|
494
|
+
return true;
|
|
495
|
+
});
|
|
496
|
+
},
|
|
497
|
+
renameNested(parent, key, value) {
|
|
498
|
+
say(nameId(nestedId(parent, key)), value);
|
|
499
|
+
const name = toCamelCase(value.trim());
|
|
500
|
+
write((next) => {
|
|
501
|
+
const holder = parentOf(next, parent);
|
|
502
|
+
if (!holder?.properties?.[key] || !name || name === key || holder.properties[name]) {
|
|
503
|
+
return false;
|
|
174
504
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const normalized = toCamelCase(newName);
|
|
179
|
-
if (!draft.properties || !normalized || draft.properties[normalized]) {
|
|
180
|
-
return;
|
|
505
|
+
renameKey(holder.properties, key, name);
|
|
506
|
+
if (holder.required) {
|
|
507
|
+
holder.required = holder.required.map((entry) => (entry === key ? name : entry));
|
|
181
508
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
509
|
+
return true;
|
|
510
|
+
});
|
|
511
|
+
},
|
|
512
|
+
setFormat(key, value) {
|
|
513
|
+
write((next) => {
|
|
514
|
+
const property = next.properties[key];
|
|
515
|
+
if (!property) {
|
|
516
|
+
return false;
|
|
185
517
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
})
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
518
|
+
next.properties[key] = schemaForType(property.type || "string", value || undefined);
|
|
519
|
+
return true;
|
|
520
|
+
});
|
|
521
|
+
},
|
|
522
|
+
setNestedFormat(parent, key, value) {
|
|
523
|
+
write((next) => {
|
|
524
|
+
const holder = parentOf(next, parent);
|
|
525
|
+
const property = holder?.properties?.[key];
|
|
526
|
+
if (!holder?.properties || !property) {
|
|
527
|
+
return false;
|
|
195
528
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
529
|
+
holder.properties[key] = schemaForType(property.type || "string", value || undefined);
|
|
530
|
+
return true;
|
|
531
|
+
});
|
|
532
|
+
},
|
|
533
|
+
setNestedRequired(parent, key, required) {
|
|
534
|
+
say(requiredId(nestedId(parent, key)), required);
|
|
535
|
+
write((next) => {
|
|
536
|
+
const holder = parentOf(next, parent);
|
|
537
|
+
if (!holder) {
|
|
538
|
+
return false;
|
|
199
539
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
540
|
+
markRequired(holder, key, required);
|
|
541
|
+
return true;
|
|
542
|
+
});
|
|
543
|
+
},
|
|
544
|
+
setNestedType(parent, key, value) {
|
|
545
|
+
write((next) => {
|
|
546
|
+
const holder = parentOf(next, parent);
|
|
547
|
+
const property = holder?.properties?.[key];
|
|
548
|
+
if (!holder?.properties || !property) {
|
|
549
|
+
return false;
|
|
203
550
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
551
|
+
/* A format survives a move between the two types that carry one, and is dropped by any type
|
|
552
|
+
that does not: `schemaForType` is handed the old format only when the new type takes it. */
|
|
553
|
+
const format = takesFormat(value) ? detectFieldFormat(property) : undefined;
|
|
554
|
+
holder.properties[key] = schemaForType(value, format || undefined);
|
|
555
|
+
return true;
|
|
556
|
+
});
|
|
557
|
+
},
|
|
558
|
+
setRequired(key, required) {
|
|
559
|
+
say(requiredId(key), required);
|
|
560
|
+
write((next) => {
|
|
561
|
+
if (!next.properties[key]) {
|
|
562
|
+
return false;
|
|
210
563
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
564
|
+
markRequired(next, key, required);
|
|
565
|
+
return true;
|
|
566
|
+
});
|
|
567
|
+
},
|
|
568
|
+
setTarget(key, value) {
|
|
569
|
+
write((next) => {
|
|
570
|
+
if (!next.properties[key]) {
|
|
571
|
+
return false;
|
|
217
572
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
573
|
+
/* The pointer form is `#/content/<type>` — the same one `settings/defs-editor.ts` writes,
|
|
574
|
+
so a reference authored here and one authored in Data Shapes are the same value. */
|
|
575
|
+
next.properties[key] = { $ref: `#/content/${value}` };
|
|
576
|
+
return true;
|
|
577
|
+
});
|
|
578
|
+
},
|
|
579
|
+
setType(key, value) {
|
|
580
|
+
write((next) => {
|
|
581
|
+
const property = next.properties[key];
|
|
582
|
+
if (!property) {
|
|
583
|
+
return false;
|
|
227
584
|
}
|
|
228
|
-
|
|
585
|
+
const format = takesFormat(value) ? detectFieldFormat(property) : undefined;
|
|
586
|
+
next.properties[key] = schemaForType(value, format || undefined);
|
|
587
|
+
return true;
|
|
588
|
+
});
|
|
589
|
+
},
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
redraw();
|
|
593
|
+
|
|
594
|
+
return {
|
|
595
|
+
dispose: () => surface.dispose(),
|
|
596
|
+
update(next) {
|
|
597
|
+
latest = next;
|
|
598
|
+
/* The host has spoken: what it says the value is wins over what this control last committed,
|
|
599
|
+
and any control that had parted from it is put back. */
|
|
600
|
+
committed = null;
|
|
601
|
+
ui.echo = null;
|
|
602
|
+
redraw();
|
|
603
|
+
},
|
|
229
604
|
};
|
|
230
|
-
|
|
231
|
-
const fieldCards = repeat(
|
|
232
|
-
Object.entries(properties),
|
|
233
|
-
([name]) => name,
|
|
234
|
-
([name, def]) => fieldCardTpl(name, def, required.includes(name), handlers, contentTypeNames),
|
|
235
|
-
);
|
|
236
|
-
|
|
237
|
-
const addFieldState = addFieldStates.get(stateKey) ?? blankAddFieldState();
|
|
238
|
-
|
|
239
|
-
return html`
|
|
240
|
-
<div class="schema-builder">
|
|
241
|
-
<div class="schema-field-list">${fieldCards}</div>
|
|
242
|
-
${
|
|
243
|
-
addFieldOpen.has(stateKey)
|
|
244
|
-
? addFieldFormTpl(addFieldState, {
|
|
245
|
-
onCancel: () => {
|
|
246
|
-
addFieldOpen.delete(stateKey);
|
|
247
|
-
addFieldStates.delete(stateKey);
|
|
248
|
-
rerender?.();
|
|
249
|
-
},
|
|
250
|
-
onConfirm: () => {
|
|
251
|
-
const raw = addFieldState.name.trim();
|
|
252
|
-
const name = toCamelCase(raw);
|
|
253
|
-
if (!name) {
|
|
254
|
-
return;
|
|
255
|
-
}
|
|
256
|
-
// Close the form before update() rerenders with the committed value
|
|
257
|
-
addFieldOpen.delete(stateKey);
|
|
258
|
-
addFieldStates.delete(stateKey);
|
|
259
|
-
update((draft) => {
|
|
260
|
-
draft.properties![name] = schemaForType(
|
|
261
|
-
addFieldState.type,
|
|
262
|
-
addFieldState.format || undefined,
|
|
263
|
-
);
|
|
264
|
-
if (addFieldState.required && !draft.required!.includes(name)) {
|
|
265
|
-
draft.required!.push(name);
|
|
266
|
-
}
|
|
267
|
-
});
|
|
268
|
-
},
|
|
269
|
-
onInput: (field, val) => {
|
|
270
|
-
addFieldStates.set(stateKey, { ...addFieldState, [field]: val });
|
|
271
|
-
rerender?.();
|
|
272
|
-
},
|
|
273
|
-
})
|
|
274
|
-
: html`
|
|
275
|
-
<sp-action-button
|
|
276
|
-
size="s"
|
|
277
|
-
quiet
|
|
278
|
-
@click=${() => {
|
|
279
|
-
addFieldOpen.add(stateKey);
|
|
280
|
-
addFieldStates.set(stateKey, blankAddFieldState());
|
|
281
|
-
rerender?.();
|
|
282
|
-
}}
|
|
283
|
-
>
|
|
284
|
-
<sp-icon-add slot="icon"></sp-icon-add> Add Field
|
|
285
|
-
</sp-action-button>
|
|
286
|
-
`
|
|
287
|
-
}
|
|
288
|
-
</div>
|
|
289
|
-
`;
|
|
290
605
|
}
|
|
291
606
|
|
|
607
|
+
/**
|
|
608
|
+
* The schema-builder as the registry holds it — a MOUNT, because it is a Jx document
|
|
609
|
+
* (`src/surfaces/schema-builder.json`) and a document needs a host of its own rather than a
|
|
610
|
+
* template slot. Named rather than written inline at the registration below so a test can drive the
|
|
611
|
+
* control the way the engine does, without the registry growing a second public lookup for it.
|
|
612
|
+
*/
|
|
613
|
+
export const schemaBuilderControl: SchemaFormMountedControl = { mount: mountSchemaBuilder };
|
|
614
|
+
|
|
292
615
|
registerFormControl("schema-builder", schemaBuilderControl);
|
|
293
616
|
|
|
294
617
|
// ─── Secret control ──────────────────────────────────────────────────────────
|
|
295
618
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
619
|
+
/**
|
|
620
|
+
* The env-var name a field currently points at, or `null` when it points at nothing.
|
|
621
|
+
*
|
|
622
|
+
* The field persists a NAME — the value itself went to the platform's secret store — so a non-empty
|
|
623
|
+
* string here is the whole of what a screen may say about the secret.
|
|
624
|
+
*/
|
|
625
|
+
function envNameOf(value: unknown): string | null {
|
|
626
|
+
return typeof value === "string" && value ? value : null;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* Mount the secret field into the host the form drew for it.
|
|
631
|
+
*
|
|
632
|
+
* Secret VALUES commit through the host's secret store (`ctx.commitSecret` → the platform's
|
|
633
|
+
* `setSecrets`), never project.json; the field itself persists only the derived env-var NAME that
|
|
634
|
+
* call answers with. A host with no secrets surface leaves the control disabled, because there is
|
|
635
|
+
* nowhere for what the reader types to go.
|
|
636
|
+
*
|
|
637
|
+
* **The field is emptied the moment the value leaves it, not when the commit lands.** The commit is
|
|
638
|
+
* asynchronous and the clear is not, so ordering them the other way round would leave the
|
|
639
|
+
* credential on screen for the length of a platform round trip — and would leave it there for good
|
|
640
|
+
* if the call refused. This way a failure costs the reader their typing, which is the direction a
|
|
641
|
+
* mistake in a secret field should fail in.
|
|
642
|
+
*
|
|
643
|
+
* @param {HTMLElement} host - The field's `[part="control-host"]`
|
|
644
|
+
* @param {SchemaFormControlArgs} args - The value, the context and the commit hook
|
|
645
|
+
* @returns {SchemaFormControlHandle}
|
|
646
|
+
*/
|
|
647
|
+
function mountSecret(host: HTMLElement, args: SchemaFormControlArgs): SchemaFormControlHandle {
|
|
648
|
+
let latest = args;
|
|
649
|
+
|
|
650
|
+
const redraw = (): void => {
|
|
651
|
+
const envName = envNameOf(latest.value);
|
|
652
|
+
surface.update({
|
|
653
|
+
disabled: !latest.ctx.commitSecret,
|
|
654
|
+
/* The FIELD's key, exactly as the row above shows it: `surfaces/schema-form.json` draws a
|
|
655
|
+
`jx-field` whose visible label is the raw property name, and the kit names a slotted
|
|
656
|
+
control from that label — but this control is mounted into a plain host, which a label
|
|
657
|
+
cannot point at. So the control carries the accessible name itself, and it is the same
|
|
658
|
+
string a sighted reader sees rather than a prettier one (WCAG 2.5.3). */
|
|
659
|
+
label: latest.key,
|
|
660
|
+
placeholder: envName ? `Stored as ${envName}` : "Not set",
|
|
661
|
+
});
|
|
662
|
+
};
|
|
663
|
+
|
|
664
|
+
const surface = mountSecretFieldSurface(host, {
|
|
665
|
+
commit(value) {
|
|
666
|
+
const { commitSecret } = latest.ctx;
|
|
667
|
+
if (!commitSecret || !value) {
|
|
312
668
|
return;
|
|
313
669
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
}
|
|
670
|
+
surface.clear();
|
|
671
|
+
const { key, onChange } = latest;
|
|
672
|
+
const envName = envNameOf(latest.value);
|
|
673
|
+
void Promise.resolve(commitSecret(key, value)).then((name) => {
|
|
674
|
+
/* A name equal to the one the field already holds is not an edit: the same secret was
|
|
675
|
+
rotated in place, the store answered with the same env var, and a patch would rewrite
|
|
676
|
+
project.json with the bytes it already has. */
|
|
677
|
+
if (typeof name === "string" && name && name !== envName) {
|
|
678
|
+
onChange(name);
|
|
679
|
+
}
|
|
680
|
+
});
|
|
681
|
+
},
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
redraw();
|
|
685
|
+
|
|
686
|
+
return {
|
|
687
|
+
dispose: () => surface.dispose(),
|
|
688
|
+
update(next) {
|
|
689
|
+
latest = next;
|
|
690
|
+
redraw();
|
|
691
|
+
},
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* The secret control as the registry holds it. Named rather than written inline at the registration
|
|
697
|
+
* below so a test can drive it the way the engine does, without the registry growing a second
|
|
698
|
+
* public lookup for it.
|
|
699
|
+
*/
|
|
700
|
+
export const secretControl: SchemaFormMountedControl = { mount: mountSecret };
|
|
701
|
+
|
|
702
|
+
registerFormControl("secret", secretControl);
|
|
324
703
|
|
|
325
704
|
// ─── Reference control ───────────────────────────────────────────────────────
|
|
326
705
|
|
|
@@ -336,8 +715,10 @@ registerFormControl("secret", ({ key, value, onChange, ctx, rerender }) => {
|
|
|
336
715
|
*
|
|
337
716
|
* Three states, and none of them lies:
|
|
338
717
|
*
|
|
339
|
-
* - **loading** — a disabled picker saying so, replaced when the read lands
|
|
340
|
-
*
|
|
718
|
+
* - **loading** — a disabled picker saying so, replaced when the read lands. The control redraws
|
|
719
|
+
* ITSELF when that happens now: it used to need the host's `rerender`, and the frontmatter
|
|
720
|
+
* renderer passes none, so lit's `until` was standing in for a second frame the control could not
|
|
721
|
+
* ask for;
|
|
341
722
|
* - **failed** — the current value stays EDITABLE as text, with the reason and a Retry beside it.
|
|
342
723
|
* Swapping a failed read for an empty dropdown would present "no entries" and "could not find
|
|
343
724
|
* out" as the same screen, which is §16.1's complaint in miniature;
|
|
@@ -352,13 +733,26 @@ const entryIdCache = new Map<string, Promise<string[]>>();
|
|
|
352
733
|
/**
|
|
353
734
|
* What the read ENDED as, per collection.
|
|
354
735
|
*
|
|
355
|
-
* Kept beside the promise so a settled collection
|
|
356
|
-
* of the enclosing form
|
|
357
|
-
*
|
|
358
|
-
* keystroke in the field beside it.
|
|
736
|
+
* Kept beside the promise so a settled collection is answered without waiting. Without it every
|
|
737
|
+
* repaint of the enclosing form would go back to a promise, and a picker that has had its answer
|
|
738
|
+
* for ten minutes would flash "Loading…" on every keystroke in the field beside it.
|
|
359
739
|
*/
|
|
360
740
|
const entryIdResult = new Map<string, { ids: string[] } | { error: string }>();
|
|
361
741
|
|
|
742
|
+
/**
|
|
743
|
+
* Who is waiting for a collection's read to land, by collection. Emptied when it does.
|
|
744
|
+
*
|
|
745
|
+
* A SET rather than a flag, and the difference is a field that never finishes loading. It was one
|
|
746
|
+
* boolean per collection — "a settle handler is already attached" — so the first control to ask got
|
|
747
|
+
* the answer and every later one was told nothing at all. That was survivable while the only asker
|
|
748
|
+
* was a lit template (`until` held a promise of its own) or a whole panel repainting on the first
|
|
749
|
+
* control's behalf; with three independently mounted documents on one collection, the second and
|
|
750
|
+
* third would sit on "Loading…" for the life of the pane. Repeated asks from one waiter dedupe by
|
|
751
|
+
* identity, and the set is dropped whole the moment the read settles, so it cannot grow past the
|
|
752
|
+
* length of one listing.
|
|
753
|
+
*/
|
|
754
|
+
const entryIdWaiting = new Map<string, Set<() => void>>();
|
|
755
|
+
|
|
362
756
|
/**
|
|
363
757
|
* Forget cached entry ids — for one collection, or all of them.
|
|
364
758
|
*
|
|
@@ -370,9 +764,11 @@ export function invalidateReferenceEntries(collection?: string): void {
|
|
|
370
764
|
if (collection === undefined) {
|
|
371
765
|
entryIdCache.clear();
|
|
372
766
|
entryIdResult.clear();
|
|
767
|
+
entryIdWaiting.clear();
|
|
373
768
|
} else {
|
|
374
769
|
entryIdCache.delete(collection);
|
|
375
770
|
entryIdResult.delete(collection);
|
|
771
|
+
entryIdWaiting.delete(collection);
|
|
376
772
|
}
|
|
377
773
|
}
|
|
378
774
|
|
|
@@ -392,120 +788,196 @@ function entryIdsFor(collection: string): Promise<string[]> {
|
|
|
392
788
|
return pending;
|
|
393
789
|
}
|
|
394
790
|
|
|
395
|
-
/**
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
791
|
+
/**
|
|
792
|
+
* How listing a collection's entry ids ENDED, for a surface that draws its own picker.
|
|
793
|
+
*
|
|
794
|
+
* `null` means the read is still in flight, and `onSettled` is called when it lands — which is the
|
|
795
|
+
* second frame a document has to be GIVEN, since nothing repaints it on the read's behalf.
|
|
796
|
+
*
|
|
797
|
+
* Exported because two surfaces draw the choices and neither is this control: the Document Header
|
|
798
|
+
* card and the Navigator's Page panel project a `$ref` field as a picker row of their own
|
|
799
|
+
* (`panels/frontmatter-fields.ts`), and what they need is not the widget but the answer behind it.
|
|
800
|
+
* Taking it from here rather than calling `listCollectionEntryIds` directly is what keeps ONE cache
|
|
801
|
+
* and ONE invalidation: a collection listed for the Document Header card is not listed again for
|
|
802
|
+
* the entry editor, and {@link invalidateReferenceEntries} after an entry is created is still the
|
|
803
|
+
* single event that forgets it.
|
|
804
|
+
*
|
|
805
|
+
* @param {string} collection
|
|
806
|
+
* @param {() => void} [onSettled]
|
|
807
|
+
* @returns {{ ids: string[] } | { error: string } | null}
|
|
808
|
+
*/
|
|
809
|
+
export function referenceEntryState(
|
|
407
810
|
collection: string,
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
) {
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
811
|
+
onSettled?: () => void,
|
|
812
|
+
): { ids: string[] } | { error: string } | null {
|
|
813
|
+
const done = entryIdResult.get(collection);
|
|
814
|
+
if (done) {
|
|
815
|
+
return done;
|
|
816
|
+
}
|
|
817
|
+
const pending = entryIdsFor(collection);
|
|
818
|
+
const waiting = entryIdWaiting.get(collection);
|
|
819
|
+
if (waiting) {
|
|
820
|
+
if (onSettled) {
|
|
821
|
+
waiting.add(onSettled);
|
|
822
|
+
}
|
|
823
|
+
return null;
|
|
824
|
+
}
|
|
825
|
+
entryIdWaiting.set(collection, new Set(onSettled ? [onSettled] : []));
|
|
826
|
+
/**
|
|
827
|
+
* Record how the read ended and tell everyone who asked — unless this is not the read anyone is
|
|
828
|
+
* waiting for any more.
|
|
829
|
+
*
|
|
830
|
+
* A Retry invalidates the collection and starts a second read while the first is still in flight,
|
|
831
|
+
* so without the identity check the loser writes its stale answer over the winner's and notifies
|
|
832
|
+
* a set of waiters that belongs to the other read. An invalidated read settles into nothing,
|
|
833
|
+
* which is what "forget this collection" has to mean.
|
|
834
|
+
*/
|
|
835
|
+
const settle = (result: { ids: string[] } | { error: string }): void => {
|
|
836
|
+
if (entryIdCache.get(collection) !== pending) {
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
const listeners = entryIdWaiting.get(collection);
|
|
840
|
+
entryIdWaiting.delete(collection);
|
|
841
|
+
entryIdResult.set(collection, result);
|
|
842
|
+
for (const run of listeners ?? []) {
|
|
843
|
+
run();
|
|
844
|
+
}
|
|
845
|
+
};
|
|
846
|
+
void pending.then(
|
|
847
|
+
(ids) => settle({ ids }),
|
|
848
|
+
(error: unknown) => settle({ error: errorMessage(error) }),
|
|
849
|
+
);
|
|
850
|
+
return null;
|
|
435
851
|
}
|
|
436
852
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
853
|
+
/**
|
|
854
|
+
* The view a reference field is drawn from, given what the flow knows right now.
|
|
855
|
+
*
|
|
856
|
+
* Every branch answers with a whole view rather than patching one, so the three states cannot leak
|
|
857
|
+
* into each other: a picker that failed to list is a TEXT field, and a text field's `options` is
|
|
858
|
+
* empty rather than stale.
|
|
859
|
+
*/
|
|
860
|
+
function referenceView(args: SchemaFormControlArgs, onSettled: () => void): ReferenceFieldView {
|
|
861
|
+
const current = typeof args.value === "string" ? args.value : "";
|
|
862
|
+
const base = {
|
|
863
|
+
canRetry: false,
|
|
864
|
+
disabled: false,
|
|
865
|
+
hasNote: false,
|
|
866
|
+
/* The row's own visible label, verbatim — see the secret control's note on why the control
|
|
867
|
+
carries its accessible name rather than inheriting one from the field around it. */
|
|
868
|
+
label: args.key,
|
|
869
|
+
note: "",
|
|
870
|
+
noteTone: "dim",
|
|
871
|
+
options: [] as ReferenceChoice[],
|
|
872
|
+
value: current,
|
|
873
|
+
} satisfies Omit<ReferenceFieldView, "kind">;
|
|
874
|
+
|
|
875
|
+
const collection = referenceTarget(args.schema);
|
|
440
876
|
if (collection === null) {
|
|
441
877
|
/* The control was named by a `ui.control` override on a property that references nothing. The
|
|
442
878
|
field is still editable — refusing to draw it would lose the value — and the note says which
|
|
443
879
|
half of the declaration is missing. */
|
|
444
|
-
return
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
</div>`;
|
|
880
|
+
return {
|
|
881
|
+
...base,
|
|
882
|
+
hasNote: true,
|
|
883
|
+
kind: "text",
|
|
884
|
+
note: 'No collection referenced — add "$ref": "#/content/<type>" to this field.',
|
|
885
|
+
};
|
|
451
886
|
}
|
|
452
887
|
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
888
|
+
const state = referenceEntryState(collection, onSettled);
|
|
889
|
+
if (state === null) {
|
|
890
|
+
// Only the FIRST paint of a collection waits; a settled one is answered from the cache above.
|
|
891
|
+
return {
|
|
892
|
+
...base,
|
|
893
|
+
disabled: true,
|
|
894
|
+
kind: "picker",
|
|
895
|
+
options: [{ label: "Loading…", value: current }],
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
if ("error" in state) {
|
|
899
|
+
return {
|
|
900
|
+
...base,
|
|
901
|
+
canRetry: true,
|
|
902
|
+
hasNote: true,
|
|
903
|
+
kind: "text",
|
|
904
|
+
note: `Could not list ${collection} entries — ${state.error}`,
|
|
905
|
+
noteTone: "danger",
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
const dangling = current !== "" && !state.ids.includes(current);
|
|
910
|
+
return {
|
|
911
|
+
...base,
|
|
912
|
+
hasNote: state.ids.length === 0,
|
|
913
|
+
kind: "picker",
|
|
914
|
+
note: state.ids.length === 0 ? `No ${collection} entries yet.` : "",
|
|
915
|
+
options: [
|
|
916
|
+
{ label: "—", value: "" },
|
|
917
|
+
...(dangling ? [{ label: `${current} — not found`, value: current }] : []),
|
|
918
|
+
...state.ids.map((id) => ({ label: id, value: id })),
|
|
919
|
+
],
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
/**
|
|
924
|
+
* Mount the reference picker into the host the form drew for it.
|
|
925
|
+
*
|
|
926
|
+
* @param {HTMLElement} host - The field's `[part="control-host"]`, or a row cell's own host
|
|
927
|
+
* @param {SchemaFormControlArgs} args - The value, the schema and the commit hook
|
|
928
|
+
* @returns {SchemaFormControlHandle}
|
|
929
|
+
*/
|
|
930
|
+
function mountReference(host: HTMLElement, args: SchemaFormControlArgs): SchemaFormControlHandle {
|
|
931
|
+
let latest = args;
|
|
932
|
+
let disposed = false;
|
|
933
|
+
|
|
934
|
+
/* The read lands turns after the control asked for it, and the field may be gone by then — a
|
|
935
|
+
cell whose row was removed, a form the pane took down. A redraw after that would update a scope
|
|
936
|
+
nothing is mounted on. */
|
|
937
|
+
const redraw = (): void => {
|
|
938
|
+
if (!disposed) {
|
|
939
|
+
surface.update(referenceView(latest, redraw));
|
|
472
940
|
}
|
|
473
|
-
|
|
941
|
+
};
|
|
474
942
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
943
|
+
const surface = mountReferenceFieldSurface(host, {
|
|
944
|
+
choose(value) {
|
|
945
|
+
latest.onChange(value === "" ? undefined : value);
|
|
946
|
+
},
|
|
947
|
+
edit(value) {
|
|
948
|
+
latest.onChange(value.trim() || undefined);
|
|
949
|
+
},
|
|
950
|
+
retry() {
|
|
951
|
+
const collection = referenceTarget(latest.schema);
|
|
952
|
+
if (collection !== null) {
|
|
953
|
+
invalidateReferenceEntries(collection);
|
|
483
954
|
}
|
|
484
|
-
|
|
485
|
-
|
|
955
|
+
redraw();
|
|
956
|
+
},
|
|
957
|
+
});
|
|
486
958
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
return referencePicker(collection, ids, current, onChange);
|
|
959
|
+
redraw();
|
|
960
|
+
|
|
961
|
+
return {
|
|
962
|
+
dispose() {
|
|
963
|
+
disposed = true;
|
|
964
|
+
surface.dispose();
|
|
494
965
|
},
|
|
495
|
-
(
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
return failedTpl(message);
|
|
966
|
+
update(next) {
|
|
967
|
+
latest = next;
|
|
968
|
+
redraw();
|
|
499
969
|
},
|
|
500
|
-
|
|
970
|
+
};
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
/**
|
|
974
|
+
* The reference control as the registry holds it. Named rather than written inline at the
|
|
975
|
+
* registration below so a test can drive it the way the engine does, without the registry growing a
|
|
976
|
+
* second public lookup for it.
|
|
977
|
+
*/
|
|
978
|
+
export const referenceControl: SchemaFormMountedControl = { mount: mountReference };
|
|
501
979
|
|
|
502
|
-
|
|
503
|
-
${until(
|
|
504
|
-
resolved,
|
|
505
|
-
html`<sp-picker size="s" class="reference-field" disabled label="Loading…"></sp-picker>`,
|
|
506
|
-
)}
|
|
507
|
-
</div>`;
|
|
508
|
-
});
|
|
980
|
+
registerFormControl("reference", referenceControl);
|
|
509
981
|
|
|
510
982
|
// Keep an explicit export so hosts can assert the built-ins module loaded
|
|
511
983
|
export const builtinFormControls = ["schema-builder", "secret", "reference"] as const;
|