camox 0.76.0 → 0.77.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.
Files changed (54) hide show
  1. package/dist/core/editing/createEditableBlock.js +1077 -1681
  2. package/dist/core/editing/fieldSlot.js +251 -0
  3. package/dist/core/editing/useFieldSlot.js +159 -0
  4. package/dist/core/layoutContext.d.ts +2 -0
  5. package/dist/core/layoutContext.js +27 -1
  6. package/dist/core/lib/artifactTheme.js +43 -6
  7. package/dist/features/canvas/CanvasPageFrame.js +2 -2
  8. package/dist/features/canvas/CanvasPreviewPageSlot.js +1 -1
  9. package/dist/features/canvas/canvasCameraController.js +595 -0
  10. package/dist/features/canvas/useCanvasCamera.js +141 -587
  11. package/dist/features/canvas/useCanvasFrame.js +1 -1
  12. package/dist/features/page/DerivedPageContent.js +4 -8
  13. package/dist/features/preview/components/AssetLightbox.js +6 -65
  14. package/dist/features/preview/components/CanvasPreview.js +2 -2
  15. package/dist/features/preview/components/Frame.js +3 -3
  16. package/dist/features/preview/components/ItemFieldsEditor.js +376 -460
  17. package/dist/features/preview/components/Overlays.js +4 -9
  18. package/dist/features/preview/components/PreviewPanel.js +3 -3
  19. package/dist/features/preview/components/RemovedBlockPlaceholder.js +2 -2
  20. package/dist/features/preview/components/SelectionEditor.js +285 -339
  21. package/dist/features/preview/components/ZoomableImage.js +105 -0
  22. package/dist/features/preview/components/contentFieldSchema.js +3 -1
  23. package/dist/features/preview/components/useFieldWriter.js +30 -51
  24. package/dist/features/preview/components/useRecordView.js +3 -3
  25. package/dist/features/preview/overlayMessages.js +20 -58
  26. package/dist/features/preview/previewStore.js +6 -123
  27. package/dist/features/preview/repeatableItemPath.js +47 -0
  28. package/dist/features/renderer/RendererApp.js +6 -6
  29. package/dist/features/renderer/RendererFrames.js +2 -2
  30. package/dist/features/renderer/rendererHost.js +1 -1
  31. package/dist/features/routes/pageRuntime.d.ts +2 -8
  32. package/dist/features/routes/pageRuntime.js +21 -105
  33. package/dist/features/runtime/pageApp.js +1 -1
  34. package/dist/features/runtime/pageRead.js +105 -0
  35. package/dist/features/runtime/reader.d.ts +25 -0
  36. package/dist/features/runtime/reader.js +142 -0
  37. package/dist/features/runtime/runtime.d.ts +9 -3
  38. package/dist/features/runtime/runtime.js +608 -776
  39. package/dist/features/sdkBundle/buildSdkBundle.d.ts +5 -0
  40. package/dist/features/sdkBundle/buildSdkBundle.js +9 -3
  41. package/dist/features/studio/components/DraftsSheet.js +124 -65
  42. package/dist/features/studio/components/LocalePicker.js +518 -134
  43. package/dist/features/studio/components/ThreadMessages.js +5 -9
  44. package/dist/features/studio/showInCanvas.js +9 -19
  45. package/dist/features/studio/useEditorSelection.js +8 -19
  46. package/dist/lib/locale-path.js +2 -2
  47. package/dist/lib/queries.js +5 -1
  48. package/dist/package.js +1 -1
  49. package/dist/studio.css +1 -1
  50. package/package.json +4 -4
  51. package/skills/camox/references/artifacts.md +7 -0
  52. package/dist/core/hooks/useFieldSelection.js +0 -67
  53. package/dist/core/hooks/useIsEditable.js +0 -21
  54. package/dist/core/hooks/useOverlayMessage.js +0 -70
