@jxsuite/studio 8.0.0 → 8.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/dist/chunks/{2020-5wtzce5g.js → 2020-dg097k3e.js} +3 -3
- package/dist/chunks/adapter-tck81zft.js +4 -0
- package/dist/chunks/{add-new-flow-psym14sn.js → add-new-flow-a1w0bnm6.js} +3 -3
- package/dist/chunks/{cf-account-picker-haqazq5r.js → cf-account-picker-ce3kp3x1.js} +3 -3
- package/dist/chunks/cf-settings-7ye6x7g2.js +4 -0
- package/dist/chunks/color-4gj49xq6.js +4 -0
- package/dist/chunks/{color-area-6rbra5dy.js → color-area-vh9cew51.js} +2 -2
- package/dist/chunks/{color-field-gy56c94g.js → color-field-e78f16bs.js} +3 -3
- package/dist/chunks/{color-slider-sqqdyf6c.js → color-slider-fg4yhvk6.js} +2 -2
- package/dist/chunks/{combobox-72h32ejk.js → combobox-6hs00996.js} +2 -2
- package/dist/chunks/components-qce0v93f.js +4 -0
- package/dist/chunks/content-source-jjx8kgzr.js +4 -0
- package/dist/chunks/{convert-to-repeater-he70fc2b.js → convert-to-repeater-grnmea6c.js} +3 -3
- package/dist/chunks/editor-bkja5dbv.js +4 -0
- package/dist/chunks/entry-commands-1wh0btvg.js +4 -0
- package/dist/chunks/{extension-sections-0egb88dm.js → extension-sections-rp7fkega.js} +3 -3
- package/dist/chunks/file-ops-6pe4z7an.js +4 -0
- package/dist/chunks/files-ybpp3j38.js +4 -0
- package/dist/chunks/format-host-3mtbhxk1.js +4 -0
- package/dist/chunks/fs-events-xfanfxds.js +4 -0
- package/dist/chunks/grid-open-dg5qmnaj.js +4 -0
- package/dist/chunks/{grid-view-qg1b5eqx.js → grid-view-x26rfpz1.js} +16 -15
- package/dist/chunks/{icons-63bjtbs7.js → icons-853n6qt3.js} +2 -2
- package/dist/chunks/{iframe-entry-2zggzajv.js → iframe-entry-02wzcyvy.js} +4 -4
- package/dist/chunks/{iframe-entry-99nr5zfb.js → iframe-entry-42hpmwz5.js} +4 -4
- package/dist/chunks/{iframe-entry-76j8vwwx.js → iframe-entry-b5qgggyp.js} +2 -2
- package/dist/chunks/iframe-host-j2bk2xkc.js +4 -0
- package/dist/chunks/{index-32eqzv1k.js → index-203h71wy.js} +3 -3
- package/dist/chunks/index-fkrm7pdk.js +4 -0
- package/dist/chunks/{javascript-pjp734q4.js → javascript-ww5pv5ae.js} +2 -2
- package/dist/chunks/{jsonMode-njkvsa02.js → jsonMode-vwbdwwq3.js} +6 -6
- package/dist/chunks/jx-validate-w7v32f6a.js +4 -0
- package/dist/chunks/layers-g7j732cm.js +4 -0
- package/dist/chunks/layers-panel-gjrsxcck.js +4 -0
- package/dist/chunks/{listbox-c421cf8c.js → listbox-7gka1h86.js} +2 -2
- package/dist/chunks/lit-html-jdfz9rhc.js +4 -0
- package/dist/chunks/media-library-9j0v4wmk.js +4 -0
- package/dist/chunks/media-picker-cpyyt2m0.js +4 -0
- package/dist/chunks/{menu-bdpzbfvs.js → menu-d2jxpgma.js} +2 -2
- package/dist/chunks/{monaco-binding-vq1hgnn0.js → monaco-binding-63tqze27.js} +3 -3
- package/dist/chunks/monaco-buffer-9wcme176.js +4 -0
- package/dist/chunks/{monaco-setup-xrd2cbkp.js → monaco-setup-zyyynnj8.js} +3 -3
- package/dist/chunks/notify-rahnnnv4.js +4 -0
- package/dist/chunks/platform-b2aqv7v5.js +4 -0
- package/dist/chunks/{popover-b89sfej7.js → popover-9tf4cjrk.js} +2 -2
- package/dist/chunks/publish-panel-4yk2461b.js +4 -0
- package/dist/chunks/{publish-quick-qx2455cj.js → publish-quick-ajzdhhs6.js} +3 -3
- package/dist/chunks/publish-workspace-mxestzcf.js +4 -0
- package/dist/chunks/quick-search-9g6z65b0.js +4 -0
- package/dist/chunks/references-c26enpxs.js +4 -0
- package/dist/chunks/right-panel-9d0e0yg7.js +4 -0
- package/dist/chunks/source-control-xyn3a8ds.js +4 -0
- package/dist/chunks/{split-c9zfdgxf.js → split-m5rwn31z.js} +2 -2
- package/dist/chunks/{studio-x9hsze61.js → studio-02phtmqn.js} +4 -4
- package/dist/chunks/{studio-4hp8pkyt.js → studio-0khd0xdw.js} +4 -4
- package/dist/chunks/studio-1fvzq9vs.js +5 -0
- package/dist/chunks/studio-1gdfsr49.js +12 -0
- package/dist/chunks/{studio-3q40e3rj.js → studio-1meqbxns.js} +4 -4
- package/dist/chunks/{studio-q0tccd43.js → studio-1yhz0s8p.js} +4 -4
- package/dist/chunks/studio-25a5by3h.js +5 -0
- package/dist/chunks/{studio-k3xxbagr.js → studio-2b8443ms.js} +3 -3
- package/dist/chunks/{studio-3qqjeep5.js → studio-2c2mjyzj.js} +5 -5
- package/dist/chunks/studio-2dtc90zs.js +10 -0
- package/dist/chunks/{studio-9tmebf0s.js → studio-332hesdk.js} +4 -4
- package/dist/chunks/{studio-03ct6yfv.js → studio-3pndhat0.js} +8 -8
- package/dist/chunks/{studio-s32578pn.js → studio-3q6n9qvq.js} +4 -4
- package/dist/chunks/studio-4dsdnan2.js +9 -0
- package/dist/chunks/{studio-ed1hp6zf.js → studio-4fjqdzrb.js} +5 -5
- package/dist/chunks/{studio-jcnsdaep.js → studio-533py3yy.js} +4 -4
- package/dist/chunks/studio-5abge3wy.js +5 -0
- package/dist/chunks/{studio-8tr6cfrc.js → studio-6vrxzkba.js} +4 -4
- package/dist/chunks/{studio-jaaz8ce9.js → studio-73wz63nn.js} +4 -4
- package/dist/chunks/{studio-bavb31r0.js → studio-74nzv280.js} +4 -4
- package/dist/chunks/{studio-q7rtx6zq.js → studio-7cg75gtj.js} +7 -7
- package/dist/chunks/studio-89cwmnae.js +5 -0
- package/dist/chunks/studio-8m1t83kn.js +7 -0
- package/dist/chunks/{studio-q0mvbaq6.js → studio-8nyw10dp.js} +4 -4
- package/dist/chunks/{studio-83mk4bgg.js → studio-94ppxeqy.js} +8 -8
- package/dist/chunks/{studio-qmn1xc24.js → studio-957jg8g3.js} +4 -4
- package/dist/chunks/{studio-r5qy30vx.js → studio-9wyqyfqy.js} +4 -4
- package/dist/chunks/studio-a3evp7f6.js +6 -0
- package/dist/chunks/{studio-n0c5h6mf.js → studio-a6x058ew.js} +4 -4
- package/dist/chunks/studio-ana67wax.js +5 -0
- package/dist/chunks/studio-b4xhpcwy.js +4 -0
- package/dist/chunks/studio-bf0rgyf7.js +5 -0
- package/dist/chunks/{studio-x1x167jh.js → studio-bqgs90r8.js} +4 -4
- package/dist/chunks/{studio-qwdqwkj0.js → studio-c4rcaftm.js} +4 -4
- package/dist/chunks/{studio-mn0476ag.js → studio-c5dj3dzk.js} +4 -4
- package/dist/chunks/studio-cgjt1zce.js +5 -0
- package/dist/chunks/{studio-bbe4c90a.js → studio-cpdzrdgp.js} +4 -4
- package/dist/chunks/studio-cq87hree.js +5 -0
- package/dist/chunks/studio-d140sy8e.js +5 -0
- package/dist/chunks/{studio-jsgf2q5k.js → studio-dmn7gpba.js} +5 -5
- package/dist/chunks/studio-e8pzjjhf.js +5 -0
- package/dist/chunks/{studio-dgk4dqtx.js → studio-f8kz3krj.js} +5 -5
- package/dist/chunks/{studio-qgh13jtz.js → studio-fqs1a22a.js} +4 -4
- package/dist/chunks/{studio-syreb644.js → studio-fw3jp9m7.js} +4 -4
- package/dist/chunks/{studio-ed3xx40h.js → studio-g6ysm05p.js} +6 -6
- package/dist/chunks/studio-gmhd0j3f.js +5 -0
- package/dist/chunks/{studio-p4nrkvqr.js → studio-gqpty626.js} +4 -4
- package/dist/chunks/{studio-kt3pjkwd.js → studio-grp684wx.js} +4 -4
- package/dist/chunks/{studio-df0baea9.js → studio-h98520qr.js} +4 -4
- package/dist/chunks/{studio-8m0y2qe7.js → studio-hnx1h1pr.js} +4 -4
- package/dist/chunks/studio-hvxt8z79.js +5 -0
- package/dist/chunks/{studio-43sd2mam.js → studio-hw60n5dh.js} +3 -3
- package/dist/chunks/{studio-wbcjbhy1.js → studio-hxq1ebp0.js} +6 -6
- package/dist/chunks/{studio-wys23mxz.js → studio-j4e627w1.js} +4 -4
- package/dist/chunks/{studio-ccv2svc6.js → studio-j5jqvyn3.js} +4 -4
- package/dist/chunks/{studio-881z1xrx.js → studio-jqrg9xxg.js} +4 -4
- package/dist/chunks/{studio-b7y5mr30.js → studio-kdseew9m.js} +4 -4
- package/dist/chunks/studio-kt2j51sa.js +5 -0
- package/dist/chunks/{studio-d5grtb9e.js → studio-m4w6sygj.js} +9 -9
- package/dist/chunks/{studio-zz9qe3rw.js → studio-mmqr9awv.js} +37 -37
- package/dist/chunks/{studio-5g0rprdt.js → studio-nm9qev4p.js} +5 -5
- package/dist/chunks/{studio-tft6r105.js → studio-nsv7g1qn.js} +4 -4
- package/dist/chunks/{studio-3e54qh39.js → studio-p89w12s2.js} +5 -5
- package/dist/chunks/{studio-y3ahhdpx.js → studio-pbagym4j.js} +4 -4
- package/dist/chunks/{studio-w4rz9hdk.js → studio-pr23qhrw.js} +4 -4
- package/dist/chunks/{studio-6m380wp7.js → studio-r4ya3sb2.js} +4 -4
- package/dist/chunks/studio-rdnezzkd.js +6 -0
- package/dist/chunks/{studio-f14e1zdj.js → studio-rm9m3z60.js} +4 -4
- package/dist/chunks/studio-tpmnbgmt.js +5 -0
- package/dist/chunks/studio-ttxhq5yt.js +5 -0
- package/dist/chunks/{studio-nh6d342n.js → studio-txkx6586.js} +6 -6
- package/dist/chunks/{studio-a2bvaptz.js → studio-v0gsjav0.js} +4 -4
- package/dist/chunks/{studio-fbhxxc5f.js → studio-v2ek69cb.js} +4 -4
- package/dist/chunks/{studio-y0n7yxea.js → studio-wcakp5bt.js} +7 -7
- package/dist/chunks/{studio-k7evxqh4.js → studio-x3ge4rts.js} +4 -4
- package/dist/chunks/{studio-pbxa5fpc.js → studio-xddgbwhe.js} +3 -3
- package/dist/chunks/{studio-c05wdnt3.js → studio-y1x65h5r.js} +4 -4
- package/dist/chunks/{studio-rthyg42z.js → studio-z42kysg7.js} +169 -169
- package/dist/chunks/studio-zf188hnd.js +5 -0
- package/dist/chunks/{studio-tfkznv39.js → studio-zht0x0v3.js} +4 -4
- package/dist/chunks/{studio-svb7yyve.js → studio-zm7dnr9g.js} +4 -4
- package/dist/chunks/{tooltip-yypz28gq.js → tooltip-dj1pvgz7.js} +2 -2
- package/dist/chunks/{tree-9xa4hm7n.js → tree-j0ebwr5s.js} +2 -2
- package/dist/chunks/{tsMode-epyfz1hv.js → tsMode-vra2hh7y.js} +5 -5
- package/dist/chunks/workspace-3nt93k48.js +4 -0
- package/dist/chunks/workspace-home-zbgyqqws.js +4 -0
- package/dist/chunks/{ws-client-bv552sfx.js → ws-client-72x5n99j.js} +3 -3
- package/dist/iframe-entry.js +14 -14
- package/dist/manifest.json +75 -75
- package/dist/studio.js +80 -80
- package/package.json +11 -11
- package/src/browse/component-examples.ts +15 -224
- package/src/browse/library-preview.ts +6 -13
- package/src/browse/preview-root.ts +7 -4
- package/src/canvas/canvas-utils.ts +18 -4
- package/src/canvas/iframe-editable-root.ts +13 -4
- package/src/canvas/iframe-entry.ts +3 -1
- package/src/canvas/iframe-interaction.ts +11 -2
- package/src/canvas/iframe-render.ts +25 -3
- package/src/canvas/preview-placeholder.ts +120 -0
- package/src/editor/shortcuts.ts +3 -0
- package/src/panels/component-preview.ts +5 -0
- package/src/panels/pane-context.ts +43 -18
- package/src/panels/site-model.ts +7 -19
- package/src/panels/site-panel.ts +2 -25
- package/src/panels/stylebook-doc.ts +2 -53
- package/src/services/references.ts +0 -0
- package/src/style/stylebook-specimens.ts +11 -15
- package/src/surfaces/pane-context.json +286 -265
- package/src/surfaces/pane-context.ts +17 -8
- package/src/tabs/tab.ts +5 -6
- package/src/utils/geometry.ts +49 -2
- package/dist/chunks/adapter-5tphg7xe.js +0 -4
- package/dist/chunks/cf-settings-h2wrh6hq.js +0 -4
- package/dist/chunks/color-m7r1vk54.js +0 -4
- package/dist/chunks/components-r22xca09.js +0 -4
- package/dist/chunks/content-source-mmppmcyg.js +0 -4
- package/dist/chunks/editor-ccd9jwsa.js +0 -4
- package/dist/chunks/entry-commands-6qxz9e8p.js +0 -4
- package/dist/chunks/file-ops-qnab36ek.js +0 -4
- package/dist/chunks/files-rcn56qvm.js +0 -4
- package/dist/chunks/format-host-yxz0kjjs.js +0 -4
- package/dist/chunks/fs-events-d1qxq4z4.js +0 -4
- package/dist/chunks/grid-open-4z1g062t.js +0 -4
- package/dist/chunks/iframe-host-ssv7kyj0.js +0 -4
- package/dist/chunks/index-f0jn7nnp.js +0 -4
- package/dist/chunks/jx-validate-ttasrbh7.js +0 -4
- package/dist/chunks/layers-bgyr11tr.js +0 -4
- package/dist/chunks/layers-panel-7ez66zeq.js +0 -4
- package/dist/chunks/lit-html-v9gh687x.js +0 -4
- package/dist/chunks/media-library-sq468wts.js +0 -4
- package/dist/chunks/media-picker-shfe59pt.js +0 -4
- package/dist/chunks/monaco-buffer-3afgwvbp.js +0 -4
- package/dist/chunks/notify-kzrjr3fv.js +0 -4
- package/dist/chunks/platform-7v4rtd7m.js +0 -4
- package/dist/chunks/publish-panel-2z9gw8ee.js +0 -4
- package/dist/chunks/publish-workspace-wrc1dgwx.js +0 -4
- package/dist/chunks/quick-search-n0t0eqxb.js +0 -4
- package/dist/chunks/references-9547rg5s.js +0 -4
- package/dist/chunks/right-panel-wsgnr9pn.js +0 -4
- package/dist/chunks/source-control-kwktr9y6.js +0 -4
- package/dist/chunks/studio-07rcfje3.js +0 -5
- package/dist/chunks/studio-3exxhz5g.js +0 -5
- package/dist/chunks/studio-3qeh9t0h.js +0 -6
- package/dist/chunks/studio-58xg6j2q.js +0 -9
- package/dist/chunks/studio-80jyh1xh.js +0 -4
- package/dist/chunks/studio-8vcvfs7r.js +0 -6
- package/dist/chunks/studio-fyh0jc96.js +0 -5
- package/dist/chunks/studio-gcxk20n7.js +0 -5
- package/dist/chunks/studio-m8g16bgz.js +0 -5
- package/dist/chunks/studio-mx1smyq0.js +0 -5
- package/dist/chunks/studio-pgmvdv7z.js +0 -7
- package/dist/chunks/studio-ph84avkf.js +0 -5
- package/dist/chunks/studio-rj9eamf8.js +0 -5
- package/dist/chunks/studio-rptgsk96.js +0 -5
- package/dist/chunks/studio-s19tp1xr.js +0 -5
- package/dist/chunks/studio-tf9jryb1.js +0 -10
- package/dist/chunks/studio-txf2kysx.js +0 -5
- package/dist/chunks/studio-v8867ddv.js +0 -12
- package/dist/chunks/studio-vxq7fcvv.js +0 -5
- package/dist/chunks/studio-wc649wss.js +0 -5
- package/dist/chunks/studio-z97c48fk.js +0 -5
- package/dist/chunks/studio-zat62m0c.js +0 -5
- package/dist/chunks/studio-zcd2e7yz.js +0 -5
- package/dist/chunks/workspace-dz7nxfhv.js +0 -4
- package/dist/chunks/workspace-home-xsd7hkjw.js +0 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jxsuite/studio",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.1.0",
|
|
4
4
|
"description": "Jx Studio — visual builder for Jx documents",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -61,31 +61,31 @@
|
|
|
61
61
|
"dependencies": {
|
|
62
62
|
"@atlaskit/pragmatic-drag-and-drop": "^4.0.0",
|
|
63
63
|
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^3.0.0",
|
|
64
|
-
"@jxsuite/ai": "^0.
|
|
64
|
+
"@jxsuite/ai": "^0.40.0",
|
|
65
65
|
"@jxsuite/collab": "^0.11.2",
|
|
66
|
-
"@jxsuite/create": "^1.
|
|
67
|
-
"@jxsuite/formulas": "^0.0.
|
|
66
|
+
"@jxsuite/create": "^1.4.0",
|
|
67
|
+
"@jxsuite/formulas": "^0.0.24",
|
|
68
68
|
"@jxsuite/markup": "^1.0.0",
|
|
69
69
|
"@jxsuite/protocol": "^4.0.0",
|
|
70
|
-
"@jxsuite/runtime": "^5.
|
|
70
|
+
"@jxsuite/runtime": "^5.2.0",
|
|
71
71
|
"@jxsuite/schema": "^4.0.0",
|
|
72
|
-
"@jxsuite/site": "^2.0
|
|
73
|
-
"@jxsuite/ui": "^0.
|
|
72
|
+
"@jxsuite/site": "^2.2.0",
|
|
73
|
+
"@jxsuite/ui": "^0.8.0",
|
|
74
74
|
"@vue/reactivity": "3.5.44",
|
|
75
75
|
"ajv": "^8.20.0",
|
|
76
76
|
"ajv-formats": "^3.0.1",
|
|
77
77
|
"lit-html": "^3.3.3",
|
|
78
78
|
"monaco-editor": "^0.57.0",
|
|
79
|
-
"tabulator-tables": "^6.
|
|
79
|
+
"tabulator-tables": "^6.7.1",
|
|
80
80
|
"yaml": "^2.9.1"
|
|
81
81
|
},
|
|
82
82
|
"devDependencies": {
|
|
83
|
-
"@happy-dom/global-registrator": "^21.0.
|
|
83
|
+
"@happy-dom/global-registrator": "^21.0.4",
|
|
84
84
|
"@jxsuite/test-support": "^0.0.0",
|
|
85
85
|
"@types/mdast": "^4.0.4",
|
|
86
|
-
"@webref/css": "^8.7.
|
|
86
|
+
"@webref/css": "^8.7.8",
|
|
87
87
|
"@webref/elements": "^2.9.2",
|
|
88
|
-
"@webref/idl": "^3.
|
|
88
|
+
"@webref/idl": "^3.86.0"
|
|
89
89
|
},
|
|
90
90
|
"//vue-reactivity": "Exact pin required — must match @jxsuite/runtime to avoid cross-boundary proxy bugs"
|
|
91
91
|
}
|
|
@@ -3,49 +3,25 @@
|
|
|
3
3
|
*
|
|
4
4
|
* A preview instance used to be given nothing, so it drew its definition's DEFAULTS, and a starter
|
|
5
5
|
* component's defaults are empty strings: a page header with no title, a band with no label, a card
|
|
6
|
-
* with no name.
|
|
7
|
-
*
|
|
6
|
+
* with no name. The definition's own `examples` fill that gap: the JSON Schema keyword a typed
|
|
7
|
+
* state entry may carry (`spec.md` §5.3, Shape 2), whose first value is the author's own sample.
|
|
8
|
+
* Only a prop that is not private (`spec.md` §5.6), and whose sample is literal data, is taken.
|
|
8
9
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* outside the page that binds it, so it is not one.
|
|
15
|
-
* 3. A list that draws it ({@link componentSampleRow}): a component used only as a repeater's row,
|
|
16
|
-
* every prop bound to the row, is drawn as that list's first row, with the list's data.
|
|
17
|
-
*
|
|
18
|
-
* Only a prop the definition declares, and that is not private (`spec.md` §5.6), is taken. Files in
|
|
19
|
-
* the project's default language are read before translations, so a preview reads in the language
|
|
20
|
-
* the site is written in first.
|
|
10
|
+
* The preview never looks further than the definition. It once borrowed props from the component's
|
|
11
|
+
* first use in the project, and drew a component used only as a list row as that list's first row,
|
|
12
|
+
* both found through the reference index; that cost one project-wide usage query per component on
|
|
13
|
+
* every Library and Project Styles paint, which on a hosted project was enough requests at once to
|
|
14
|
+
* reset the session. A batched or on-demand source may bring it back.
|
|
21
15
|
*
|
|
22
16
|
* @docs studio/interface
|
|
23
17
|
*/
|
|
24
18
|
|
|
25
|
-
import {
|
|
26
|
-
|
|
27
|
-
isNodeObject,
|
|
28
|
-
isPrivateStateKey,
|
|
29
|
-
isRef,
|
|
30
|
-
isString,
|
|
31
|
-
} from "@jxsuite/schema/guards";
|
|
32
|
-
import { localeOfPath, resolveI18n } from "@jxsuite/schema/locale";
|
|
33
|
-
import { getPlatform } from "../platform";
|
|
34
|
-
import { formatForPath, loadFormats } from "../format/format-host";
|
|
35
|
-
import { parseSourceForPath } from "../files/file-ops";
|
|
36
|
-
import { loadUsages } from "../services/references";
|
|
37
|
-
import { documentBase } from "../canvas/canvas-origin";
|
|
38
|
-
import { bakeRepeaterSources } from "../canvas/repeater-sources";
|
|
39
|
-
import { pageContextFor } from "../canvas/canvas-page-context";
|
|
40
|
-
import { projectState } from "../store";
|
|
41
|
-
import type { JsonValue, JxDocument, JxDocumentValue, JxMutableNode } from "@jxsuite/schema/types";
|
|
19
|
+
import { isJsonObject, isPrivateStateKey, isString } from "@jxsuite/schema/guards";
|
|
20
|
+
import type { JsonValue, JxDocument, JxDocumentValue } from "@jxsuite/schema/types";
|
|
42
21
|
|
|
43
22
|
/** A component's sample props, by state key. */
|
|
44
23
|
export type ExampleProps = Map<string, JsonValue>;
|
|
45
24
|
|
|
46
|
-
/** How many of the files that use a component are read for an instance to draw it from. */
|
|
47
|
-
const USAGE_FILES_READ = 4;
|
|
48
|
-
|
|
49
25
|
/** Whether a value is plain data a prop can hold outside any page: no template, no binding. */
|
|
50
26
|
export function isLiteralProp(value: JxDocumentValue | undefined): value is JsonValue {
|
|
51
27
|
if (value === undefined) {
|
|
@@ -68,13 +44,11 @@ export function isLiteralProp(value: JxDocumentValue | undefined): value is Json
|
|
|
68
44
|
return true;
|
|
69
45
|
}
|
|
70
46
|
|
|
71
|
-
/**
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
/** The first `examples` value of each typed state entry that declares one. */
|
|
77
|
-
export function declaredExamples(definition: JxDocument): ExampleProps {
|
|
47
|
+
/**
|
|
48
|
+
* The props a component's preview instance is given: the first `examples` value of each public
|
|
49
|
+
* typed state entry that declares a literal one.
|
|
50
|
+
*/
|
|
51
|
+
export function componentExampleProps(definition: JxDocument): ExampleProps {
|
|
78
52
|
const out: ExampleProps = new Map();
|
|
79
53
|
for (const [key, entry] of Object.entries(definition.state ?? {})) {
|
|
80
54
|
if (isPrivateStateKey(key) || !isJsonObject(entry) || !Array.isArray(entry.examples)) {
|
|
@@ -87,186 +61,3 @@ export function declaredExamples(definition: JxDocument): ExampleProps {
|
|
|
87
61
|
}
|
|
88
62
|
return out;
|
|
89
63
|
}
|
|
90
|
-
|
|
91
|
-
/** Every node of a tree, each with the repeater (if any) whose row template holds it. */
|
|
92
|
-
function eachNode(
|
|
93
|
-
tree: JxDocumentValue | undefined,
|
|
94
|
-
visit: (node: JxMutableNode, repeater: JxMutableNode | null) => void,
|
|
95
|
-
): void {
|
|
96
|
-
const walk = (value: JxDocumentValue | undefined, repeater: JxMutableNode | null): void => {
|
|
97
|
-
if (Array.isArray(value)) {
|
|
98
|
-
for (const member of value) {
|
|
99
|
-
walk(member, repeater);
|
|
100
|
-
}
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
if (!isNodeObject(value)) {
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
visit(value, repeater);
|
|
107
|
-
walk(value.children, repeater);
|
|
108
|
-
walk(value.map, value.$prototype === "Array" ? value : repeater);
|
|
109
|
-
for (const branch of Object.values(value.cases ?? {})) {
|
|
110
|
-
walk(branch, repeater);
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
walk(tree, null);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* The literal props of the instance of `tagName` in `tree` that passes the most of them, among the
|
|
118
|
-
* keys the definition declares; empty when no instance passes any.
|
|
119
|
-
*/
|
|
120
|
-
export function literalUsageProps(
|
|
121
|
-
tree: JxDocumentValue | undefined,
|
|
122
|
-
tagName: string,
|
|
123
|
-
keys: ReadonlySet<string>,
|
|
124
|
-
): ExampleProps {
|
|
125
|
-
let best: ExampleProps = new Map();
|
|
126
|
-
eachNode(tree, (node) => {
|
|
127
|
-
if (node.tagName !== tagName || !isJsonObject(node.$props)) {
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
const found: ExampleProps = new Map();
|
|
131
|
-
for (const [key, prop] of Object.entries(node.$props)) {
|
|
132
|
-
if (keys.has(key) && isLiteralProp(prop)) {
|
|
133
|
-
found.set(key, prop);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
if (found.size > best.size) {
|
|
137
|
-
best = found;
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
return best;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/** One project document as a tree, or null when it cannot be read or parsed. */
|
|
144
|
-
async function readTree(path: string): Promise<JxMutableNode | null> {
|
|
145
|
-
try {
|
|
146
|
-
const content = await getPlatform().readFile(path);
|
|
147
|
-
await loadFormats();
|
|
148
|
-
let tree: JxDocumentValue;
|
|
149
|
-
if (formatForPath(path)) {
|
|
150
|
-
const parsed = await parseSourceForPath(path, content);
|
|
151
|
-
tree = parsed.document;
|
|
152
|
-
} else if (path.endsWith(".json")) {
|
|
153
|
-
/* SAFETY: a `.json` project document is JSON, and every read of it below is guarded. */
|
|
154
|
-
tree = JSON.parse(content) as JxDocumentValue;
|
|
155
|
-
} else {
|
|
156
|
-
return null;
|
|
157
|
-
}
|
|
158
|
-
return isNodeObject(tree) ? tree : null;
|
|
159
|
-
} catch {
|
|
160
|
-
return null;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/** The files that use a component, default language first, at most {@link USAGE_FILES_READ}. */
|
|
165
|
-
async function usageFiles(path: string, tagName: string): Promise<string[]> {
|
|
166
|
-
const usages = await loadUsages({ path, tagName });
|
|
167
|
-
if (usages.status !== "ready") {
|
|
168
|
-
return [];
|
|
169
|
-
}
|
|
170
|
-
const { i18n } = resolveI18n(projectState?.projectConfig ?? {});
|
|
171
|
-
const translated = (file: string): boolean => {
|
|
172
|
-
const locale = localeOfPath(file, i18n);
|
|
173
|
-
return locale !== null && locale !== i18n?.defaultLocale;
|
|
174
|
-
};
|
|
175
|
-
const files = usages.result.files.map((file) => file.path);
|
|
176
|
-
return [
|
|
177
|
-
...files.filter((file) => !translated(file)),
|
|
178
|
-
...files.filter((file) => translated(file)),
|
|
179
|
-
].slice(0, USAGE_FILES_READ);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/** The props a component's preview instance is given (sources 1 and 2 of the module comment). */
|
|
183
|
-
export async function componentExampleProps(
|
|
184
|
-
definition: JxDocument,
|
|
185
|
-
path: string,
|
|
186
|
-
): Promise<ExampleProps> {
|
|
187
|
-
const keys = propKeys(definition);
|
|
188
|
-
const tagName = isString(definition.tagName) ? definition.tagName : null;
|
|
189
|
-
const examples = declaredExamples(definition);
|
|
190
|
-
if (!tagName || keys.size === 0) {
|
|
191
|
-
return examples;
|
|
192
|
-
}
|
|
193
|
-
let best: ExampleProps = new Map();
|
|
194
|
-
for (const file of await usageFiles(path, tagName)) {
|
|
195
|
-
const found = literalUsageProps(await readTree(file), tagName, keys);
|
|
196
|
-
if (found.size > best.size) {
|
|
197
|
-
best = found;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
// The author's own sample wins over a page's value for the same prop.
|
|
201
|
-
return new Map([...best, ...examples]);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/** A sample-row document, and the file whose list it draws (its relative refs resolve there). */
|
|
205
|
-
export interface SampleRow {
|
|
206
|
-
doc: JxDocument;
|
|
207
|
-
file: string;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/** The state key a repeater reads its whole list from (`#/state/shows`), or null. */
|
|
211
|
-
function listKeyOf(repeater: JxMutableNode): string | null {
|
|
212
|
-
const { items } = repeater;
|
|
213
|
-
if (!isRef(items) || !items.$ref.startsWith("#/state/")) {
|
|
214
|
-
return null;
|
|
215
|
-
}
|
|
216
|
-
const key = items.$ref.slice("#/state/".length);
|
|
217
|
-
return key !== "" && !key.includes("/") ? key : null;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* A document that draws the first row of a list using the component (source 3 of the module
|
|
222
|
-
* comment), or null when no file that uses it has one: the list's repeater, its data resolved the
|
|
223
|
-
* way the canvas resolves it for Edit (`canvas/repeater-sources.ts`) and cut to its first item, in
|
|
224
|
-
* the page's own state and `$site` / `$page`.
|
|
225
|
-
*/
|
|
226
|
-
export async function componentSampleRow(
|
|
227
|
-
definition: JxDocument,
|
|
228
|
-
path: string,
|
|
229
|
-
): Promise<SampleRow | null> {
|
|
230
|
-
const tagName = isString(definition.tagName) ? definition.tagName : null;
|
|
231
|
-
if (!tagName) {
|
|
232
|
-
return null;
|
|
233
|
-
}
|
|
234
|
-
for (const file of await usageFiles(path, tagName)) {
|
|
235
|
-
const tree = await readTree(file);
|
|
236
|
-
const lists: JxMutableNode[] = [];
|
|
237
|
-
eachNode(tree, (node, repeater) => {
|
|
238
|
-
if (node.tagName === tagName && repeater && listKeyOf(repeater)) {
|
|
239
|
-
lists.push(repeater);
|
|
240
|
-
}
|
|
241
|
-
});
|
|
242
|
-
const [list] = lists;
|
|
243
|
-
const key = list ? listKeyOf(list) : null;
|
|
244
|
-
if (!tree || !list || !key) {
|
|
245
|
-
continue;
|
|
246
|
-
}
|
|
247
|
-
const baked = await bakeRepeaterSources(
|
|
248
|
-
tree,
|
|
249
|
-
[key],
|
|
250
|
-
new URL(file, documentBase(projectState?.projectRoot)).href,
|
|
251
|
-
);
|
|
252
|
-
const rows = baked.state?.[key];
|
|
253
|
-
if (!Array.isArray(rows) || rows.length === 0) {
|
|
254
|
-
continue;
|
|
255
|
-
}
|
|
256
|
-
const context = file.startsWith("pages/")
|
|
257
|
-
? await pageContextFor(
|
|
258
|
-
{ documentPath: file, isEntry: false, lang: tree.$lang, title: tree.title },
|
|
259
|
-
projectState?.projectConfig,
|
|
260
|
-
)
|
|
261
|
-
: null;
|
|
262
|
-
return {
|
|
263
|
-
doc: {
|
|
264
|
-
children: [list],
|
|
265
|
-
state: { ...baked.state, [key]: rows.slice(0, 1), ...context },
|
|
266
|
-
tagName: "div",
|
|
267
|
-
},
|
|
268
|
-
file,
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
return null;
|
|
272
|
-
}
|
|
@@ -39,8 +39,8 @@ import { componentRegistry } from "../files/components";
|
|
|
39
39
|
import { getPlatform } from "../platform";
|
|
40
40
|
import { loadFormats, formatForPath } from "../format/format-host";
|
|
41
41
|
import { parseSourceForPath } from "../files/file-ops";
|
|
42
|
-
import {
|
|
43
|
-
import { componentExampleProps
|
|
42
|
+
import { renderComponentPreview } from "../panels/component-preview";
|
|
43
|
+
import { componentExampleProps } from "./component-examples";
|
|
44
44
|
import { loadProjectPreviewSheets } from "./preview-style";
|
|
45
45
|
import type { ComponentEntry } from "../files/components";
|
|
46
46
|
import type { JsonValue, JxDocument } from "@jxsuite/schema/types";
|
|
@@ -170,7 +170,7 @@ export async function renderDocPreview(filePath: string): Promise<HTMLElement |
|
|
|
170
170
|
async function renderDocument(
|
|
171
171
|
document_: JxDocument,
|
|
172
172
|
filePath: string,
|
|
173
|
-
context
|
|
173
|
+
context: ReturnType<typeof assetContextFor>,
|
|
174
174
|
): Promise<HTMLElement | null> {
|
|
175
175
|
setSkipServerFunctions(true);
|
|
176
176
|
/* The document's own URL, as the canvas spells it, and the canvas's dev-proxy token. With the
|
|
@@ -195,8 +195,8 @@ async function renderDocument(
|
|
|
195
195
|
}
|
|
196
196
|
|
|
197
197
|
/**
|
|
198
|
-
* A project component's preview: an instance given its sample props
|
|
199
|
-
*
|
|
198
|
+
* A project component's preview: an instance given its declared sample props
|
|
199
|
+
* (`component-examples.ts`), or none when the definition does not render with them.
|
|
200
200
|
*/
|
|
201
201
|
async function projectComponentPreview(
|
|
202
202
|
component: ComponentEntry,
|
|
@@ -206,14 +206,7 @@ async function projectComponentPreview(
|
|
|
206
206
|
if (!definition) {
|
|
207
207
|
return null;
|
|
208
208
|
}
|
|
209
|
-
const props =
|
|
210
|
-
if (props.size > 0 && (await rendersWith(definition, path, props))) {
|
|
211
|
-
return renderComponentPreview(component, props);
|
|
212
|
-
}
|
|
213
|
-
const sample = await componentSampleRow(definition, path);
|
|
214
|
-
if (sample && (await defineComponent(component))) {
|
|
215
|
-
return renderDocument(sample.doc, sample.file);
|
|
216
|
-
}
|
|
209
|
+
const props = componentExampleProps(definition);
|
|
217
210
|
return (await rendersWith(definition, path, props))
|
|
218
211
|
? renderComponentPreview(component, props)
|
|
219
212
|
: null;
|
|
@@ -14,10 +14,13 @@ import { isFunction } from "@jxsuite/schema/guards";
|
|
|
14
14
|
import { projectPreviewSheets } from "./preview-style";
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* The one rule a preview needs that its box cannot give it from outside: an image
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
17
|
+
* The one rule a preview needs that its box cannot give it from outside: an image with no source
|
|
18
|
+
* draws a broken-image icon and its alt text, which in a thumbnail is noise. A COMPONENT preview
|
|
19
|
+
* never reaches it — `renderComponentPreview` fills such an image with the preview placeholder
|
|
20
|
+
* (`canvas/preview-placeholder.ts`), whose `data:` source this selector does not match — so it
|
|
21
|
+
* hides the empty images of DOCUMENT previews (the Content home's cards), which draw what the page
|
|
22
|
+
* wrote. Everything else about the box (its size, its scale, its light surface) is the surface
|
|
23
|
+
* document's, on the island itself.
|
|
21
24
|
*/
|
|
22
25
|
const PREVIEW_RULES = 'img:not([src]), img[src=""] { visibility: hidden; }';
|
|
23
26
|
|
|
@@ -925,6 +925,17 @@ export const CANVAS_MODES = [
|
|
|
925
925
|
|
|
926
926
|
export type CanvasMode = (typeof CANVAS_MODES)[number];
|
|
927
927
|
|
|
928
|
+
/**
|
|
929
|
+
* The modes a person, the assistant or a script may ASK for by name: every mode but `git-diff`.
|
|
930
|
+
*
|
|
931
|
+
* Diff is implicit (studio.md §3): a comparison is entered by choosing a changed file or a version
|
|
932
|
+
* in Publish, which says what it is compared against, and left by Back. Named here it would be a
|
|
933
|
+
* comparison of whatever file happened to be open against whatever HEAD happened to be, which is
|
|
934
|
+
* not a thing anyone asked to see. The mode stays in {@link CANVAS_MODES} because the stage draws
|
|
935
|
+
* it; the Source Control panel enters it through `setCanvasMode` directly.
|
|
936
|
+
*/
|
|
937
|
+
export const SELECTABLE_CANVAS_MODES = CANVAS_MODES.filter((mode) => mode !== "git-diff");
|
|
938
|
+
|
|
928
939
|
/** The base modes the preview flag composes with — the two the Canvas view axis shows it beside. */
|
|
929
940
|
const PREVIEWABLE_BASE_MODES = new Set(["edit", "design"]);
|
|
930
941
|
|
|
@@ -1166,7 +1177,10 @@ export function canvasViewCommands(deps: CanvasCommandDeps): AnyCommand[] {
|
|
|
1166
1177
|
return [
|
|
1167
1178
|
{
|
|
1168
1179
|
args: argsSchema({
|
|
1169
|
-
mode: enumProperty(
|
|
1180
|
+
mode: enumProperty(
|
|
1181
|
+
SELECTABLE_CANVAS_MODES,
|
|
1182
|
+
"The canvas mode to switch the active pane to.",
|
|
1183
|
+
),
|
|
1170
1184
|
}),
|
|
1171
1185
|
category: "View",
|
|
1172
1186
|
id: "canvas.setMode",
|
|
@@ -1189,7 +1203,7 @@ export function canvasViewCommands(deps: CanvasCommandDeps): AnyCommand[] {
|
|
|
1189
1203
|
description:
|
|
1190
1204
|
"Switch the active document's editor mode. edit and design put its element tree on " +
|
|
1191
1205
|
"the canvas, where the document tools work; preview renders it as a visitor sees it; " +
|
|
1192
|
-
"source shows the JSON; stylebook, tokens
|
|
1206
|
+
"source shows the JSON; stylebook, tokens and grid open other editors. Use it to " +
|
|
1193
1207
|
"return a document to edit or design when the tree tools are refused.",
|
|
1194
1208
|
name: "set_canvas_mode",
|
|
1195
1209
|
/* `after.editor.kind` rather than the tab's own field: it is the gate's read (`treeEditable`
|
|
@@ -1197,7 +1211,7 @@ export function canvasViewCommands(deps: CanvasCommandDeps): AnyCommand[] {
|
|
|
1197
1211
|
cannot disagree. The mode named is the one requested — `run` resolved, so it is the mode
|
|
1198
1212
|
the tab is in. */
|
|
1199
1213
|
report: ({ after, args }) => {
|
|
1200
|
-
const mode = enumArg("canvas.setMode", args, "mode",
|
|
1214
|
+
const mode = enumArg("canvas.setMode", args, "mode", SELECTABLE_CANVAS_MODES);
|
|
1201
1215
|
const reach =
|
|
1202
1216
|
after.editor.kind === "canvas"
|
|
1203
1217
|
? "the document tools address its element tree"
|
|
@@ -1206,7 +1220,7 @@ export function canvasViewCommands(deps: CanvasCommandDeps): AnyCommand[] {
|
|
|
1206
1220
|
},
|
|
1207
1221
|
},
|
|
1208
1222
|
run: (_commandCtx, args) => {
|
|
1209
|
-
const mode = enumArg("canvas.setMode", args, "mode",
|
|
1223
|
+
const mode = enumArg("canvas.setMode", args, "mode", SELECTABLE_CANVAS_MODES);
|
|
1210
1224
|
const tab = requireTab("canvas.setMode");
|
|
1211
1225
|
const { modes } = tab.capabilities;
|
|
1212
1226
|
if (!modes.includes(mode)) {
|
|
@@ -439,14 +439,20 @@ export function startEditableRoot(
|
|
|
439
439
|
* cannot put a caret in them. Capture-phase pointerdown gives the bridge a chance to open a
|
|
440
440
|
* nested editing host BEFORE the browser computes the caret, so the click still lands where it
|
|
441
441
|
* was aimed.
|
|
442
|
+
*
|
|
443
|
+
* Any other press inside uneditable content (a component's own internals, frozen layout chrome)
|
|
444
|
+
* drops the selection. The browser leaves the selection where it was when the press lands on
|
|
445
|
+
* something no caret can enter, so the caret went on blinking in the block clicked BEFORE, with
|
|
446
|
+
* the component now selected in the editor and every keystroke still typed into that block.
|
|
442
447
|
*/
|
|
443
448
|
const onPointerDownCapture = (e: Event) => {
|
|
444
|
-
if (!editingAllowed()
|
|
449
|
+
if (!editingAllowed()) {
|
|
445
450
|
return;
|
|
446
451
|
}
|
|
447
|
-
|
|
452
|
+
const target = e.target instanceof Element ? e.target : null;
|
|
453
|
+
let el = deps.onPropActivate ? target : null;
|
|
448
454
|
while (el && el !== container) {
|
|
449
|
-
if (hasDataset(el) && el.dataset.jxBoundProp) {
|
|
455
|
+
if (hasDataset(el) && el.dataset.jxBoundProp && deps.onPropActivate) {
|
|
450
456
|
/* SAFETY: The runtime builds SVG and MathML nodes as `createElementNS(...) as HTMLElement`
|
|
451
457
|
and stamps `data-jx-bound-prop` through that same `HTMLElement`-typed path, so a
|
|
452
458
|
foreign-namespace prop host already travels downstream as an `HTMLElement`. */
|
|
@@ -463,10 +469,13 @@ export function startEditableRoot(
|
|
|
463
469
|
}
|
|
464
470
|
// A stamped page block first means this is ordinary page DOM, not component internals.
|
|
465
471
|
if (hasDataset(el) && el.dataset.jxPath) {
|
|
466
|
-
|
|
472
|
+
break;
|
|
467
473
|
}
|
|
468
474
|
el = el.parentElement;
|
|
469
475
|
}
|
|
476
|
+
if (target?.closest("[contenteditable]")?.getAttribute("contenteditable") === "false") {
|
|
477
|
+
doc.getSelection()?.removeAllRanges();
|
|
478
|
+
}
|
|
470
479
|
};
|
|
471
480
|
|
|
472
481
|
/**
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
captureLocaleBaseline,
|
|
17
17
|
applySiteStyle,
|
|
18
18
|
installCanvasImageRetry,
|
|
19
|
+
isRetryableImage,
|
|
19
20
|
renderResolvedDocument,
|
|
20
21
|
} from "./iframe-render";
|
|
21
22
|
import { measureHits, startInteraction } from "./iframe-interaction";
|
|
@@ -613,7 +614,8 @@ export function startCanvasIframe(opts: {
|
|
|
613
614
|
}
|
|
614
615
|
|
|
615
616
|
const onImageError = (event: Event): void => {
|
|
616
|
-
|
|
617
|
+
// Only a failure `installCanvasImageRetry` will retry is pending; an empty source settles broken.
|
|
618
|
+
if (event.target instanceof HTMLImageElement && isRetryableImage(event.target)) {
|
|
617
619
|
retryingImages.set(event.target, Date.now() + IMAGE_RETRY_WINDOW_MS);
|
|
618
620
|
armIdleWatch();
|
|
619
621
|
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { jxPathSelector, parseJxPath, serializeJxPath } from "./path-mapping";
|
|
10
10
|
import { isFunction } from "@jxsuite/schema/guards";
|
|
11
|
-
import { rectOf } from "../utils/geometry";
|
|
11
|
+
import { isDisplayContents, rectOf } from "../utils/geometry";
|
|
12
12
|
import { computeInsertZones, insertZonesKey } from "./iframe-insert";
|
|
13
13
|
import { getActiveElement, isEditing } from "../editor/inline-edit";
|
|
14
14
|
import type { IframeChannel } from "./iframe-channel";
|
|
@@ -76,11 +76,20 @@ export function nearestHit(target: EventTarget | null): NodeHit | null {
|
|
|
76
76
|
*
|
|
77
77
|
* An engine without `checkVisibility` gets the benefit of the doubt. Withholding a selection box
|
|
78
78
|
* from a node that is on screen is a worse failure than drawing one for a node that is not.
|
|
79
|
+
*
|
|
80
|
+
* `checkVisibility` also answers false for an element with no box of its own, and a `display:
|
|
81
|
+
* contents` element never has one. A component host styled that way (its rendered root does the
|
|
82
|
+
* layout) is on screen whenever anything inside it is, so its children are asked instead. Without
|
|
83
|
+
* this, selecting such a component drew no selection box and no action bar, because every one of
|
|
84
|
+
* them was reported hidden.
|
|
79
85
|
*/
|
|
80
86
|
function isRendered(el: Element): boolean {
|
|
81
87
|
// Absent on engines that predate it, whatever lib.dom says.
|
|
82
88
|
const check: Element["checkVisibility"] | undefined = el.checkVisibility;
|
|
83
|
-
|
|
89
|
+
if (!isFunction(check) || check.call(el, { checkVisibilityCSS: true })) {
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
return isDisplayContents(el) && [...el.children].some((child) => isRendered(child));
|
|
84
93
|
}
|
|
85
94
|
|
|
86
95
|
/**
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
setStampPropBindings,
|
|
31
31
|
} from "@jxsuite/runtime";
|
|
32
32
|
import { resolveAssetRef } from "./asset-resolve";
|
|
33
|
+
import { fillPreviewImages } from "./preview-placeholder";
|
|
33
34
|
import { classifyRenderNode, jxPathSelector, serializeJxPath } from "./path-mapping";
|
|
34
35
|
/* No cycle: `iframe-position.ts` imports only `path-mapping` and a type. It already owns the
|
|
35
36
|
stamped-attribute lookup, escaping included, so a second query built here would be a second
|
|
@@ -994,6 +995,20 @@ export function makeStamper(ctx: PathMapCtx) {
|
|
|
994
995
|
* so they're untouched. Returns a teardown that removes the listener. <img> error events don't
|
|
995
996
|
* bubble, so listen in CAPTURE.
|
|
996
997
|
*/
|
|
998
|
+
/**
|
|
999
|
+
* Whether a failed `<img>` is one a retry could ever fix.
|
|
1000
|
+
*
|
|
1001
|
+
* Read from the AUTHORED attribute, not `img.src`: the property is the RESOLVED url, and an empty
|
|
1002
|
+
* `src=""` resolves to the document's own url, so a check on the property never saw the empty
|
|
1003
|
+
* source it meant to skip. The frame then re-requested `canvas.html` as an image three times, and
|
|
1004
|
+
* its quiescence report counted each failure as a pending retry, holding Studio's idle probe open
|
|
1005
|
+
* for a component drawn without its image prop. A data: URL never errors transiently either.
|
|
1006
|
+
*/
|
|
1007
|
+
export function isRetryableImage(img: HTMLImageElement): boolean {
|
|
1008
|
+
const raw = img.getAttribute("src")?.trim() ?? "";
|
|
1009
|
+
return raw !== "" && !raw.startsWith("data:");
|
|
1010
|
+
}
|
|
1011
|
+
|
|
997
1012
|
export function installCanvasImageRetry(root: HTMLElement, maxAttempts = 3): () => void {
|
|
998
1013
|
const attempts = new WeakMap<HTMLImageElement, number>();
|
|
999
1014
|
const onError = (event: Event): void => {
|
|
@@ -1001,8 +1016,8 @@ export function installCanvasImageRetry(root: HTMLElement, maxAttempts = 3): ()
|
|
|
1001
1016
|
if (!(img instanceof HTMLImageElement)) {
|
|
1002
1017
|
return;
|
|
1003
1018
|
}
|
|
1004
|
-
//
|
|
1005
|
-
if (!img
|
|
1019
|
+
// An empty or data: source is not a request a retry could fix.
|
|
1020
|
+
if (!isRetryableImage(img)) {
|
|
1006
1021
|
return;
|
|
1007
1022
|
}
|
|
1008
1023
|
const attempt = (attempts.get(img) ?? 0) + 1;
|
|
@@ -1127,8 +1142,15 @@ export async function renderResolvedDocument(opts: {
|
|
|
1127
1142
|
// Marks last, and for the same reason: they are resolved by querying stamped attributes, which
|
|
1128
1143
|
// Only exist once the tree the stamper walked is actually in the container.
|
|
1129
1144
|
applyDiffMarks(opts.container, opts.diffMarks ?? null);
|
|
1145
|
+
/* Stylebook specimens are component previews: an image a specimen has no source for draws as a
|
|
1146
|
+
placeholder (`preview-placeholder.ts`). Never in a page's canvas, where an empty image is
|
|
1147
|
+
something its author should see. */
|
|
1148
|
+
const stopPlaceholders = opts.mode === "stylebook" ? fillPreviewImages(opts.container) : null;
|
|
1130
1149
|
return {
|
|
1131
1150
|
ctx: { defs: $defs, docBase: opts.docBase, mapperCtx: opts.mapperCtx, mode: opts.mode },
|
|
1132
|
-
dispose:
|
|
1151
|
+
dispose: () => {
|
|
1152
|
+
stopPlaceholders?.();
|
|
1153
|
+
stop();
|
|
1154
|
+
},
|
|
1133
1155
|
};
|
|
1134
1156
|
}
|