@aglyn/besigner-ui 1.0.0-beta.238 → 1.0.0-beta.240
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglyn/besigner-ui",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.240",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://aglyn.com",
|
|
6
6
|
"repository": {
|
|
@@ -25,22 +25,22 @@
|
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@aglyn/aglyn": "1.0.0-beta.
|
|
29
|
-
"@aglyn/aglyn-markdown-editor": "1.0.0-beta.
|
|
30
|
-
"@aglyn/aglyn-node-renderer": "1.0.0-beta.
|
|
31
|
-
"@aglyn/besigner": "1.0.0-beta.
|
|
32
|
-
"@aglyn/shared-data-enums": "1.0.0-beta.
|
|
33
|
-
"@aglyn/shared-data-mdi": "1.0.0-beta.
|
|
34
|
-
"@aglyn/shared-data-types": "1.0.0-beta.
|
|
35
|
-
"@aglyn/shared-svg-icons": "1.0.0-beta.
|
|
36
|
-
"@aglyn/shared-ui-json-editor": "1.0.0-beta.
|
|
37
|
-
"@aglyn/shared-ui-jsx": "1.0.0-beta.
|
|
38
|
-
"@aglyn/shared-ui-jsx-forms": "1.0.0-beta.
|
|
39
|
-
"@aglyn/shared-ui-snackstack": "1.0.0-beta.
|
|
40
|
-
"@aglyn/shared-ui-theme": "1.0.0-beta.
|
|
41
|
-
"@aglyn/shared-util-dom": "1.0.0-beta.
|
|
42
|
-
"@aglyn/shared-util-tools": "1.0.0-beta.
|
|
43
|
-
"@aglyn/shared-util-vendor": "1.0.0-beta.
|
|
28
|
+
"@aglyn/aglyn": "1.0.0-beta.240",
|
|
29
|
+
"@aglyn/aglyn-markdown-editor": "1.0.0-beta.240",
|
|
30
|
+
"@aglyn/aglyn-node-renderer": "1.0.0-beta.240",
|
|
31
|
+
"@aglyn/besigner": "1.0.0-beta.240",
|
|
32
|
+
"@aglyn/shared-data-enums": "1.0.0-beta.240",
|
|
33
|
+
"@aglyn/shared-data-mdi": "1.0.0-beta.240",
|
|
34
|
+
"@aglyn/shared-data-types": "1.0.0-beta.240",
|
|
35
|
+
"@aglyn/shared-svg-icons": "1.0.0-beta.240",
|
|
36
|
+
"@aglyn/shared-ui-json-editor": "1.0.0-beta.240",
|
|
37
|
+
"@aglyn/shared-ui-jsx": "1.0.0-beta.240",
|
|
38
|
+
"@aglyn/shared-ui-jsx-forms": "1.0.0-beta.240",
|
|
39
|
+
"@aglyn/shared-ui-snackstack": "1.0.0-beta.240",
|
|
40
|
+
"@aglyn/shared-ui-theme": "1.0.0-beta.240",
|
|
41
|
+
"@aglyn/shared-util-dom": "1.0.0-beta.240",
|
|
42
|
+
"@aglyn/shared-util-tools": "1.0.0-beta.240",
|
|
43
|
+
"@aglyn/shared-util-vendor": "1.0.0-beta.240",
|
|
44
44
|
"@dnd-kit/core": "^6.3.1",
|
|
45
45
|
"@dnd-kit/utilities": "^3.2.2",
|
|
46
46
|
"@react-aria/utils": "^3.34.1",
|
|
@@ -18,7 +18,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
18
18
|
import { canvas } from "@aglyn/aglyn";
|
|
19
19
|
// By path: the repeat rules and the source registry stay out of every
|
|
20
20
|
// `@aglyn/aglyn` barrel, so nothing on a published page can pull them in.
|
|
21
|
-
import { expandRepeatables, repeatedRecords, repeatScope, substituteRecordTokens } from "@aglyn/aglyn/app-utils/expand-repeatables";
|
|
21
|
+
import { expandRepeatables, REPEAT_NODE_ID_PREFIX, repeatedRecords, repeatScope, substituteRecordTokens } from "@aglyn/aglyn/app-utils/expand-repeatables";
|
|
22
22
|
import { repeatSourceOf } from "@aglyn/aglyn/app-utils/repeat-sources";
|
|
23
23
|
import { useContext, useEffect, useMemo, useSyncExternalStore } from "react";
|
|
24
24
|
import { RepeatRowsContext, repeatRowsKey } from "../contexts/repeat-rows-context.js";
|
|
@@ -153,10 +153,8 @@ const noVersion = ()=>0;
|
|
|
153
153
|
const recordCount = (_ref = preview == null ? void 0 : preview.records.length) != null ? _ref : 0;
|
|
154
154
|
const key = preview ? `${$id}\n${preview.scope}\n${JSON.stringify(preview.records)}` : '';
|
|
155
155
|
return useMemo(()=>{
|
|
156
|
-
var _preview_datasetsByKey, _ref
|
|
157
|
-
var _expanded_holderId, _expanded_$id
|
|
158
|
-
// record's copies are the ones the real children already draw.
|
|
159
|
-
_expanded_$id1;
|
|
156
|
+
var _preview_datasetsByKey, _ref;
|
|
157
|
+
var _expanded_holderId, _expanded_$id;
|
|
160
158
|
if (!$id || !preview || recordCount < 2) return [];
|
|
161
159
|
const snapshot = subtreeSnapshot($id);
|
|
162
160
|
if (!snapshot[$id]) return [];
|
|
@@ -174,7 +172,11 @@ const noVersion = ()=>0;
|
|
|
174
172
|
]
|
|
175
173
|
};
|
|
176
174
|
const expanded = expandRepeatables(snapshot, _extends({}, (_preview_datasetsByKey = preview.datasetsByKey) != null ? _preview_datasetsByKey : {}));
|
|
177
|
-
|
|
175
|
+
// The first record's copies are the ones the real nodes already draw. By
|
|
176
|
+
// id, not by count: a copy leaves out an element whose binding its record
|
|
177
|
+
// leaves empty (AGL-3616), so records need not copy alike.
|
|
178
|
+
const firstCopy = `${REPEAT_NODE_ID_PREFIX}${$id}__0__`;
|
|
179
|
+
const copyIds = ((_ref = preview.scope === 'self' ? (_expanded_holderId = expanded[holderId]) == null ? void 0 : _expanded_holderId.nodes : (_expanded_$id = expanded[$id]) == null ? void 0 : _expanded_$id.nodes) != null ? _ref : []).filter((id)=>!id.startsWith(firstCopy));
|
|
178
180
|
return copyIds.map((id)=>denormalize(expanded, id)).filter(Boolean);
|
|
179
181
|
// `key` stands in for the records and the node; both are snapshots.
|
|
180
182
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../libs/besigner/feature/designer/src/lib/hooks/use-repeat-preview.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { canvas } from '@aglyn/aglyn'\n// By path: the repeat rules and the source registry stay out of every\n// `@aglyn/aglyn` barrel, so nothing on a published page can pull them in.\nimport {\n expandRepeatables,\n type RepeatableDataset,\n type RepeatScope,\n repeatedRecords,\n repeatScope,\n substituteRecordTokens,\n} from '@aglyn/aglyn/app-utils/expand-repeatables'\nimport { repeatSourceOf } from '@aglyn/aglyn/app-utils/repeat-sources'\nimport { useContext, useEffect, useMemo, useSyncExternalStore } from 'react'\nimport {\n RepeatRowsContext,\n repeatRowsKey,\n} from '../contexts/repeat-rows-context'\nimport { RepeatRecordContext } from '../contexts/repeat-record-context'\n\nconst subscribeToNothing = () => () => undefined\nconst noVersion = () => 0\n\n/** What a repeating element draws on the canvas. */\nexport interface RepeatPreview {\n /** What authors call the thing being repeated, for the badge. */\n label: string\n /**\n * The records this element renders, in order, bounded exactly as the\n * published page's composition bounds them. EMPTY when nothing matches: the\n * published page then renders no copy at all (AGL-3496), while the canvas\n * keeps the template on screen to be edited and the badge says so.\n */\n records: ReadonlyArray<Record<string, unknown>>\n /**\n * The key names nothing this site can read — a deleted or unshared dataset.\n * `records` is empty, and the published page renders no copy.\n */\n missing?: boolean\n /** Which scope applies, so the canvas draws what the page will build. */\n scope: RepeatScope\n /** The repeated rows, for the token substitution's reference hops. */\n dataset?: RepeatableDataset\n /** Every key the rows answered under, for a one-hop reference (AGL-180). */\n datasetsByKey?: Readonly<Record<string, RepeatableDataset>>\n}\n\n/**\n * The rows a canvas element repeats over, and the copies it therefore draws\n * (AGL-3111).\n *\n * The designer cannot read them itself — it renders inside a plugin sandbox,\n * and which documents hold them is the source's business — so this HOLDS the\n * request and reads back whatever the host app's reader filed. Without a\n * provider, before an answer, or after a failed read, it returns `undefined`\n * and the element draws its template once with no badge: nothing is known.\n *\n * An answer of no rows — a filter that matches nothing, an empty dataset, a\n * key that names nothing (`missing`) — IS known, and is returned with no\n * records rather than as `undefined`: the published page renders that repeat\n * zero times (AGL-3496), and an author looking at their template on the\n * canvas has to be told so.\n *\n * The records are {@link repeatedRecords}, not a second reading of the same\n * props: the filter, the sort, the limit and `REPEAT_MAX_RECORDS` are applied\n * by the function the page applies them with, so a copy on the canvas is a\n * copy a visitor gets.\n */\nexport function useRepeatPreview(node: unknown): RepeatPreview | undefined {\n const source = useContext(RepeatRowsContext)\n const props = (node as { props?: Record<string, unknown> })?.props\n // MobX props are observable and change in place; their JSON keys the memos\n // below, the way every other render copy in the canvas is keyed.\n const propsJson = JSON.stringify(props ?? null)\n const request = useMemo(() => {\n const repeat = source ? repeatSourceOf({ props }) : undefined\n return repeat\n ? { sourceId: repeat.source.id, key: repeat.key, label: repeat.source.label }\n : undefined\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [source, props, propsJson])\n\n const key = request ? repeatRowsKey(request) : ''\n useEffect(() => {\n if (!source || !request) return undefined\n return source.retain(request)\n // `key` stands in for `request`: the same source and key keeps the read\n // it already holds rather than dropping and re-taking it.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [source, key])\n\n const version = useSyncExternalStore(\n source && request ? source.subscribe : subscribeToNothing,\n source ? source.getVersion : noVersion,\n noVersion,\n )\n\n return useMemo(() => {\n if (!source || !request) return undefined\n const answer = source.get(key)\n if (answer?.status === 'missing') {\n return {\n label: request.label,\n records: [],\n scope: repeatScope(node),\n missing: true,\n }\n }\n if (answer?.status !== 'ready') return undefined\n const dataset = answer.rowsByKey[request.key]\n const records = repeatedRecords({ props }, dataset)\n return {\n label: answer.label,\n records,\n scope: repeatScope(node),\n dataset,\n datasetsByKey: answer.rowsByKey,\n }\n // `version` carries a new answer in; the body reads it through `get`.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [source, request, key, version, node, props, propsJson])\n}\n\n/**\n * The stored subtree under `rootId`, as a plain snapshot.\n *\n * Canvas nodes are MobX observables the expansion must not read through — it\n * copies props by reference into the copies it builds, and an observable read\n * outside a reaction is both a stale value and a missing dependency.\n */\nfunction subtreeSnapshot(rootId: string): Record<string, any> {\n const nodes = (canvas.toJSON().nodes ?? {}) as Record<string, any>\n const snapshot: Record<string, any> = {}\n const walk = (id: string) => {\n const node = nodes[id]\n if (!node || snapshot[id]) return\n snapshot[id] = JSON.parse(JSON.stringify(node))\n for (const childId of (node.nodes ?? []) as string[]) walk(childId)\n }\n walk(rootId)\n return snapshot\n}\n\n/**\n * The copies a repeating element draws BESIDE the one an author edits\n * (AGL-3111): records 2..n, as denormalized trees ready for an inert render.\n *\n * The first record is deliberately missing. It is drawn by the element's own\n * children — the real canvas nodes, selectable and draggable, with that\n * record's values laid over their render copies through\n * {@link RepeatRecordContext}. That is what makes editing the template edit\n * every copy: there is one template, it is on the canvas, and the copies are\n * pictures of it.\n *\n * Built by running the published page's own {@link expandRepeatables} over a\n * snapshot of this subtree, so the canvas cannot draw a shape the page would\n * not compose.\n */\nexport function useRepeatCopies(\n node: { $id?: string; parentId?: string } | null | undefined,\n preview: RepeatPreview | undefined,\n): any[] {\n const $id = node?.$id\n const recordCount = preview?.records.length ?? 0\n const key = preview\n ? `${$id}\\n${preview.scope}\\n${JSON.stringify(preview.records)}`\n : ''\n return useMemo(() => {\n if (!$id || !preview || recordCount < 2) return []\n const snapshot = subtreeSnapshot($id)\n if (!snapshot[$id]) return []\n // A parent the copies can take their place in. The real parent is not in\n // this snapshot — only the subtree is — so a self-scoped repeat is given\n // a holder that lists it, which is all the expansion reads a parent for.\n const holderId = `${$id}__repeat-preview-parent`\n snapshot[$id] = { ...snapshot[$id], parentId: holderId }\n snapshot[holderId] = { $id: holderId, nodes: [$id] }\n const expanded = expandRepeatables(snapshot as never, {\n // Under the key the node stores, which is the key the expansion looks\n // the rows up by.\n ...(preview.datasetsByKey ?? {}),\n } as never) as Record<string, any>\n const copyIds =\n preview.scope === 'self'\n ? (expanded[holderId]?.nodes ?? []).slice(1)\n : (expanded[$id]?.nodes ?? []).slice(\n // One template's worth of children per record: the first\n // record's copies are the ones the real children already draw.\n (expanded[$id]?.nodes ?? []).length / recordCount,\n )\n return copyIds\n .map((id: string) => denormalize(expanded, id))\n .filter(Boolean)\n // `key` stands in for the records and the node; both are snapshots.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [$id, key, recordCount])\n}\n\n/** Nested-object form of an expanded map, which is what `Stem` walks. */\nfunction denormalize(\n nodes: Record<string, any>,\n rootId: string,\n seen: Set<string> = new Set(),\n): any {\n const node = nodes?.[rootId]\n if (!node || seen.has(rootId)) return undefined\n seen.add(rootId)\n return {\n ...node,\n children: ((node.nodes ?? []) as string[])\n .map((childId) => denormalize(nodes, childId, seen))\n .filter(Boolean),\n }\n}\n\n/**\n * A node's render copy with the record its subtree is drawing laid over it\n * (AGL-3111).\n *\n * Inside a repeating element the FIRST record's values belong on the real\n * canvas nodes, so the element an author edits reads as the first copy rather\n * than as a row of raw `{{item.*}}` tokens beside the copies that resolved\n * them. Render copies only — the node keeps its tokens, and every save writes\n * the template.\n */\nexport function useNodeWithRepeatRecord<N>(node: N): N {\n const context = useContext(RepeatRecordContext)\n const rendered = node as { props?: Record<string, unknown> } | null | undefined\n const propsJson = context ? JSON.stringify(rendered?.props ?? null) : ''\n return useMemo(() => {\n if (!context || !rendered?.props) return node\n const props = substituteRecordTokens({ ...rendered.props }, context)\n return { ...rendered, props } as N\n // Observable props: the JSON string keys the copy, as everywhere else.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [node, rendered, context, propsJson])\n}\n\nexport default useRepeatPreview\n"],"names":["canvas","expandRepeatables","repeatedRecords","repeatScope","substituteRecordTokens","repeatSourceOf","useContext","useEffect","useMemo","useSyncExternalStore","RepeatRowsContext","repeatRowsKey","RepeatRecordContext","subscribeToNothing","undefined","noVersion","useRepeatPreview","node","source","props","propsJson","JSON","stringify","request","repeat","sourceId","id","key","label","retain","version","subscribe","getVersion","answer","get","status","records","scope","missing","dataset","rowsByKey","datasetsByKey","subtreeSnapshot","rootId","nodes","toJSON","snapshot","walk","parse","childId","useRepeatCopies","preview","$id","recordCount","length","expanded","holderId","parentId","copyIds","slice","map","denormalize","filter","Boolean","seen","Set","has","add","children","useNodeWithRepeatRecord","context","rendered"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/besigner/feature/designer/src/lib/hooks/use-repeat-preview.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { canvas } from '@aglyn/aglyn'\n// By path: the repeat rules and the source registry stay out of every\n// `@aglyn/aglyn` barrel, so nothing on a published page can pull them in.\nimport {\n expandRepeatables,\n REPEAT_NODE_ID_PREFIX,\n type RepeatableDataset,\n type RepeatScope,\n repeatedRecords,\n repeatScope,\n substituteRecordTokens,\n} from '@aglyn/aglyn/app-utils/expand-repeatables'\nimport { repeatSourceOf } from '@aglyn/aglyn/app-utils/repeat-sources'\nimport { useContext, useEffect, useMemo, useSyncExternalStore } from 'react'\nimport {\n RepeatRowsContext,\n repeatRowsKey,\n} from '../contexts/repeat-rows-context'\nimport { RepeatRecordContext } from '../contexts/repeat-record-context'\n\nconst subscribeToNothing = () => () => undefined\nconst noVersion = () => 0\n\n/** What a repeating element draws on the canvas. */\nexport interface RepeatPreview {\n /** What authors call the thing being repeated, for the badge. */\n label: string\n /**\n * The records this element renders, in order, bounded exactly as the\n * published page's composition bounds them. EMPTY when nothing matches: the\n * published page then renders no copy at all (AGL-3496), while the canvas\n * keeps the template on screen to be edited and the badge says so.\n */\n records: ReadonlyArray<Record<string, unknown>>\n /**\n * The key names nothing this site can read — a deleted or unshared dataset.\n * `records` is empty, and the published page renders no copy.\n */\n missing?: boolean\n /** Which scope applies, so the canvas draws what the page will build. */\n scope: RepeatScope\n /** The repeated rows, for the token substitution's reference hops. */\n dataset?: RepeatableDataset\n /** Every key the rows answered under, for a one-hop reference (AGL-180). */\n datasetsByKey?: Readonly<Record<string, RepeatableDataset>>\n}\n\n/**\n * The rows a canvas element repeats over, and the copies it therefore draws\n * (AGL-3111).\n *\n * The designer cannot read them itself — it renders inside a plugin sandbox,\n * and which documents hold them is the source's business — so this HOLDS the\n * request and reads back whatever the host app's reader filed. Without a\n * provider, before an answer, or after a failed read, it returns `undefined`\n * and the element draws its template once with no badge: nothing is known.\n *\n * An answer of no rows — a filter that matches nothing, an empty dataset, a\n * key that names nothing (`missing`) — IS known, and is returned with no\n * records rather than as `undefined`: the published page renders that repeat\n * zero times (AGL-3496), and an author looking at their template on the\n * canvas has to be told so.\n *\n * The records are {@link repeatedRecords}, not a second reading of the same\n * props: the filter, the sort, the limit and `REPEAT_MAX_RECORDS` are applied\n * by the function the page applies them with, so a copy on the canvas is a\n * copy a visitor gets.\n */\nexport function useRepeatPreview(node: unknown): RepeatPreview | undefined {\n const source = useContext(RepeatRowsContext)\n const props = (node as { props?: Record<string, unknown> })?.props\n // MobX props are observable and change in place; their JSON keys the memos\n // below, the way every other render copy in the canvas is keyed.\n const propsJson = JSON.stringify(props ?? null)\n const request = useMemo(() => {\n const repeat = source ? repeatSourceOf({ props }) : undefined\n return repeat\n ? { sourceId: repeat.source.id, key: repeat.key, label: repeat.source.label }\n : undefined\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [source, props, propsJson])\n\n const key = request ? repeatRowsKey(request) : ''\n useEffect(() => {\n if (!source || !request) return undefined\n return source.retain(request)\n // `key` stands in for `request`: the same source and key keeps the read\n // it already holds rather than dropping and re-taking it.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [source, key])\n\n const version = useSyncExternalStore(\n source && request ? source.subscribe : subscribeToNothing,\n source ? source.getVersion : noVersion,\n noVersion,\n )\n\n return useMemo(() => {\n if (!source || !request) return undefined\n const answer = source.get(key)\n if (answer?.status === 'missing') {\n return {\n label: request.label,\n records: [],\n scope: repeatScope(node),\n missing: true,\n }\n }\n if (answer?.status !== 'ready') return undefined\n const dataset = answer.rowsByKey[request.key]\n const records = repeatedRecords({ props }, dataset)\n return {\n label: answer.label,\n records,\n scope: repeatScope(node),\n dataset,\n datasetsByKey: answer.rowsByKey,\n }\n // `version` carries a new answer in; the body reads it through `get`.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [source, request, key, version, node, props, propsJson])\n}\n\n/**\n * The stored subtree under `rootId`, as a plain snapshot.\n *\n * Canvas nodes are MobX observables the expansion must not read through — it\n * copies props by reference into the copies it builds, and an observable read\n * outside a reaction is both a stale value and a missing dependency.\n */\nfunction subtreeSnapshot(rootId: string): Record<string, any> {\n const nodes = (canvas.toJSON().nodes ?? {}) as Record<string, any>\n const snapshot: Record<string, any> = {}\n const walk = (id: string) => {\n const node = nodes[id]\n if (!node || snapshot[id]) return\n snapshot[id] = JSON.parse(JSON.stringify(node))\n for (const childId of (node.nodes ?? []) as string[]) walk(childId)\n }\n walk(rootId)\n return snapshot\n}\n\n/**\n * The copies a repeating element draws BESIDE the one an author edits\n * (AGL-3111): records 2..n, as denormalized trees ready for an inert render.\n *\n * The first record is deliberately missing. It is drawn by the element's own\n * children — the real canvas nodes, selectable and draggable, with that\n * record's values laid over their render copies through\n * {@link RepeatRecordContext}. That is what makes editing the template edit\n * every copy: there is one template, it is on the canvas, and the copies are\n * pictures of it.\n *\n * Built by running the published page's own {@link expandRepeatables} over a\n * snapshot of this subtree, so the canvas cannot draw a shape the page would\n * not compose.\n */\nexport function useRepeatCopies(\n node: { $id?: string; parentId?: string } | null | undefined,\n preview: RepeatPreview | undefined,\n): any[] {\n const $id = node?.$id\n const recordCount = preview?.records.length ?? 0\n const key = preview\n ? `${$id}\\n${preview.scope}\\n${JSON.stringify(preview.records)}`\n : ''\n return useMemo(() => {\n if (!$id || !preview || recordCount < 2) return []\n const snapshot = subtreeSnapshot($id)\n if (!snapshot[$id]) return []\n // A parent the copies can take their place in. The real parent is not in\n // this snapshot — only the subtree is — so a self-scoped repeat is given\n // a holder that lists it, which is all the expansion reads a parent for.\n const holderId = `${$id}__repeat-preview-parent`\n snapshot[$id] = { ...snapshot[$id], parentId: holderId }\n snapshot[holderId] = { $id: holderId, nodes: [$id] }\n const expanded = expandRepeatables(snapshot as never, {\n // Under the key the node stores, which is the key the expansion looks\n // the rows up by.\n ...(preview.datasetsByKey ?? {}),\n } as never) as Record<string, any>\n // The first record's copies are the ones the real nodes already draw. By\n // id, not by count: a copy leaves out an element whose binding its record\n // leaves empty (AGL-3616), so records need not copy alike.\n const firstCopy = `${REPEAT_NODE_ID_PREFIX}${$id}__0__`\n const copyIds = (\n (preview.scope === 'self'\n ? expanded[holderId]?.nodes\n : expanded[$id]?.nodes) ?? []\n ).filter((id: string) => !id.startsWith(firstCopy))\n return copyIds\n .map((id: string) => denormalize(expanded, id))\n .filter(Boolean)\n // `key` stands in for the records and the node; both are snapshots.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [$id, key, recordCount])\n}\n\n/** Nested-object form of an expanded map, which is what `Stem` walks. */\nfunction denormalize(\n nodes: Record<string, any>,\n rootId: string,\n seen: Set<string> = new Set(),\n): any {\n const node = nodes?.[rootId]\n if (!node || seen.has(rootId)) return undefined\n seen.add(rootId)\n return {\n ...node,\n children: ((node.nodes ?? []) as string[])\n .map((childId) => denormalize(nodes, childId, seen))\n .filter(Boolean),\n }\n}\n\n/**\n * A node's render copy with the record its subtree is drawing laid over it\n * (AGL-3111).\n *\n * Inside a repeating element the FIRST record's values belong on the real\n * canvas nodes, so the element an author edits reads as the first copy rather\n * than as a row of raw `{{item.*}}` tokens beside the copies that resolved\n * them. Render copies only — the node keeps its tokens, and every save writes\n * the template.\n */\nexport function useNodeWithRepeatRecord<N>(node: N): N {\n const context = useContext(RepeatRecordContext)\n const rendered = node as { props?: Record<string, unknown> } | null | undefined\n const propsJson = context ? JSON.stringify(rendered?.props ?? null) : ''\n return useMemo(() => {\n if (!context || !rendered?.props) return node\n const props = substituteRecordTokens({ ...rendered.props }, context)\n return { ...rendered, props } as N\n // Observable props: the JSON string keys the copy, as everywhere else.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [node, rendered, context, propsJson])\n}\n\nexport default useRepeatPreview\n"],"names":["canvas","expandRepeatables","REPEAT_NODE_ID_PREFIX","repeatedRecords","repeatScope","substituteRecordTokens","repeatSourceOf","useContext","useEffect","useMemo","useSyncExternalStore","RepeatRowsContext","repeatRowsKey","RepeatRecordContext","subscribeToNothing","undefined","noVersion","useRepeatPreview","node","source","props","propsJson","JSON","stringify","request","repeat","sourceId","id","key","label","retain","version","subscribe","getVersion","answer","get","status","records","scope","missing","dataset","rowsByKey","datasetsByKey","subtreeSnapshot","rootId","nodes","toJSON","snapshot","walk","parse","childId","useRepeatCopies","preview","$id","recordCount","length","expanded","holderId","parentId","firstCopy","copyIds","filter","startsWith","map","denormalize","Boolean","seen","Set","has","add","children","useNodeWithRepeatRecord","context","rendered"],"mappings":"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"}
|