@@ -0,0 +1,251 @@
1
+ //#region src/core/editing/fieldSlot.ts
2
+ /**
3
+ * Whether a block places the record. Checks `blockId` rather than `layoutId`, because editing
4
+ * events spread a layout editing owner's `layoutId` next to block-owned placements.
5
+ */
6
+ function isBlockPlacement(placement) {
7
+ return "blockId" in placement && placement.blockId != null;
8
+ }
9
+ function copyNested(placement) {
10
+ if (!placement.nested) return {};
11
+ return { nested: {
12
+ fieldName: placement.nested.fieldName,
13
+ recordId: placement.nested.recordId
14
+ } };
15
+ }
16
+ function recordPlacement(placement) {
17
+ if (!isBlockPlacement(placement)) return {
18
+ layoutId: placement.layoutId,
19
+ recordId: placement.recordId,
20
+ ...copyNested(placement)
21
+ };
22
+ return {
23
+ blockId: placement.blockId,
24
+ ...placement.itemId == null ? {} : { itemId: placement.itemId },
25
+ fieldName: placement.fieldName,
26
+ recordId: placement.recordId,
27
+ ...copyNested(placement)
28
+ };
29
+ }
30
+ /** Whether two placements are the same occurrence: same owner, field and record. */
31
+ function samePlacement(a, b) {
32
+ if (a.recordId !== b.recordId) return false;
33
+ if (a.nested?.fieldName !== b.nested?.fieldName) return false;
34
+ if (a.nested?.recordId !== b.nested?.recordId) return false;
35
+ if (!isBlockPlacement(a)) return !isBlockPlacement(b) && a.layoutId === b.layoutId;
36
+ if (!isBlockPlacement(b)) return false;
37
+ return a.blockId === b.blockId && (a.itemId ?? null) === (b.itemId ?? null) && a.fieldName === b.fieldName;
38
+ }
39
+ /** The owner the loose ids name: a placed record's, else a repeatable item's, else the block's. */
40
+ function fieldOwner({ blockId, itemId, placement }) {
41
+ if (placement) return {
42
+ kind: "record",
43
+ placement
44
+ };
45
+ if (itemId != null) return {
46
+ kind: "item",
47
+ blockId,
48
+ itemId
49
+ };
50
+ return {
51
+ kind: "block",
52
+ blockId
53
+ };
54
+ }
55
+ /** The selection that selects the slot. */
56
+ function slotSelection({ owner, name, type }) {
57
+ switch (owner.kind) {
58
+ case "block": return {
59
+ type: "block-field",
60
+ blockId: owner.blockId,
61
+ fieldName: name,
62
+ fieldType: type
63
+ };
64
+ case "item": return {
65
+ type: "item-field",
66
+ blockId: owner.blockId,
67
+ itemId: owner.itemId,
68
+ fieldName: name,
69
+ fieldType: type
70
+ };
71
+ case "record": return {
72
+ type: "record-field",
73
+ ...recordPlacement(owner.placement),
74
+ recordFieldName: name,
75
+ recordFieldType: type
76
+ };
77
+ }
78
+ }
79
+ /** The slot a selection selects, or null when it selects no field. */
80
+ function slotOf(selection) {
81
+ switch (selection?.type) {
82
+ case "block-field": return {
83
+ owner: {
84
+ kind: "block",
85
+ blockId: selection.blockId
86
+ },
87
+ name: selection.fieldName,
88
+ type: selection.fieldType
89
+ };
90
+ case "item-field": return {
91
+ owner: {
92
+ kind: "item",
93
+ blockId: selection.blockId,
94
+ itemId: selection.itemId
95
+ },
96
+ name: selection.fieldName,
97
+ type: selection.fieldType
98
+ };
99
+ case "record-field": return {
100
+ owner: {
101
+ kind: "record",
102
+ placement: recordPlacement(selection)
103
+ },
104
+ name: selection.recordFieldName,
105
+ type: selection.recordFieldType
106
+ };
107
+ default: return null;
108
+ }
109
+ }
110
+ /** The selection that selects the owner itself: a slot's parent. */
111
+ function ownerSelection(owner) {
112
+ switch (owner.kind) {
113
+ case "block": return {
114
+ type: "block",
115
+ blockId: owner.blockId
116
+ };
117
+ case "item": return {
118
+ type: "item",
119
+ blockId: owner.blockId,
120
+ itemId: owner.itemId
121
+ };
122
+ case "record": return {
123
+ type: "record",
124
+ ...recordPlacement(owner.placement)
125
+ };
126
+ }
127
+ }
128
+ /**
129
+ * What selecting the parent of `selection` selects: a field's owner; a placed record's
130
+ * reference field, in the block, item or record that places it; an item's block. A block and a
131
+ * record page's record have none.
132
+ */
133
+ function parentOf(selection) {
134
+ const slot = slotOf(selection);
135
+ if (slot) return ownerSelection(slot.owner);
136
+ switch (selection.type) {
137
+ case "item": return {
138
+ type: "block",
139
+ blockId: selection.blockId
140
+ };
141
+ case "record":
142
+ if (selection.nested) {
143
+ const { nested, ...outer } = recordPlacement(selection);
144
+ return slotSelection({
145
+ owner: {
146
+ kind: "record",
147
+ placement: outer
148
+ },
149
+ name: nested.fieldName,
150
+ type: "Reference"
151
+ });
152
+ }
153
+ if (!isBlockPlacement(selection)) return null;
154
+ return slotSelection({
155
+ owner: fieldOwner(selection),
156
+ name: selection.fieldName,
157
+ type: "Reference"
158
+ });
159
+ default: return null;
160
+ }
161
+ }
162
+ /**
163
+ * Where a record is placed through the reference field `fieldName` of `owner`: directly by a
164
+ * block or repeatable item, or, from a placed record, as the second hop it links.
165
+ */
166
+ function placementThrough(owner, fieldName, recordId) {
167
+ if (owner.kind === "record") return {
168
+ ...recordPlacement(owner.placement),
169
+ nested: {
170
+ fieldName,
171
+ recordId
172
+ }
173
+ };
174
+ return {
175
+ blockId: owner.blockId,
176
+ ...owner.kind === "item" ? { itemId: owner.itemId } : {},
177
+ fieldName,
178
+ recordId
179
+ };
180
+ }
181
+ /**
182
+ * The overlay id of one placement of a record: `block__referenceField__record`, or
183
+ * `block__item__referenceField__record` when a repeatable item places it, or
184
+ * `layout-layoutId__record` when a layout renders it on a record page. Followed by
185
+ * `__recordField__linkedRecord` for a record that record links. Unique per record
186
+ * within a reference list, and distinct from the same record placed elsewhere.
187
+ */
188
+ function placementId(placement) {
189
+ const id = isBlockPlacement(placement) ? `${ownerId(fieldOwner(placement))}__${placement.fieldName}__${placement.recordId}` : `layout-${placement.layoutId}__${placement.recordId}`;
190
+ if (!placement.nested) return id;
191
+ return `${id}__${placement.nested.fieldName}__${placement.nested.recordId}`;
192
+ }
193
+ function ownerId(owner) {
194
+ switch (owner.kind) {
195
+ case "block": return owner.blockId;
196
+ case "item": return `${owner.blockId}__${owner.itemId}`;
197
+ case "record": return placementId(owner.placement);
198
+ }
199
+ }
200
+ /**
201
+ * The slot's overlay id: `block__field`, `block__item__field`, or the record placement's id
202
+ * followed by `__recordField`, so each record of a reference list is its own target.
203
+ */
204
+ function slotId({ owner, name }) {
205
+ return `${ownerId(owner)}__${name}`;
206
+ }
207
+ /**
208
+ * Whether two selections point to the same thing. A field is the same field whatever kind it is
209
+ * read as: its name is unique in its owner.
210
+ */
211
+ function sameTarget(a, b) {
212
+ if (!a || !b) return false;
213
+ const slotA = slotOf(a);
214
+ const slotB = slotOf(b);
215
+ if (slotA || slotB) return !!slotA && !!slotB && slotA.name === slotB.name && sameOwner(slotA.owner, slotB.owner);
216
+ if (a.type === "record" || b.type === "record") return a.type === "record" && b.type === "record" && samePlacement(a, b);
217
+ if (a.type === "item" || b.type === "item") return a.type === "item" && b.type === "item" && a.itemId === b.itemId;
218
+ return a.type === "block" && b.type === "block" && a.blockId === b.blockId;
219
+ }
220
+ function sameOwner(a, b) {
221
+ if (a.kind === "record" || b.kind === "record") return a.kind === "record" && b.kind === "record" && samePlacement(a.placement, b.placement);
222
+ if (a.kind === "item" || b.kind === "item") return a.kind === "item" && b.kind === "item" && a.itemId === b.itemId;
223
+ return a.blockId === b.blockId;
224
+ }
225
+ /** The edit that writes `value` to field `name` of the target. */
226
+ function fieldEdit(target, name, value) {
227
+ switch (target.kind) {
228
+ case "block": return {
229
+ type: "blockContent",
230
+ blockId: target.blockId,
231
+ content: { [name]: value }
232
+ };
233
+ case "item": return {
234
+ type: "itemContent",
235
+ itemId: target.itemId,
236
+ content: { [name]: value }
237
+ };
238
+ case "record":
239
+ const { projectUuid, record } = target;
240
+ return {
241
+ type: "recordField",
242
+ projectUuid,
243
+ record,
244
+ field: name,
245
+ value
246
+ };
247
+ }
248
+ }
249
+
250
+ //#endregion
251
+ export { fieldEdit, fieldOwner, isBlockPlacement, ownerSelection, parentOf, placementId, placementThrough, recordPlacement, samePlacement, sameTarget, slotId, slotOf, slotSelection };
@@ -0,0 +1,159 @@
1
+ import { useIsAuthenticated } from "../../lib/auth.js";
2
+ import { fieldEdit, ownerSelection, sameTarget, slotId, slotSelection } from "./fieldSlot.js";
3
+ import { previewStore, selectIsCanvasShown } from "../../features/preview/previewStore.js";
4
+ import { isOverlayMessage, selectionHoverMessage } from "../../features/preview/overlayMessages.js";
5
+ import { useFrame } from "../../features/preview/components/Frame.js";
6
+ import { usePreviewSelection, usePreviewTargetSelection } from "../../features/preview/previewSelection.js";
7
+ import { useApplyContentEdit } from "./contentEdits.js";
8
+ import * as React from "react";
9
+ import { useSelector } from "@xstate/store-react";
10
+
11
+ //#region src/core/editing/useFieldSlot.tsx
12
+ /** Whether `data` carries every entry of `message`. */
13
+ const carries = (data, message) => Object.entries(message).every(([key, value]) => data[key] === value);
14
+ /**
15
+ * The studio's transport: the preview store, scoped to the page the frame edits, and the
16
+ * sidebar's hover messages posted to the frame's window.
17
+ */
18
+ const studioTransport = {
19
+ useEditing() {
20
+ const isAuthenticated = useIsAuthenticated();
21
+ const isCanvasShown = useSelector(previewStore, selectIsCanvasShown);
22
+ return isAuthenticated && isCanvasShown;
23
+ },
24
+ useSelection: usePreviewTargetSelection,
25
+ useSelect: usePreviewSelection,
26
+ useSidebarHover(target, enabled) {
27
+ const { window: frameWindow } = useFrame();
28
+ const [hovered, setHovered] = React.useState(false);
29
+ const start = target ? JSON.stringify(selectionHoverMessage(target, true)) : null;
30
+ React.useEffect(() => {
31
+ if (!enabled || !frameWindow || !start) return;
32
+ const startMessage = JSON.parse(start);
33
+ const endMessage = {
34
+ ...startMessage,
35
+ type: `${String(startMessage.type)}_END`
36
+ };
37
+ const handleMessage = (event) => {
38
+ if (!isOverlayMessage(event.data)) return;
39
+ const data = event.data;
40
+ if (carries(data, startMessage)) setHovered(true);
41
+ else if (carries(data, endMessage)) setHovered(false);
42
+ };
43
+ frameWindow.addEventListener("message", handleMessage);
44
+ return () => frameWindow.removeEventListener("message", handleMessage);
45
+ }, [
46
+ enabled,
47
+ frameWindow,
48
+ start
49
+ ]);
50
+ return enabled && hovered;
51
+ }
52
+ };
53
+ const SelectionTransportContext = React.createContext(studioTransport);
54
+ function useTransport() {
55
+ return React.use(SelectionTransportContext);
56
+ }
57
+ /** What is selected on the page this frame edits, or null. */
58
+ function useEditingSelection() {
59
+ return useTransport().useSelection();
60
+ }
61
+ /** Selects a target on the page this frame edits. */
62
+ function useSelectTarget() {
63
+ return useTransport().useSelect();
64
+ }
65
+ /**
66
+ * Whether the frame offers editing while the Canvas shows. Only the selected Page's frame does:
67
+ * the others render as the site does, so a click on them only selects the Page. Frames keep
68
+ * the editing runtime either way, so toggling never remounts the page.
69
+ */
70
+ const EditableSurfaceContext = React.createContext(true);
71
+ /**
72
+ * Whether content on this page edits in place: for a member, while the Canvas shows, in the
73
+ * frame that offers editing.
74
+ */
75
+ function useIsEditable() {
76
+ return useTransport().useEditing() && React.use(EditableSurfaceContext);
77
+ }
78
+ /** Whether the sidebar hovers `target`, followed only while `enabled`. */
79
+ function useSidebarHover(target, enabled) {
80
+ return useTransport().useSidebarHover(target, enabled);
81
+ }
82
+ const FieldScopeContext = React.createContext(null);
83
+ /** The scope fields render in here, or null outside any. */
84
+ function useFieldScope() {
85
+ return React.use(FieldScopeContext);
86
+ }
87
+ /** Inline field writes report no failure unless their owner follows them. */
88
+ const ignore = () => {};
89
+ /** How an editable element is outlined: shared content is told apart from the block's own. */
90
+ function overlayMode(owner, synced) {
91
+ if (owner.kind === "record") return "reference";
92
+ return synced ? "synced" : void 0;
93
+ }
94
+ /**
95
+ * What a field kind needs to edit one field where it renders: its value and schema, whether it
96
+ * is hovered, selected and focused, how to select it, how to write it, and its parent. Its
97
+ * overlay props, spread onto the element that stands for the field, outline it and select it on
98
+ * click.
99
+ */
100
+ function useFieldSlot(name, type, { editing = false, enabled = true } = {}) {
101
+ const scope = useFieldScope();
102
+ if (!scope) throw new Error(`The "${name}" field must render inside a Block Component`);
103
+ const transport = useTransport();
104
+ const editable = useIsEditable() && enabled;
105
+ const slot = scope.list ? {
106
+ owner: scope.owner,
107
+ name: scope.list.name,
108
+ type: scope.list.type
109
+ } : {
110
+ owner: scope.owner,
111
+ name,
112
+ type
113
+ };
114
+ const target = slotSelection(slot);
115
+ const selection = transport.useSelection();
116
+ const selectTarget = transport.useSelect();
117
+ const sidebarHovered = transport.useSidebarHover(target, editable);
118
+ const [pointerHovered, setPointerHovered] = React.useState(false);
119
+ const applyEdit = useApplyContentEdit();
120
+ const selected = sameTarget(selection, target);
121
+ const hovered = editable && (pointerHovered || sidebarHovered);
122
+ const focused = editable && (selected || editing);
123
+ const select = () => {
124
+ if (editable) selectTarget(target);
125
+ };
126
+ const write = (value) => {
127
+ if (!scope.target) return;
128
+ const writing = applyEdit(fieldEdit(scope.target, slot.name, value));
129
+ if (scope.onWrite) scope.onWrite(writing);
130
+ else writing.catch(ignore);
131
+ };
132
+ const id = slotId(slot);
133
+ return {
134
+ slot,
135
+ id,
136
+ editable,
137
+ value: scope.content[name],
138
+ schema: scope.schema?.[slot.name],
139
+ hovered,
140
+ selected,
141
+ focused,
142
+ select,
143
+ write,
144
+ /** The owner's selection: what selecting the field's parent selects. */
145
+ parent: ownerSelection(slot.owner),
146
+ overlayProps: {
147
+ "data-camox-field-id": id,
148
+ "data-camox-hovered": hovered || void 0,
149
+ "data-camox-focused": focused || void 0,
150
+ "data-camox-overlay-mode": overlayMode(slot.owner, scope.synced),
151
+ onMouseEnter: () => setPointerHovered(true),
152
+ onMouseLeave: () => setPointerHovered(false),
153
+ onClickCapture: select
154
+ }
155
+ };
156
+ }
157
+
158
+ //#endregion
159
+ export { EditableSurfaceContext, FieldScopeContext, SelectionTransportContext, studioTransport, useEditingSelection, useFieldScope, useFieldSlot, useIsEditable, useSelectTarget, useSidebarHover };
@@ -1,4 +1,6 @@
1
1
  import "../lib/locale-path.js";
2
+ import "./createLayout.js";
3
+ import "./createCollection.js";
2
4
  //#region src/core/layoutContext.d.ts
3
5
  /**
4
6
  * The context every layout `to*` option receives. Each name always means the same thing and is
@@ -1,4 +1,5 @@
1
1
  import { localizedPath } from "../lib/locale-path.js";
2
+ import { asCollectionItem, recordPageValues } from "./recordItem.js";
2
3
 
3
4
  //#region src/core/layoutContext.ts
4
5
  /**
@@ -61,6 +62,31 @@ function buildLayoutContext(layout, source) {
61
62
  ...source.record && { content: source.record.content }
62
63
  };
63
64
  }
65
+ /**
66
+ * A Layout Page's context, from what its loader returned, and the record it renders when it's a
67
+ * Record Page. The server's Page Read and the client's render build it the same way.
68
+ */
69
+ function layoutPageContext(app, layout, source) {
70
+ const item = asCollectionItem(source.result);
71
+ const collection = item && app.getCollectionById(item.collectionId);
72
+ const record = item && collection && {
73
+ item,
74
+ collection,
75
+ values: recordPageValues(collection, item)
76
+ };
77
+ const context = buildLayoutContext(layout, {
78
+ path: source.path,
79
+ locale: source.locale,
80
+ projectName: source.projectName,
81
+ data: source.result?.data,
82
+ params: source.params,
83
+ record: record?.values
84
+ });
85
+ return record ? {
86
+ context,
87
+ record
88
+ } : { context };
89
+ }
64
90
 
65
91
  //#endregion
66
- export { buildLayoutContext, curatedLayoutContext, curatedPageTitle };
92
+ export { buildLayoutContext, curatedLayoutContext, curatedPageTitle, layoutPageContext };
@@ -39,18 +39,55 @@ function readArtifactTheme(frame) {
39
39
  function isThemeAttribute(name) {
40
40
  return name === "class" || name === "style" || !!name?.startsWith("data-");
41
41
  }
42
+ const isStylesheet = (node) => node?.nodeName === "STYLE" || node?.nodeName === "LINK" && /\bstylesheet\b/i.test(node.getAttribute("rel") ?? "");
43
+ /** Whether a change in the page's head may change its stylesheets. */
44
+ function isStylesheetChange(record) {
45
+ if (record.type === "childList") {
46
+ if (isStylesheet(record.target)) return true;
47
+ return [...record.addedNodes, ...record.removedNodes].some(isStylesheet);
48
+ }
49
+ const target = record.type === "characterData" ? record.target.parentNode : record.target;
50
+ return isStylesheet(target);
51
+ }
42
52
  /**
43
- * Calls `onChange` when the `class`, `style` or `data-*` attributes of the frame or one of its
44
- * ancestors change, as these may change the tokens at its position. Returns a disconnect.
53
+ * Calls `onChange` when the tokens at the frame's position may have changed: when the `class`,
54
+ * `style` or `data-*` attributes of the frame or one of its ancestors change, or when the page's
55
+ * stylesheets do, as the studio swaps them when it shows Live or a draft, or a draft's code
56
+ * changes them. A new stylesheet's tokens only resolve once it loaded, so its load counts too.
57
+ * Returns a disconnect.
45
58
  */
46
59
  function observeArtifactTheme(frame, onChange) {
47
- const page = frame.ownerDocument.defaultView;
60
+ const doc = frame.ownerDocument;
61
+ const page = doc.defaultView;
48
62
  if (!page) return () => {};
49
- const observer = new page.MutationObserver((records) => {
63
+ const ancestors = new page.MutationObserver((records) => {
50
64
  if (records.some((record) => isThemeAttribute(record.attributeName))) onChange();
51
65
  });
52
- for (let element = frame; element; element = element.parentElement) observer.observe(element, { attributes: true });
53
- return () => observer.disconnect();
66
+ for (let element = frame; element; element = element.parentElement) ancestors.observe(element, { attributes: true });
67
+ const stylesheets = new page.MutationObserver((records) => {
68
+ if (records.some(isStylesheetChange)) onChange();
69
+ });
70
+ stylesheets.observe(doc.head, {
71
+ childList: true,
72
+ subtree: true,
73
+ characterData: true,
74
+ attributes: true,
75
+ attributeFilter: [
76
+ "href",
77
+ "rel",
78
+ "media",
79
+ "disabled"
80
+ ]
81
+ });
82
+ const onLoad = (event) => {
83
+ if (isStylesheet(event.target)) onChange();
84
+ };
85
+ doc.addEventListener("load", onLoad, true);
86
+ return () => {
87
+ ancestors.disconnect();
88
+ stylesheets.disconnect();
89
+ doc.removeEventListener("load", onLoad, true);
90
+ };
54
91
  }
55
92
 
56
93
  //#endregion
@@ -1,11 +1,11 @@
1
1
  import { ArtifactsRunContext } from "../../core/hooks/useArtifactHost.js";
2
2
  import { useEditingFrameClaim, useEditingOwner } from "../preview/editingFrame.js";
3
- import { EditableSurfaceContext } from "../../core/hooks/useIsEditable.js";
3
+ import { PreviewDocumentContext } from "../runtime/PreviewDocumentContext.js";
4
4
  import { PreviewEditingOwnerContext } from "../preview/previewSelection.js";
5
+ import { EditableSurfaceContext } from "../../core/editing/useFieldSlot.js";
5
6
  import { RendererSlot, useRendererHost } from "../renderer/RendererFrames.js";
6
7
  import { PreviewPreparationContext } from "../preview/previewPreparation.js";
7
8
  import { canvasPageHref, measureCanvasDocument } from "./canvasFrameDocument.js";
8
- import { PreviewDocumentContext } from "../runtime/PreviewDocumentContext.js";
9
9
  import { Overlays } from "../preview/components/Overlays.js";
10
10
  import { PreviewFrameEffects } from "../preview/components/PreviewPanel.js";
11
11
  import { CanvasOverlays } from "./CanvasOverlays.js";
@@ -1,8 +1,8 @@
1
1
  import { useEditingFrameClaim, useEditingOwner } from "../preview/editingFrame.js";
2
+ import { ViewportContinuityContext } from "../preview/viewportContinuity.js";
2
3
  import { CanvasWorkspaceContext } from "./canvasZoom.js";
3
4
  import { usePreviewPageFrame } from "../preview/previewPageFrame.js";
4
5
  import { PreviewPreparationContext } from "../preview/previewPreparation.js";
5
- import { ViewportContinuityContext } from "../preview/viewportContinuity.js";
6
6
  import { useCanvasFrameMode } from "./canvasFrameMode.js";
7
7
  import { CanvasOverlays } from "./CanvasOverlays.js";
8
8
  import { CanvasPageEdge } from "./CanvasPageEdge.js";