@composy/layout-runtime 0.0.1 → 0.0.2

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 (42) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +101 -17
  3. package/dist/index.js +68 -20
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.min.js +191 -1
  6. package/dist/index.min.js.map +1 -0
  7. package/es/.dts-output-manifest.json +1 -0
  8. package/es/.dts.tsbuildinfo +1 -0
  9. package/es/index.d.ts +0 -1
  10. package/es/index.js +1 -1
  11. package/es/material-renderer.d.ts +0 -1
  12. package/es/material-renderer.js +40 -7
  13. package/es/material-renderer.js.map +1 -1
  14. package/es/runtime.d.ts +0 -1
  15. package/es/runtime.js +27 -12
  16. package/es/runtime.js.map +1 -1
  17. package/es/styles.d.ts +0 -1
  18. package/es/styles.js +3 -3
  19. package/es/styles.js.map +1 -1
  20. package/esm/index.js +13 -0
  21. package/esm/material-renderer.js +102 -0
  22. package/esm/material-renderer.js.map +1 -0
  23. package/esm/runtime.js +171 -0
  24. package/esm/runtime.js.map +1 -0
  25. package/esm/styles.js +39 -0
  26. package/esm/styles.js.map +1 -0
  27. package/lib/index.cjs +1 -1
  28. package/lib/{index.d.ts → index.d.cts} +0 -1
  29. package/lib/material-renderer.cjs +40 -7
  30. package/lib/material-renderer.cjs.map +1 -1
  31. package/lib/{material-renderer.d.ts → material-renderer.d.cts} +0 -1
  32. package/lib/runtime.cjs +27 -12
  33. package/lib/runtime.cjs.map +1 -1
  34. package/lib/{runtime.d.ts → runtime.d.cts} +0 -1
  35. package/lib/styles.cjs +3 -3
  36. package/lib/styles.cjs.map +1 -1
  37. package/lib/{styles.d.ts → styles.d.cts} +0 -1
  38. package/package.json +13 -7
  39. package/es/index.d.ts.map +0 -1
  40. package/es/material-renderer.d.ts.map +0 -1
  41. package/es/runtime.d.ts.map +0 -1
  42. package/es/styles.d.ts.map +0 -1
@@ -0,0 +1,102 @@
1
+ /*!
2
+ * ***********************************
3
+ * @composy/layout-runtime v0.0.1 *
4
+ * Built with rollup *
5
+ * Build time: 2024-10-12 17:44:18 *
6
+ * Build mode: production *
7
+ * Minified: No *
8
+ * ***********************************
9
+ */
10
+ import { createMaterialRegistry } from "@composy/layout-core";
11
+ //#region src/material-renderer.ts
12
+ function normalizeMaterialSource(source) {
13
+ if (!source) return void 0;
14
+ if (Array.isArray(source)) return createMaterialRegistry(source);
15
+ return source;
16
+ }
17
+ function renderRuntimeMaterial(context, materials) {
18
+ const material = materials?.get(context.item.type);
19
+ const renderer = material?.renderers?.webcomponent ?? material?.renderers?.html;
20
+ if (!material || !renderer) return void 0;
21
+ const host = context.shell.ownerDocument;
22
+ let node;
23
+ if (renderer.tagName) node = host.createElement(renderer.tagName);
24
+ else if (renderer.render) {
25
+ const rendered = renderer.render({
26
+ item: context.item,
27
+ placement: context.placement,
28
+ breakpointId: context.breakpointId
29
+ });
30
+ if (isDomNode(rendered)) node = rendered;
31
+ else if (typeof rendered === "string") {
32
+ const wrapper = host.createElement("div");
33
+ wrapper.innerHTML = sanitizeMarkup(host, rendered);
34
+ node = wrapper;
35
+ }
36
+ }
37
+ if (!node) return void 0;
38
+ const appliedProps = /* @__PURE__ */ new Set();
39
+ const applyProps = (item) => {
40
+ if (!isElement(node, host)) return;
41
+ const nextProps = item.props ?? {};
42
+ for (const key of appliedProps) {
43
+ if (key in nextProps) continue;
44
+ node.removeAttribute(key);
45
+ delete node.dataset[`prop${toDataKey(key)}`];
46
+ appliedProps.delete(key);
47
+ }
48
+ for (const [key, value] of Object.entries(nextProps)) {
49
+ appliedProps.add(key);
50
+ try {
51
+ node[key] = value;
52
+ } catch {}
53
+ if (value === null || value === void 0 || typeof value === "object" || typeof value === "function") {
54
+ if (value !== null && value !== void 0) node.dataset[`prop${toDataKey(key)}`] = JSON.stringify(value);
55
+ } else node.setAttribute(key, String(value));
56
+ }
57
+ };
58
+ applyProps(context.item);
59
+ context.shell.replaceChildren(node);
60
+ return {
61
+ update(next) {
62
+ applyProps(next.item);
63
+ },
64
+ destroy() {
65
+ if (node?.parentNode === context.shell) context.shell.removeChild(node);
66
+ }
67
+ };
68
+ }
69
+ function toDataKey(value) {
70
+ return value.replace(/[^a-zA-Z0-9]/g, "");
71
+ }
72
+ function isDomNode(value) {
73
+ return Boolean(value && typeof value === "object" && "nodeType" in value && typeof value.cloneNode === "function");
74
+ }
75
+ function isElement(value, document) {
76
+ if (!value || value.nodeType !== 1) return false;
77
+ const ElementConstructor = document.defaultView?.HTMLElement;
78
+ return ElementConstructor ? value instanceof ElementConstructor : typeof value.setAttribute === "function";
79
+ }
80
+ function sanitizeMarkup(document, value) {
81
+ const template = document.createElement("template");
82
+ template.innerHTML = value;
83
+ template.content.querySelectorAll("script,style,iframe,object,embed,foreignObject").forEach((node) => node.remove());
84
+ template.content.querySelectorAll("*").forEach((element) => {
85
+ for (const attribute of Array.from(element.attributes)) {
86
+ const name = attribute.name.toLowerCase();
87
+ const content = attribute.value.trim().toLowerCase();
88
+ if (name.startsWith("on") || [
89
+ "href",
90
+ "src",
91
+ "xlink:href",
92
+ "formaction",
93
+ "srcdoc"
94
+ ].includes(name) && /^(?:javascript:|data:text\/html)/i.test(content)) element.removeAttribute(attribute.name);
95
+ }
96
+ });
97
+ return template.innerHTML;
98
+ }
99
+ //#endregion
100
+ export { normalizeMaterialSource, renderRuntimeMaterial };
101
+ /*! End of @composy/layout-runtime | Powered by @composy/builder */
102
+ //# sourceMappingURL=material-renderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"material-renderer.js","names":[],"sources":["../src/material-renderer.ts"],"sourcesContent":["import {\n createMaterialRegistry,\n type LayoutNode,\n type MaterialDefinition,\n type MaterialRegistry,\n} from '@composy/layout-core'\nimport type { LayoutRenderContext, LayoutRenderHandle } from './runtime'\n\nexport type RuntimeMaterialSource =\n MaterialRegistry | readonly MaterialDefinition[] | undefined\n\nexport function normalizeMaterialSource(\n source: RuntimeMaterialSource\n): MaterialRegistry | undefined {\n if (!source) return undefined\n if (Array.isArray(source)) return createMaterialRegistry(source)\n return source as MaterialRegistry\n}\n\nexport function renderRuntimeMaterial(\n context: LayoutRenderContext,\n materials: MaterialRegistry | undefined\n): LayoutRenderHandle | undefined {\n const material = materials?.get(context.item.type)\n const renderer =\n material?.renderers?.webcomponent ?? material?.renderers?.html\n if (!material || !renderer) return undefined\n\n const host = context.shell.ownerDocument\n let node: Node | undefined\n if (renderer.tagName) {\n node = host.createElement(renderer.tagName)\n } else if (renderer.render) {\n const rendered = renderer.render({\n item: context.item,\n placement: context.placement,\n breakpointId: context.breakpointId,\n })\n if (isDomNode(rendered)) node = rendered\n else if (typeof rendered === 'string') {\n const wrapper = host.createElement('div')\n wrapper.innerHTML = sanitizeMarkup(host, rendered)\n node = wrapper\n }\n }\n if (!node) return undefined\n\n const appliedProps = new Set<string>()\n const applyProps = (item: LayoutNode): void => {\n if (!isElement(node, host)) return\n const nextProps = item.props ?? {}\n for (const key of appliedProps) {\n if (key in nextProps) continue\n node.removeAttribute(key)\n delete node.dataset[`prop${toDataKey(key)}`]\n appliedProps.delete(key)\n }\n for (const [key, value] of Object.entries(nextProps)) {\n appliedProps.add(key)\n try {\n ;(node as unknown as Record<string, unknown>)[key] = value\n } catch {\n // Some custom elements expose readonly properties. The attribute is a\n // useful fallback for string-like values.\n }\n if (\n value === null ||\n value === undefined ||\n typeof value === 'object' ||\n typeof value === 'function'\n ) {\n if (value !== null && value !== undefined)\n node.dataset[`prop${toDataKey(key)}`] = JSON.stringify(value)\n } else node.setAttribute(key, String(value))\n }\n }\n applyProps(context.item)\n context.shell.replaceChildren(node)\n return {\n update(next) {\n applyProps(next.item)\n },\n destroy() {\n if (node?.parentNode === context.shell) context.shell.removeChild(node)\n },\n }\n}\n\nfunction toDataKey(value: string): string {\n return value.replace(/[^a-zA-Z0-9]/g, '')\n}\n\nfunction isDomNode(value: unknown): value is Node {\n return Boolean(\n value &&\n typeof value === 'object' &&\n 'nodeType' in value &&\n typeof (value as { cloneNode?: unknown }).cloneNode === 'function'\n )\n}\n\nfunction isElement(\n value: Node | undefined,\n document: Document\n): value is HTMLElement {\n if (!value || value.nodeType !== 1) return false\n const ElementConstructor = document.defaultView?.HTMLElement\n return ElementConstructor\n ? value instanceof ElementConstructor\n : typeof (value as Element).setAttribute === 'function'\n}\n\nfunction sanitizeMarkup(document: Document, value: string): string {\n const template = document.createElement('template')\n template.innerHTML = value\n template.content\n .querySelectorAll('script,style,iframe,object,embed,foreignObject')\n .forEach(node => node.remove())\n template.content.querySelectorAll<HTMLElement>('*').forEach(element => {\n for (const attribute of Array.from(element.attributes)) {\n const name = attribute.name.toLowerCase()\n const content = attribute.value.trim().toLowerCase()\n if (\n name.startsWith('on') ||\n (['href', 'src', 'xlink:href', 'formaction', 'srcdoc'].includes(name) &&\n /^(?:javascript:|data:text\\/html)/i.test(content))\n )\n element.removeAttribute(attribute.name)\n }\n })\n return template.innerHTML\n}\n"],"mappings":"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"}
package/esm/runtime.js ADDED
@@ -0,0 +1,171 @@
1
+ /*!
2
+ * ***********************************
3
+ * @composy/layout-runtime v0.0.1 *
4
+ * Built with rollup *
5
+ * Build time: 2024-10-12 17:44:18 *
6
+ * Build mode: production *
7
+ * Minified: No *
8
+ * ***********************************
9
+ */
10
+ import { resolveGridStyle, resolveItemStyle } from "./styles.js";
11
+ import { normalizeMaterialSource, renderRuntimeMaterial } from "./material-renderer.js";
12
+ import { resolveBreakpoint, validateLayoutDocument } from "@composy/layout-core";
13
+ //#region src/runtime.ts
14
+ function createLayoutRuntime(options) {
15
+ let document = options.document;
16
+ let mounted = false;
17
+ let manualBreakpoint;
18
+ let activeBreakpoint;
19
+ let observer;
20
+ const handles = /* @__PURE__ */ new Map();
21
+ const shells = /* @__PURE__ */ new Map();
22
+ const ownedShells = /* @__PURE__ */ new Set();
23
+ const materials = normalizeMaterialSource(options.materials);
24
+ function render() {
25
+ if (!mounted || !document) return;
26
+ const result = validateLayoutDocument(document);
27
+ if (!result.valid) {
28
+ options.onError?.(new Error(result.issues[0]?.message ?? "Invalid layout document"));
29
+ return;
30
+ }
31
+ const breakpointId = manualBreakpoint ?? activeBreakpoint ?? document.defaultBreakpointId;
32
+ activeBreakpoint = breakpointId;
33
+ const gridStyle = resolveGridStyle(document, breakpointId);
34
+ Object.assign(options.container.style, gridStyle);
35
+ const currentIds = new Set(document.items.map((item) => item.id));
36
+ for (const [id, handle] of handles) {
37
+ if (currentIds.has(id)) continue;
38
+ disposeHandle(id, handle);
39
+ const shell = shells.get(id);
40
+ if (shell && ownedShells.has(shell)) shell.remove();
41
+ if (shell) ownedShells.delete(shell);
42
+ shells.delete(id);
43
+ }
44
+ for (const item of document.items) {
45
+ const placement = item.layout[breakpointId];
46
+ let shell = shells.get(item.id);
47
+ if (!shell) {
48
+ shell = options.getItemShell?.(item);
49
+ if (!shell) {
50
+ shell = options.container.ownerDocument.createElement("div");
51
+ shell.className = "ldesign-layout-item";
52
+ shell.dataset.layoutItemId = item.id;
53
+ shell.dataset.layoutType = item.type;
54
+ ownedShells.add(shell);
55
+ options.container.appendChild(shell);
56
+ }
57
+ shells.set(item.id, shell);
58
+ }
59
+ Object.assign(shell.style, resolveItemStyle(item, breakpointId));
60
+ if (!placement) {
61
+ const handle = handles.get(item.id);
62
+ if (handle) disposeHandle(item.id, handle);
63
+ continue;
64
+ }
65
+ const context = {
66
+ shell,
67
+ item,
68
+ placement,
69
+ breakpointId
70
+ };
71
+ try {
72
+ let handle = handles.get(item.id);
73
+ if (!handle) {
74
+ handle = options.renderItem ? options.renderItem(context) ?? {} : renderRuntimeMaterial(context, materials) ?? {};
75
+ handles.set(item.id, handle);
76
+ } else handle.update?.(context);
77
+ } catch (error) {
78
+ options.onError?.(error);
79
+ }
80
+ }
81
+ }
82
+ function disposeHandle(id, handle = handles.get(id)) {
83
+ if (!handle) return;
84
+ handles.delete(id);
85
+ try {
86
+ handle.destroy?.();
87
+ } catch (error) {
88
+ options.onError?.(error);
89
+ }
90
+ }
91
+ function updateBreakpoint(width) {
92
+ if (!document || manualBreakpoint) return;
93
+ const next = resolveBreakpoint(document, width).id;
94
+ if (next !== activeBreakpoint) {
95
+ const previous = activeBreakpoint;
96
+ activeBreakpoint = next;
97
+ options.onBreakpointChange?.(next);
98
+ if (previous !== next) render();
99
+ }
100
+ }
101
+ function updateBreakpointFromContainer() {
102
+ if (!document || manualBreakpoint) return;
103
+ const width = options.container.getBoundingClientRect().width || options.container.clientWidth;
104
+ if (width > 0) updateBreakpoint(width);
105
+ else if (activeBreakpoint !== document.defaultBreakpointId) {
106
+ const previous = activeBreakpoint;
107
+ activeBreakpoint = document.defaultBreakpointId;
108
+ if (previous) options.onBreakpointChange?.(activeBreakpoint);
109
+ }
110
+ }
111
+ return {
112
+ mount() {
113
+ if (mounted) return;
114
+ mounted = true;
115
+ if (options.observeResize !== false && typeof ResizeObserver !== "undefined") {
116
+ observer = new ResizeObserver((entries) => {
117
+ const width = entries[0]?.contentRect.width ?? options.container.clientWidth;
118
+ if (width > 0) updateBreakpoint(width);
119
+ else updateBreakpointFromContainer();
120
+ });
121
+ observer.observe(options.container);
122
+ }
123
+ updateBreakpointFromContainer();
124
+ render();
125
+ },
126
+ update(next) {
127
+ document = next;
128
+ render();
129
+ },
130
+ setBreakpoint(id) {
131
+ if (id && document && !document.breakpoints.some((bp) => bp.id === id)) {
132
+ options.onError?.(/* @__PURE__ */ new Error(`Unknown breakpoint ${id}.`));
133
+ return;
134
+ }
135
+ manualBreakpoint = id;
136
+ if (id) activeBreakpoint = id;
137
+ else updateBreakpointFromContainer();
138
+ render();
139
+ },
140
+ getBreakpoint() {
141
+ return activeBreakpoint;
142
+ },
143
+ getContainer() {
144
+ return options.container;
145
+ },
146
+ destroy() {
147
+ if (!mounted) return;
148
+ observer?.disconnect();
149
+ observer = void 0;
150
+ for (const [id, handle] of handles) disposeHandle(id, handle);
151
+ handles.clear();
152
+ for (const shell of shells.values()) if (ownedShells.has(shell)) shell.remove();
153
+ shells.clear();
154
+ ownedShells.clear();
155
+ options.container.style.removeProperty("display");
156
+ options.container.style.removeProperty("grid-template-columns");
157
+ options.container.style.removeProperty("grid-auto-rows");
158
+ options.container.style.removeProperty("gap");
159
+ options.container.style.removeProperty("direction");
160
+ options.container.style.removeProperty("min-height");
161
+ options.container.style.removeProperty("max-height");
162
+ manualBreakpoint = void 0;
163
+ activeBreakpoint = void 0;
164
+ mounted = false;
165
+ }
166
+ };
167
+ }
168
+ //#endregion
169
+ export { createLayoutRuntime, resolveGridStyle, resolveItemStyle };
170
+ /*! End of @composy/layout-runtime | Powered by @composy/builder */
171
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.js","names":[],"sources":["../src/runtime.ts"],"sourcesContent":["import type {\n BreakpointId,\n LayoutDocument,\n LayoutNode,\n LayoutPlacement,\n MaterialDefinition,\n MaterialRegistry,\n} from '@composy/layout-core'\nimport { resolveBreakpoint, validateLayoutDocument } from '@composy/layout-core'\nimport { resolveGridStyle, resolveItemStyle } from './styles'\nimport {\n normalizeMaterialSource,\n renderRuntimeMaterial,\n} from './material-renderer'\n\nexport interface LayoutRenderContext {\n shell: HTMLElement\n item: LayoutNode\n placement: LayoutPlacement\n breakpointId: BreakpointId\n}\nexport interface LayoutRenderHandle {\n update?(context: LayoutRenderContext): void\n destroy?(): void\n}\nexport interface LayoutRuntimeOptions {\n container: HTMLElement\n document?: LayoutDocument\n /** Lets a host adapter provide an existing light-DOM item shell. */\n getItemShell?: (item: LayoutNode) => HTMLElement | undefined\n renderItem?: (context: LayoutRenderContext) => LayoutRenderHandle | void\n /** Material registry used by the framework-neutral default renderer. */\n materials?: MaterialRegistry | readonly MaterialDefinition[]\n observeResize?: boolean\n onBreakpointChange?: (id: BreakpointId) => void\n onError?: (error: unknown) => void\n}\n\nexport interface LayoutRuntime {\n mount(): void\n update(document: LayoutDocument): void\n setBreakpoint(id?: BreakpointId): void\n getBreakpoint(): BreakpointId | undefined\n getContainer(): HTMLElement\n destroy(): void\n}\n\nexport function createLayoutRuntime(\n options: LayoutRuntimeOptions\n): LayoutRuntime {\n let document = options.document\n let mounted = false\n let manualBreakpoint: BreakpointId | undefined\n let activeBreakpoint: BreakpointId | undefined\n let observer: ResizeObserver | undefined\n const handles = new Map<string, LayoutRenderHandle>()\n const shells = new Map<string, HTMLElement>()\n const ownedShells = new Set<HTMLElement>()\n const materials = normalizeMaterialSource(options.materials)\n\n function render(): void {\n if (!mounted || !document) return\n const result = validateLayoutDocument(document)\n if (!result.valid) {\n options.onError?.(\n new Error(result.issues[0]?.message ?? 'Invalid layout document')\n )\n return\n }\n const breakpointId =\n manualBreakpoint ?? activeBreakpoint ?? document.defaultBreakpointId\n activeBreakpoint = breakpointId\n const gridStyle = resolveGridStyle(document, breakpointId)\n Object.assign(options.container.style, gridStyle)\n const currentIds = new Set(document.items.map(item => item.id))\n for (const [id, handle] of handles) {\n if (currentIds.has(id)) continue\n disposeHandle(id, handle)\n const shell = shells.get(id)\n if (shell && ownedShells.has(shell)) shell.remove()\n if (shell) ownedShells.delete(shell)\n shells.delete(id)\n }\n for (const item of document.items) {\n const placement = item.layout[breakpointId]\n let shell = shells.get(item.id)\n if (!shell) {\n shell = options.getItemShell?.(item)\n if (!shell) {\n shell = options.container.ownerDocument.createElement('div')\n shell.className = 'ldesign-layout-item'\n shell.dataset.layoutItemId = item.id\n shell.dataset.layoutType = item.type\n ownedShells.add(shell)\n options.container.appendChild(shell)\n }\n shells.set(item.id, shell)\n }\n Object.assign(shell.style, resolveItemStyle(item, breakpointId))\n if (!placement) {\n const handle = handles.get(item.id)\n if (handle) disposeHandle(item.id, handle)\n continue\n }\n const context = { shell, item, placement, breakpointId }\n try {\n let handle = handles.get(item.id)\n if (!handle) {\n handle = options.renderItem\n ? (options.renderItem(context) ?? {})\n : (renderRuntimeMaterial(context, materials) ?? {})\n handles.set(item.id, handle)\n } else handle.update?.(context)\n } catch (error) {\n options.onError?.(error)\n }\n }\n }\n\n function disposeHandle(id: string, handle = handles.get(id)): void {\n if (!handle) return\n handles.delete(id)\n try {\n handle.destroy?.()\n } catch (error) {\n options.onError?.(error)\n }\n }\n\n function updateBreakpoint(width: number): void {\n if (!document || manualBreakpoint) return\n const next = resolveBreakpoint(document, width).id\n if (next !== activeBreakpoint) {\n const previous = activeBreakpoint\n activeBreakpoint = next\n options.onBreakpointChange?.(next)\n if (previous !== next) render()\n }\n }\n\n function updateBreakpointFromContainer(): void {\n if (!document || manualBreakpoint) return\n const width =\n options.container.getBoundingClientRect().width ||\n options.container.clientWidth\n // A hidden or detached container has no measurable width. Keep the\n // document default until the first real resize instead of selecting the\n // smallest breakpoint for width 0.\n if (width > 0) updateBreakpoint(width)\n else if (activeBreakpoint !== document.defaultBreakpointId) {\n const previous = activeBreakpoint\n activeBreakpoint = document.defaultBreakpointId\n if (previous) options.onBreakpointChange?.(activeBreakpoint)\n }\n }\n\n return {\n mount() {\n if (mounted) return\n mounted = true\n if (\n options.observeResize !== false &&\n typeof ResizeObserver !== 'undefined'\n ) {\n observer = new ResizeObserver(entries => {\n const width =\n entries[0]?.contentRect.width ?? options.container.clientWidth\n if (width > 0) updateBreakpoint(width)\n else updateBreakpointFromContainer()\n })\n observer.observe(options.container)\n }\n updateBreakpointFromContainer()\n render()\n },\n update(next) {\n document = next\n render()\n },\n setBreakpoint(id) {\n if (id && document && !document.breakpoints.some(bp => bp.id === id)) {\n options.onError?.(new Error(`Unknown breakpoint ${id}.`))\n return\n }\n manualBreakpoint = id\n if (id) activeBreakpoint = id\n else updateBreakpointFromContainer()\n render()\n },\n getBreakpoint() {\n return activeBreakpoint\n },\n getContainer() {\n return options.container\n },\n destroy() {\n if (!mounted) return\n observer?.disconnect()\n observer = undefined\n for (const [id, handle] of handles) disposeHandle(id, handle)\n handles.clear()\n for (const shell of shells.values())\n if (ownedShells.has(shell)) shell.remove()\n shells.clear()\n ownedShells.clear()\n options.container.style.removeProperty('display')\n options.container.style.removeProperty('grid-template-columns')\n options.container.style.removeProperty('grid-auto-rows')\n options.container.style.removeProperty('gap')\n options.container.style.removeProperty('direction')\n options.container.style.removeProperty('min-height')\n options.container.style.removeProperty('max-height')\n manualBreakpoint = undefined\n activeBreakpoint = undefined\n mounted = false\n },\n }\n}\n\nexport { resolveGridStyle, resolveItemStyle }\n"],"mappings":";;;;;;;;;;;;;AA+CO,SAAS,oBACd,SACe;CACf,IAAI,WAAW,QAAQ;CACvB,IAAI,UAAU;CACd,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,MAAM,0BAAU,IAAI,IAAgC;CACpD,MAAM,yBAAS,IAAI,IAAyB;CAC5C,MAAM,8BAAc,IAAI,IAAiB;CACzC,MAAM,YAAY,wBAAwB,QAAQ,SAAS;CAE3D,SAAS,SAAe;EACtB,IAAI,CAAC,WAAW,CAAC,UAAU;EAC3B,MAAM,SAAS,uBAAuB,QAAQ;EAC9C,IAAI,CAAC,OAAO,OAAO;GACjB,QAAQ,UACN,IAAI,MAAM,OAAO,OAAO,EAAC,EAAG,WAAW,yBAAyB,CAClE;GACA;EACF;EACA,MAAM,eACJ,oBAAoB,oBAAoB,SAAS;EACnD,mBAAmB;EACnB,MAAM,YAAY,iBAAiB,UAAU,YAAY;EACzD,OAAO,OAAO,QAAQ,UAAU,OAAO,SAAS;EAChD,MAAM,aAAa,IAAI,IAAI,SAAS,MAAM,KAAI,SAAQ,KAAK,EAAE,CAAC;EAC9D,KAAA,MAAW,CAAC,IAAI,WAAW,SAAS;GAClC,IAAI,WAAW,IAAI,EAAE,GAAG;GACxB,cAAc,IAAI,MAAM;GACxB,MAAM,QAAQ,OAAO,IAAI,EAAE;GAC3B,IAAI,SAAS,YAAY,IAAI,KAAK,GAAG,MAAM,OAAO;GAClD,IAAI,OAAO,YAAY,OAAO,KAAK;GACnC,OAAO,OAAO,EAAE;EAClB;EACA,KAAA,MAAW,QAAQ,SAAS,OAAO;GACjC,MAAM,YAAY,KAAK,OAAO;GAC9B,IAAI,QAAQ,OAAO,IAAI,KAAK,EAAE;GAC9B,IAAI,CAAC,OAAO;IACV,QAAQ,QAAQ,eAAe,IAAI;IACnC,IAAI,CAAC,OAAO;KACV,QAAQ,QAAQ,UAAU,cAAc,cAAc,KAAK;KAC3D,MAAM,YAAY;KAClB,MAAM,QAAQ,eAAe,KAAK;KAClC,MAAM,QAAQ,aAAa,KAAK;KAChC,YAAY,IAAI,KAAK;KACrB,QAAQ,UAAU,YAAY,KAAK;IACrC;IACA,OAAO,IAAI,KAAK,IAAI,KAAK;GAC3B;GACA,OAAO,OAAO,MAAM,OAAO,iBAAiB,MAAM,YAAY,CAAC;GAC/D,IAAI,CAAC,WAAW;IACd,MAAM,SAAS,QAAQ,IAAI,KAAK,EAAE;IAClC,IAAI,QAAQ,cAAc,KAAK,IAAI,MAAM;IACzC;GACF;GACA,MAAM,UAAU;IAAE;IAAO;IAAM;IAAW;GAAa;GACvD,IAAI;IACF,IAAI,SAAS,QAAQ,IAAI,KAAK,EAAE;IAChC,IAAI,CAAC,QAAQ;KACX,SAAS,QAAQ,aACZ,QAAQ,WAAW,OAAO,KAAK,CAAC,IAChC,sBAAsB,SAAS,SAAS,KAAK,CAAC;KACnD,QAAQ,IAAI,KAAK,IAAI,MAAM;IAC7B,OAAO,OAAO,SAAS,OAAO;GAChC,SAAS,OAAO;IACd,QAAQ,UAAU,KAAK;GACzB;EACF;CACF;CAEA,SAAS,cAAc,IAAY,SAAS,QAAQ,IAAI,EAAE,GAAS;EACjE,IAAI,CAAC,QAAQ;EACb,QAAQ,OAAO,EAAE;EACjB,IAAI;GACF,OAAO,UAAU;EACnB,SAAS,OAAO;GACd,QAAQ,UAAU,KAAK;EACzB;CACF;CAEA,SAAS,iBAAiB,OAAqB;EAC7C,IAAI,CAAC,YAAY,kBAAkB;EACnC,MAAM,OAAO,kBAAkB,UAAU,KAAK,CAAA,CAAE;EAChD,IAAI,SAAS,kBAAkB;GAC7B,MAAM,WAAW;GACjB,mBAAmB;GACnB,QAAQ,qBAAqB,IAAI;GACjC,IAAI,aAAa,MAAM,OAAO;EAChC;CACF;CAEA,SAAS,gCAAsC;EAC7C,IAAI,CAAC,YAAY,kBAAkB;EACnC,MAAM,QACJ,QAAQ,UAAU,sBAAsB,CAAA,CAAE,SAC1C,QAAQ,UAAU;EAIpB,IAAI,QAAQ,GAAG,iBAAiB,KAAK;OAAA,IAC5B,qBAAqB,SAAS,qBAAqB;GAC1D,MAAM,WAAW;GACjB,mBAAmB,SAAS;GAC5B,IAAI,UAAU,QAAQ,qBAAqB,gBAAgB;EAC7D;CACF;CAEA,OAAO;EACL,QAAQ;GACN,IAAI,SAAS;GACb,UAAU;GACV,IACE,QAAQ,kBAAkB,SAC1B,OAAO,mBAAmB,aAC1B;IACA,WAAW,IAAI,gBAAe,YAAW;KACvC,MAAM,QACJ,QAAQ,EAAC,EAAG,YAAY,SAAS,QAAQ,UAAU;KACrD,IAAI,QAAQ,GAAG,iBAAiB,KAAK;UAChC,8BAA8B;IACrC,CAAC;IACD,SAAS,QAAQ,QAAQ,SAAS;GACpC;GACA,8BAA8B;GAC9B,OAAO;EACT;EACA,OAAO,MAAM;GACX,WAAW;GACX,OAAO;EACT;EACA,cAAc,IAAI;GAChB,IAAI,MAAM,YAAY,CAAC,SAAS,YAAY,MAAK,OAAM,GAAG,OAAO,EAAE,GAAG;IACpE,QAAQ,0BAAU,IAAI,MAAM,sBAAsB,GAAE,EAAG,CAAC;IACxD;GACF;GACA,mBAAmB;GACnB,IAAI,IAAI,mBAAmB;QACtB,8BAA8B;GACnC,OAAO;EACT;EACA,gBAAgB;GACd,OAAO;EACT;EACA,eAAe;GACb,OAAO,QAAQ;EACjB;EACA,UAAU;GACR,IAAI,CAAC,SAAS;GACd,UAAU,WAAW;GACrB,WAAW,KAAA;GACX,KAAA,MAAW,CAAC,IAAI,WAAW,SAAS,cAAc,IAAI,MAAM;GAC5D,QAAQ,MAAM;GACd,KAAA,MAAW,SAAS,OAAO,OAAO,GAChC,IAAI,YAAY,IAAI,KAAK,GAAG,MAAM,OAAO;GAC3C,OAAO,MAAM;GACb,YAAY,MAAM;GAClB,QAAQ,UAAU,MAAM,eAAe,SAAS;GAChD,QAAQ,UAAU,MAAM,eAAe,uBAAuB;GAC9D,QAAQ,UAAU,MAAM,eAAe,gBAAgB;GACvD,QAAQ,UAAU,MAAM,eAAe,KAAK;GAC5C,QAAQ,UAAU,MAAM,eAAe,WAAW;GAClD,QAAQ,UAAU,MAAM,eAAe,YAAY;GACnD,QAAQ,UAAU,MAAM,eAAe,YAAY;GACnD,mBAAmB,KAAA;GACnB,mBAAmB,KAAA;GACnB,UAAU;EACZ;CACF;AACF"}
package/esm/styles.js ADDED
@@ -0,0 +1,39 @@
1
+ /*!
2
+ * ***********************************
3
+ * @composy/layout-runtime v0.0.1 *
4
+ * Built with rollup *
5
+ * Build time: 2024-10-12 17:44:18 *
6
+ * Build mode: production *
7
+ * Minified: No *
8
+ * ***********************************
9
+ */
10
+ import { getBreakpoint, getEffectiveGrid } from "@composy/layout-core";
11
+ //#region src/styles.ts
12
+ function resolveGridStyle(document, breakpointId) {
13
+ const grid = getEffectiveGrid(document, breakpointId);
14
+ const breakpoint = getBreakpoint(document, breakpointId);
15
+ const style = {
16
+ display: "grid",
17
+ gridTemplateColumns: `repeat(${grid.columns}, minmax(0, 1fr))`,
18
+ gridAutoRows: `${grid.rowSize}px`,
19
+ gap: `${grid.gap}px`,
20
+ direction: grid.direction
21
+ };
22
+ if (document.grid.minRows !== void 0) style.minHeight = `${Math.max(0, document.grid.minRows * (grid.rowSize + grid.gap) - grid.gap)}px`;
23
+ if (document.grid.maxRows !== void 0) style.maxHeight = `${Math.max(0, document.grid.maxRows * (grid.rowSize + grid.gap) - grid.gap)}px`;
24
+ if (breakpoint?.canvasWidth) style.gridTemplateColumns = `repeat(${grid.columns}, minmax(0, 1fr))`;
25
+ return style;
26
+ }
27
+ function resolveItemStyle(node, breakpointId) {
28
+ const placement = node.layout[breakpointId];
29
+ if (!placement) return { display: "none" };
30
+ return {
31
+ gridColumn: `${placement.x + 1} / span ${placement.w}`,
32
+ gridRow: `${placement.y + 1} / span ${placement.h}`,
33
+ display: placement.hidden ? "none" : ""
34
+ };
35
+ }
36
+ //#endregion
37
+ export { resolveGridStyle, resolveItemStyle };
38
+ /*! End of @composy/layout-runtime | Powered by @composy/builder */
39
+ //# sourceMappingURL=styles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"styles.js","names":[],"sources":["../src/styles.ts"],"sourcesContent":["import type {\n BreakpointId,\n LayoutDocument,\n LayoutNode,\n LayoutPlacement,\n} from '@composy/layout-core'\nimport { getEffectiveGrid, getBreakpoint } from '@composy/layout-core'\n\nexport interface GridStyleResult {\n display: 'grid'\n gridTemplateColumns: string\n gridAutoRows: string\n gap: string\n direction: 'ltr' | 'rtl'\n minHeight?: string\n maxHeight?: string\n}\n\nexport function resolveGridStyle(\n document: LayoutDocument,\n breakpointId: BreakpointId\n): GridStyleResult {\n const grid = getEffectiveGrid(document, breakpointId)\n const breakpoint = getBreakpoint(document, breakpointId)\n const style: GridStyleResult = {\n display: 'grid',\n gridTemplateColumns: `repeat(${grid.columns}, minmax(0, 1fr))`,\n gridAutoRows: `${grid.rowSize}px`,\n gap: `${grid.gap}px`,\n direction: grid.direction,\n }\n if (document.grid.minRows !== undefined)\n style.minHeight = `${Math.max(\n 0,\n document.grid.minRows * (grid.rowSize + grid.gap) - grid.gap\n )}px`\n if (document.grid.maxRows !== undefined)\n style.maxHeight = `${Math.max(\n 0,\n document.grid.maxRows * (grid.rowSize + grid.gap) - grid.gap\n )}px`\n if (breakpoint?.canvasWidth)\n style.gridTemplateColumns = `repeat(${grid.columns}, minmax(0, 1fr))`\n return style\n}\n\nexport function resolveItemStyle(\n node: LayoutNode,\n breakpointId: BreakpointId\n): Record<string, string> {\n const placement = node.layout[breakpointId]\n if (!placement) return { display: 'none' }\n return {\n gridColumn: `${placement.x + 1} / span ${placement.w}`,\n gridRow: `${placement.y + 1} / span ${placement.h}`,\n display: placement.hidden ? 'none' : '',\n }\n}\n"],"mappings":";;;;;;;;;;;AAkBO,SAAS,iBACd,UACA,cACiB;CACjB,MAAM,OAAO,iBAAiB,UAAU,YAAY;CACpD,MAAM,aAAa,cAAc,UAAU,YAAY;CACvD,MAAM,QAAyB;EAC7B,SAAS;EACT,qBAAqB,UAAU,KAAK,QAAO;EAC3C,cAAc,GAAG,KAAK,QAAO;EAC7B,KAAK,GAAG,KAAK,IAAG;EAChB,WAAW,KAAK;CAClB;CACA,IAAI,SAAS,KAAK,YAAY,KAAA,GAC5B,MAAM,YAAY,GAAG,KAAK,IACxB,GACA,SAAS,KAAK,WAAW,KAAK,UAAU,KAAK,OAAO,KAAK,GAC3D,EAAC;CACH,IAAI,SAAS,KAAK,YAAY,KAAA,GAC5B,MAAM,YAAY,GAAG,KAAK,IACxB,GACA,SAAS,KAAK,WAAW,KAAK,UAAU,KAAK,OAAO,KAAK,GAC3D,EAAC;CACH,IAAI,YAAY,aACd,MAAM,sBAAsB,UAAU,KAAK,QAAO;CACpD,OAAO;AACT;AAEO,SAAS,iBACd,MACA,cACwB;CACxB,MAAM,YAAY,KAAK,OAAO;CAC9B,IAAI,CAAC,WAAW,OAAO,EAAE,SAAS,OAAO;CACzC,OAAO;EACL,YAAY,GAAG,UAAU,IAAI,EAAC,UAAW,UAAU;EACnD,SAAS,GAAG,UAAU,IAAI,EAAC,UAAW,UAAU;EAChD,SAAS,UAAU,SAAS,SAAS;CACvC;AACF"}
package/lib/index.cjs CHANGED
@@ -2,7 +2,7 @@
2
2
  * ***********************************
3
3
  * @composy/layout-runtime v0.0.1 *
4
4
  * Built with rollup *
5
- * Build time: 2024-08-17 11:42:14 *
5
+ * Build time: 2024-10-12 17:44:18 *
6
6
  * Build mode: production *
7
7
  * Minified: No *
8
8
  * ***********************************
@@ -1,3 +1,2 @@
1
1
  export * from './styles';
2
2
  export * from './runtime';
3
- //# sourceMappingURL=index.d.ts.map
@@ -2,7 +2,7 @@
2
2
  * ***********************************
3
3
  * @composy/layout-runtime v0.0.1 *
4
4
  * Built with rollup *
5
- * Build time: 2024-08-17 11:42:14 *
5
+ * Build time: 2024-10-12 17:44:18 *
6
6
  * Build mode: production *
7
7
  * Minified: No *
8
8
  * ***********************************
@@ -29,17 +29,26 @@ function renderRuntimeMaterial(context, materials) {
29
29
  placement: context.placement,
30
30
  breakpointId: context.breakpointId
31
31
  });
32
- if (rendered instanceof Node) node = rendered;
32
+ if (isDomNode(rendered)) node = rendered;
33
33
  else if (typeof rendered === "string") {
34
34
  const wrapper = host.createElement("div");
35
- wrapper.innerHTML = sanitizeMarkup(rendered);
35
+ wrapper.innerHTML = sanitizeMarkup(host, rendered);
36
36
  node = wrapper;
37
37
  }
38
38
  }
39
39
  if (!node) return void 0;
40
+ const appliedProps = /* @__PURE__ */ new Set();
40
41
  const applyProps = (item) => {
41
- if (!(node instanceof HTMLElement)) return;
42
- for (const [key, value] of Object.entries(item.props ?? {})) {
42
+ if (!isElement(node, host)) return;
43
+ const nextProps = item.props ?? {};
44
+ for (const key of appliedProps) {
45
+ if (key in nextProps) continue;
46
+ node.removeAttribute(key);
47
+ delete node.dataset[`prop${toDataKey(key)}`];
48
+ appliedProps.delete(key);
49
+ }
50
+ for (const [key, value] of Object.entries(nextProps)) {
51
+ appliedProps.add(key);
43
52
  try {
44
53
  node[key] = value;
45
54
  } catch {}
@@ -62,8 +71,32 @@ function renderRuntimeMaterial(context, materials) {
62
71
  function toDataKey(value) {
63
72
  return value.replace(/[^a-zA-Z0-9]/g, "");
64
73
  }
65
- function sanitizeMarkup(value) {
66
- return value.replace(/<script\b[^>]*>[\s\S]*?<\/script\s*>/gi, "").replace(/\s+on[a-z]+\s*=\s*(?:"[^"]*"|'[^']*'|[^\s>]+)/gi, "").replace(/\s+(?:href|src)\s*=\s*("|')\s*javascript:[^"']*\1/gi, "");
74
+ function isDomNode(value) {
75
+ return Boolean(value && typeof value === "object" && "nodeType" in value && typeof value.cloneNode === "function");
76
+ }
77
+ function isElement(value, document) {
78
+ if (!value || value.nodeType !== 1) return false;
79
+ const ElementConstructor = document.defaultView?.HTMLElement;
80
+ return ElementConstructor ? value instanceof ElementConstructor : typeof value.setAttribute === "function";
81
+ }
82
+ function sanitizeMarkup(document, value) {
83
+ const template = document.createElement("template");
84
+ template.innerHTML = value;
85
+ template.content.querySelectorAll("script,style,iframe,object,embed,foreignObject").forEach((node) => node.remove());
86
+ template.content.querySelectorAll("*").forEach((element) => {
87
+ for (const attribute of Array.from(element.attributes)) {
88
+ const name = attribute.name.toLowerCase();
89
+ const content = attribute.value.trim().toLowerCase();
90
+ if (name.startsWith("on") || [
91
+ "href",
92
+ "src",
93
+ "xlink:href",
94
+ "formaction",
95
+ "srcdoc"
96
+ ].includes(name) && /^(?:javascript:|data:text\/html)/i.test(content)) element.removeAttribute(attribute.name);
97
+ }
98
+ });
99
+ return template.innerHTML;
67
100
  }
68
101
  //#endregion
69
102
  exports.normalizeMaterialSource = normalizeMaterialSource;
@@ -1 +1 @@
1
- {"version":3,"file":"material-renderer.cjs","names":[],"sources":["../src/material-renderer.ts"],"sourcesContent":["import {\n createMaterialRegistry,\n type LayoutNode,\n type MaterialDefinition,\n type MaterialRegistry,\n} from '@composy/layout-core'\nimport type { LayoutRenderContext, LayoutRenderHandle } from './runtime'\n\nexport type RuntimeMaterialSource =\n MaterialRegistry | readonly MaterialDefinition[] | undefined\n\nexport function normalizeMaterialSource(\n source: RuntimeMaterialSource\n): MaterialRegistry | undefined {\n if (!source) return undefined\n if (Array.isArray(source)) return createMaterialRegistry(source)\n return source as MaterialRegistry\n}\n\nexport function renderRuntimeMaterial(\n context: LayoutRenderContext,\n materials: MaterialRegistry | undefined\n): LayoutRenderHandle | undefined {\n const material = materials?.get(context.item.type)\n const renderer =\n material?.renderers?.webcomponent ?? material?.renderers?.html\n if (!material || !renderer) return undefined\n\n const host = context.shell.ownerDocument\n let node: Node | undefined\n if (renderer.tagName) {\n node = host.createElement(renderer.tagName)\n } else if (renderer.render) {\n const rendered = renderer.render({\n item: context.item,\n placement: context.placement,\n breakpointId: context.breakpointId,\n })\n if (rendered instanceof Node) node = rendered\n else if (typeof rendered === 'string') {\n const wrapper = host.createElement('div')\n wrapper.innerHTML = sanitizeMarkup(rendered)\n node = wrapper\n }\n }\n if (!node) return undefined\n\n const applyProps = (item: LayoutNode): void => {\n if (!(node instanceof HTMLElement)) return\n for (const [key, value] of Object.entries(item.props ?? {})) {\n try {\n ;(node as unknown as Record<string, unknown>)[key] = value\n } catch {\n // Some custom elements expose readonly properties. The attribute is a\n // useful fallback for string-like values.\n }\n if (\n value === null ||\n value === undefined ||\n typeof value === 'object' ||\n typeof value === 'function'\n ) {\n if (value !== null && value !== undefined)\n node.dataset[`prop${toDataKey(key)}`] = JSON.stringify(value)\n } else node.setAttribute(key, String(value))\n }\n }\n applyProps(context.item)\n context.shell.replaceChildren(node)\n return {\n update(next) {\n applyProps(next.item)\n },\n destroy() {\n if (node?.parentNode === context.shell) context.shell.removeChild(node)\n },\n }\n}\n\nfunction toDataKey(value: string): string {\n return value.replace(/[^a-zA-Z0-9]/g, '')\n}\n\nfunction sanitizeMarkup(value: string): string {\n return value\n .replace(/<script\\b[^>]*>[\\s\\S]*?<\\/script\\s*>/gi, '')\n .replace(/\\s+on[a-z]+\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s>]+)/gi, '')\n .replace(/\\s+(?:href|src)\\s*=\\s*(\"|')\\s*javascript:[^\"']*\\1/gi, '')\n}\n"],"mappings":";;;;;;;;;;;;;AAWO,SAAS,wBACd,QAC8B;CAC9B,IAAI,CAAC,QAAQ,OAAO,KAAA;CACpB,IAAI,MAAM,QAAQ,MAAM,GAAG,QAAA,GAAA,qBAAA,uBAAA,CAA8B,MAAM;CAC/D,OAAO;AACT;AAEO,SAAS,sBACd,SACA,WACgC;CAChC,MAAM,WAAW,WAAW,IAAI,QAAQ,KAAK,IAAI;CACjD,MAAM,WACJ,UAAU,WAAW,gBAAgB,UAAU,WAAW;CAC5D,IAAI,CAAC,YAAY,CAAC,UAAU,OAAO,KAAA;CAEnC,MAAM,OAAO,QAAQ,MAAM;CAC3B,IAAI;CACJ,IAAI,SAAS,SACX,OAAO,KAAK,cAAc,SAAS,OAAO;MAC5C,IAAW,SAAS,QAAQ;EAC1B,MAAM,WAAW,SAAS,OAAO;GAC/B,MAAM,QAAQ;GACd,WAAW,QAAQ;GACnB,cAAc,QAAQ;EACxB,CAAC;EACD,IAAI,oBAAoB,MAAM,OAAO;OAAA,IAC5B,OAAO,aAAa,UAAU;GACrC,MAAM,UAAU,KAAK,cAAc,KAAK;GACxC,QAAQ,YAAY,eAAe,QAAQ;GAC3C,OAAO;EACT;CACF;CACA,IAAI,CAAC,MAAM,OAAO,KAAA;CAElB,MAAM,cAAc,SAA2B;EAC7C,IAAI,EAAE,gBAAgB,cAAc;EACpC,KAAA,MAAW,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,SAAS,CAAC,CAAC,GAAG;GAC3D,IAAI;IACA,KAA4C,OAAO;GACvD,QAAQ,CAGR;GACA,IACE,UAAU,QACV,UAAU,KAAA,KACV,OAAO,UAAU,YACjB,OAAO,UAAU;QAEb,UAAU,QAAQ,UAAU,KAAA,GAC9B,KAAK,QAAQ,OAAO,UAAU,GAAG,OAAO,KAAK,UAAU,KAAK;GAAA,OACzD,KAAK,aAAa,KAAK,OAAO,KAAK,CAAC;EAC7C;CACF;CACA,WAAW,QAAQ,IAAI;CACvB,QAAQ,MAAM,gBAAgB,IAAI;CAClC,OAAO;EACL,OAAO,MAAM;GACX,WAAW,KAAK,IAAI;EACtB;EACA,UAAU;GACR,IAAI,MAAM,eAAe,QAAQ,OAAO,QAAQ,MAAM,YAAY,IAAI;EACxE;CACF;AACF;AAEA,SAAS,UAAU,OAAuB;CACxC,OAAO,MAAM,QAAQ,iBAAiB,EAAE;AAC1C;AAEA,SAAS,eAAe,OAAuB;CAC7C,OAAO,MACJ,QAAQ,0CAA0C,EAAE,CAAA,CACpD,QAAQ,mDAAmD,EAAE,CAAA,CAC7D,QAAQ,uDAAuD,EAAE;AACtE"}
1
+ {"version":3,"file":"material-renderer.cjs","names":[],"sources":["../src/material-renderer.ts"],"sourcesContent":["import {\n createMaterialRegistry,\n type LayoutNode,\n type MaterialDefinition,\n type MaterialRegistry,\n} from '@composy/layout-core'\nimport type { LayoutRenderContext, LayoutRenderHandle } from './runtime'\n\nexport type RuntimeMaterialSource =\n MaterialRegistry | readonly MaterialDefinition[] | undefined\n\nexport function normalizeMaterialSource(\n source: RuntimeMaterialSource\n): MaterialRegistry | undefined {\n if (!source) return undefined\n if (Array.isArray(source)) return createMaterialRegistry(source)\n return source as MaterialRegistry\n}\n\nexport function renderRuntimeMaterial(\n context: LayoutRenderContext,\n materials: MaterialRegistry | undefined\n): LayoutRenderHandle | undefined {\n const material = materials?.get(context.item.type)\n const renderer =\n material?.renderers?.webcomponent ?? material?.renderers?.html\n if (!material || !renderer) return undefined\n\n const host = context.shell.ownerDocument\n let node: Node | undefined\n if (renderer.tagName) {\n node = host.createElement(renderer.tagName)\n } else if (renderer.render) {\n const rendered = renderer.render({\n item: context.item,\n placement: context.placement,\n breakpointId: context.breakpointId,\n })\n if (isDomNode(rendered)) node = rendered\n else if (typeof rendered === 'string') {\n const wrapper = host.createElement('div')\n wrapper.innerHTML = sanitizeMarkup(host, rendered)\n node = wrapper\n }\n }\n if (!node) return undefined\n\n const appliedProps = new Set<string>()\n const applyProps = (item: LayoutNode): void => {\n if (!isElement(node, host)) return\n const nextProps = item.props ?? {}\n for (const key of appliedProps) {\n if (key in nextProps) continue\n node.removeAttribute(key)\n delete node.dataset[`prop${toDataKey(key)}`]\n appliedProps.delete(key)\n }\n for (const [key, value] of Object.entries(nextProps)) {\n appliedProps.add(key)\n try {\n ;(node as unknown as Record<string, unknown>)[key] = value\n } catch {\n // Some custom elements expose readonly properties. The attribute is a\n // useful fallback for string-like values.\n }\n if (\n value === null ||\n value === undefined ||\n typeof value === 'object' ||\n typeof value === 'function'\n ) {\n if (value !== null && value !== undefined)\n node.dataset[`prop${toDataKey(key)}`] = JSON.stringify(value)\n } else node.setAttribute(key, String(value))\n }\n }\n applyProps(context.item)\n context.shell.replaceChildren(node)\n return {\n update(next) {\n applyProps(next.item)\n },\n destroy() {\n if (node?.parentNode === context.shell) context.shell.removeChild(node)\n },\n }\n}\n\nfunction toDataKey(value: string): string {\n return value.replace(/[^a-zA-Z0-9]/g, '')\n}\n\nfunction isDomNode(value: unknown): value is Node {\n return Boolean(\n value &&\n typeof value === 'object' &&\n 'nodeType' in value &&\n typeof (value as { cloneNode?: unknown }).cloneNode === 'function'\n )\n}\n\nfunction isElement(\n value: Node | undefined,\n document: Document\n): value is HTMLElement {\n if (!value || value.nodeType !== 1) return false\n const ElementConstructor = document.defaultView?.HTMLElement\n return ElementConstructor\n ? value instanceof ElementConstructor\n : typeof (value as Element).setAttribute === 'function'\n}\n\nfunction sanitizeMarkup(document: Document, value: string): string {\n const template = document.createElement('template')\n template.innerHTML = value\n template.content\n .querySelectorAll('script,style,iframe,object,embed,foreignObject')\n .forEach(node => node.remove())\n template.content.querySelectorAll<HTMLElement>('*').forEach(element => {\n for (const attribute of Array.from(element.attributes)) {\n const name = attribute.name.toLowerCase()\n const content = attribute.value.trim().toLowerCase()\n if (\n name.startsWith('on') ||\n (['href', 'src', 'xlink:href', 'formaction', 'srcdoc'].includes(name) &&\n /^(?:javascript:|data:text\\/html)/i.test(content))\n )\n element.removeAttribute(attribute.name)\n }\n })\n return template.innerHTML\n}\n"],"mappings":"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"}
@@ -3,4 +3,3 @@ import type { LayoutRenderContext, LayoutRenderHandle } from './runtime';
3
3
  export type RuntimeMaterialSource = MaterialRegistry | readonly MaterialDefinition[] | undefined;
4
4
  export declare function normalizeMaterialSource(source: RuntimeMaterialSource): MaterialRegistry | undefined;
5
5
  export declare function renderRuntimeMaterial(context: LayoutRenderContext, materials: MaterialRegistry | undefined): LayoutRenderHandle | undefined;
6
- //# sourceMappingURL=material-renderer.d.ts.map
package/lib/runtime.cjs CHANGED
@@ -2,7 +2,7 @@
2
2
  * ***********************************
3
3
  * @composy/layout-runtime v0.0.1 *
4
4
  * Built with rollup *
5
- * Build time: 2024-08-17 11:42:14 *
5
+ * Build time: 2024-10-12 17:44:18 *
6
6
  * Build mode: production *
7
7
  * Minified: No *
8
8
  * ***********************************
@@ -37,15 +37,11 @@ function createLayoutRuntime(options) {
37
37
  const currentIds = new Set(document.items.map((item) => item.id));
38
38
  for (const [id, handle] of handles) {
39
39
  if (currentIds.has(id)) continue;
40
- try {
41
- handle.destroy?.();
42
- } finally {
43
- handles.delete(id);
44
- const shell = shells.get(id);
45
- if (shell && ownedShells.has(shell)) shell.remove();
46
- if (shell) ownedShells.delete(shell);
47
- shells.delete(id);
48
- }
40
+ disposeHandle(id, handle);
41
+ const shell = shells.get(id);
42
+ if (shell && ownedShells.has(shell)) shell.remove();
43
+ if (shell) ownedShells.delete(shell);
44
+ shells.delete(id);
49
45
  }
50
46
  for (const item of document.items) {
51
47
  const placement = item.layout[breakpointId];
@@ -63,7 +59,11 @@ function createLayoutRuntime(options) {
63
59
  shells.set(item.id, shell);
64
60
  }
65
61
  Object.assign(shell.style, require_styles.resolveItemStyle(item, breakpointId));
66
- if (!placement) continue;
62
+ if (!placement) {
63
+ const handle = handles.get(item.id);
64
+ if (handle) disposeHandle(item.id, handle);
65
+ continue;
66
+ }
67
67
  const context = {
68
68
  shell,
69
69
  item,
@@ -81,6 +81,15 @@ function createLayoutRuntime(options) {
81
81
  }
82
82
  }
83
83
  }
84
+ function disposeHandle(id, handle = handles.get(id)) {
85
+ if (!handle) return;
86
+ handles.delete(id);
87
+ try {
88
+ handle.destroy?.();
89
+ } catch (error) {
90
+ options.onError?.(error);
91
+ }
92
+ }
84
93
  function updateBreakpoint(width) {
85
94
  if (!document || manualBreakpoint) return;
86
95
  const next = (0, _composy_layout_core.resolveBreakpoint)(document, width).id;
@@ -121,6 +130,10 @@ function createLayoutRuntime(options) {
121
130
  render();
122
131
  },
123
132
  setBreakpoint(id) {
133
+ if (id && document && !document.breakpoints.some((bp) => bp.id === id)) {
134
+ options.onError?.(/* @__PURE__ */ new Error(`Unknown breakpoint ${id}.`));
135
+ return;
136
+ }
124
137
  manualBreakpoint = id;
125
138
  if (id) activeBreakpoint = id;
126
139
  else updateBreakpointFromContainer();
@@ -136,7 +149,7 @@ function createLayoutRuntime(options) {
136
149
  if (!mounted) return;
137
150
  observer?.disconnect();
138
151
  observer = void 0;
139
- for (const handle of handles.values()) handle.destroy?.();
152
+ for (const [id, handle] of handles) disposeHandle(id, handle);
140
153
  handles.clear();
141
154
  for (const shell of shells.values()) if (ownedShells.has(shell)) shell.remove();
142
155
  shells.clear();
@@ -148,6 +161,8 @@ function createLayoutRuntime(options) {
148
161
  options.container.style.removeProperty("direction");
149
162
  options.container.style.removeProperty("min-height");
150
163
  options.container.style.removeProperty("max-height");
164
+ manualBreakpoint = void 0;
165
+ activeBreakpoint = void 0;
151
166
  mounted = false;
152
167
  }
153
168
  };
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.cjs","names":[],"sources":["../src/runtime.ts"],"sourcesContent":["import type {\n BreakpointId,\n LayoutDocument,\n LayoutNode,\n LayoutPlacement,\n MaterialDefinition,\n MaterialRegistry,\n} from '@composy/layout-core'\nimport { resolveBreakpoint, validateLayoutDocument } from '@composy/layout-core'\nimport { resolveGridStyle, resolveItemStyle } from './styles'\nimport {\n normalizeMaterialSource,\n renderRuntimeMaterial,\n} from './material-renderer'\n\nexport interface LayoutRenderContext {\n shell: HTMLElement\n item: LayoutNode\n placement: LayoutPlacement\n breakpointId: BreakpointId\n}\nexport interface LayoutRenderHandle {\n update?(context: LayoutRenderContext): void\n destroy?(): void\n}\nexport interface LayoutRuntimeOptions {\n container: HTMLElement\n document?: LayoutDocument\n /** Lets a host adapter provide an existing light-DOM item shell. */\n getItemShell?: (item: LayoutNode) => HTMLElement | undefined\n renderItem?: (context: LayoutRenderContext) => LayoutRenderHandle | void\n /** Material registry used by the framework-neutral default renderer. */\n materials?: MaterialRegistry | readonly MaterialDefinition[]\n observeResize?: boolean\n onBreakpointChange?: (id: BreakpointId) => void\n onError?: (error: unknown) => void\n}\n\nexport interface LayoutRuntime {\n mount(): void\n update(document: LayoutDocument): void\n setBreakpoint(id?: BreakpointId): void\n getBreakpoint(): BreakpointId | undefined\n getContainer(): HTMLElement\n destroy(): void\n}\n\nexport function createLayoutRuntime(\n options: LayoutRuntimeOptions\n): LayoutRuntime {\n let document = options.document\n let mounted = false\n let manualBreakpoint: BreakpointId | undefined\n let activeBreakpoint: BreakpointId | undefined\n let observer: ResizeObserver | undefined\n const handles = new Map<string, LayoutRenderHandle>()\n const shells = new Map<string, HTMLElement>()\n const ownedShells = new Set<HTMLElement>()\n const materials = normalizeMaterialSource(options.materials)\n\n function render(): void {\n if (!mounted || !document) return\n const result = validateLayoutDocument(document)\n if (!result.valid) {\n options.onError?.(\n new Error(result.issues[0]?.message ?? 'Invalid layout document')\n )\n return\n }\n const breakpointId =\n manualBreakpoint ?? activeBreakpoint ?? document.defaultBreakpointId\n activeBreakpoint = breakpointId\n const gridStyle = resolveGridStyle(document, breakpointId)\n Object.assign(options.container.style, gridStyle)\n const currentIds = new Set(document.items.map(item => item.id))\n for (const [id, handle] of handles) {\n if (currentIds.has(id)) continue\n try {\n handle.destroy?.()\n } finally {\n handles.delete(id)\n const shell = shells.get(id)\n if (shell && ownedShells.has(shell)) shell.remove()\n if (shell) ownedShells.delete(shell)\n shells.delete(id)\n }\n }\n for (const item of document.items) {\n const placement = item.layout[breakpointId]\n let shell = shells.get(item.id)\n if (!shell) {\n shell = options.getItemShell?.(item)\n if (!shell) {\n shell = options.container.ownerDocument.createElement('div')\n shell.className = 'ldesign-layout-item'\n shell.dataset.layoutItemId = item.id\n shell.dataset.layoutType = item.type\n ownedShells.add(shell)\n options.container.appendChild(shell)\n }\n shells.set(item.id, shell)\n }\n Object.assign(shell.style, resolveItemStyle(item, breakpointId))\n if (!placement) continue\n const context = { shell, item, placement, breakpointId }\n try {\n let handle = handles.get(item.id)\n if (!handle) {\n handle = options.renderItem\n ? (options.renderItem(context) ?? {})\n : (renderRuntimeMaterial(context, materials) ?? {})\n handles.set(item.id, handle)\n } else handle.update?.(context)\n } catch (error) {\n options.onError?.(error)\n }\n }\n }\n\n function updateBreakpoint(width: number): void {\n if (!document || manualBreakpoint) return\n const next = resolveBreakpoint(document, width).id\n if (next !== activeBreakpoint) {\n const previous = activeBreakpoint\n activeBreakpoint = next\n options.onBreakpointChange?.(next)\n if (previous !== next) render()\n }\n }\n\n function updateBreakpointFromContainer(): void {\n if (!document || manualBreakpoint) return\n const width =\n options.container.getBoundingClientRect().width ||\n options.container.clientWidth\n // A hidden or detached container has no measurable width. Keep the\n // document default until the first real resize instead of selecting the\n // smallest breakpoint for width 0.\n if (width > 0) updateBreakpoint(width)\n else if (activeBreakpoint !== document.defaultBreakpointId) {\n const previous = activeBreakpoint\n activeBreakpoint = document.defaultBreakpointId\n if (previous) options.onBreakpointChange?.(activeBreakpoint)\n }\n }\n\n return {\n mount() {\n if (mounted) return\n mounted = true\n if (\n options.observeResize !== false &&\n typeof ResizeObserver !== 'undefined'\n ) {\n observer = new ResizeObserver(entries => {\n const width =\n entries[0]?.contentRect.width ?? options.container.clientWidth\n if (width > 0) updateBreakpoint(width)\n else updateBreakpointFromContainer()\n })\n observer.observe(options.container)\n }\n updateBreakpointFromContainer()\n render()\n },\n update(next) {\n document = next\n render()\n },\n setBreakpoint(id) {\n manualBreakpoint = id\n if (id) activeBreakpoint = id\n else updateBreakpointFromContainer()\n render()\n },\n getBreakpoint() {\n return activeBreakpoint\n },\n getContainer() {\n return options.container\n },\n destroy() {\n if (!mounted) return\n observer?.disconnect()\n observer = undefined\n for (const handle of handles.values()) handle.destroy?.()\n handles.clear()\n for (const shell of shells.values())\n if (ownedShells.has(shell)) shell.remove()\n shells.clear()\n ownedShells.clear()\n options.container.style.removeProperty('display')\n options.container.style.removeProperty('grid-template-columns')\n options.container.style.removeProperty('grid-auto-rows')\n options.container.style.removeProperty('gap')\n options.container.style.removeProperty('direction')\n options.container.style.removeProperty('min-height')\n options.container.style.removeProperty('max-height')\n mounted = false\n },\n }\n}\n\nexport { resolveGridStyle, resolveItemStyle }\n"],"mappings":"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"}
1
+ {"version":3,"file":"runtime.cjs","names":[],"sources":["../src/runtime.ts"],"sourcesContent":["import type {\n BreakpointId,\n LayoutDocument,\n LayoutNode,\n LayoutPlacement,\n MaterialDefinition,\n MaterialRegistry,\n} from '@composy/layout-core'\nimport { resolveBreakpoint, validateLayoutDocument } from '@composy/layout-core'\nimport { resolveGridStyle, resolveItemStyle } from './styles'\nimport {\n normalizeMaterialSource,\n renderRuntimeMaterial,\n} from './material-renderer'\n\nexport interface LayoutRenderContext {\n shell: HTMLElement\n item: LayoutNode\n placement: LayoutPlacement\n breakpointId: BreakpointId\n}\nexport interface LayoutRenderHandle {\n update?(context: LayoutRenderContext): void\n destroy?(): void\n}\nexport interface LayoutRuntimeOptions {\n container: HTMLElement\n document?: LayoutDocument\n /** Lets a host adapter provide an existing light-DOM item shell. */\n getItemShell?: (item: LayoutNode) => HTMLElement | undefined\n renderItem?: (context: LayoutRenderContext) => LayoutRenderHandle | void\n /** Material registry used by the framework-neutral default renderer. */\n materials?: MaterialRegistry | readonly MaterialDefinition[]\n observeResize?: boolean\n onBreakpointChange?: (id: BreakpointId) => void\n onError?: (error: unknown) => void\n}\n\nexport interface LayoutRuntime {\n mount(): void\n update(document: LayoutDocument): void\n setBreakpoint(id?: BreakpointId): void\n getBreakpoint(): BreakpointId | undefined\n getContainer(): HTMLElement\n destroy(): void\n}\n\nexport function createLayoutRuntime(\n options: LayoutRuntimeOptions\n): LayoutRuntime {\n let document = options.document\n let mounted = false\n let manualBreakpoint: BreakpointId | undefined\n let activeBreakpoint: BreakpointId | undefined\n let observer: ResizeObserver | undefined\n const handles = new Map<string, LayoutRenderHandle>()\n const shells = new Map<string, HTMLElement>()\n const ownedShells = new Set<HTMLElement>()\n const materials = normalizeMaterialSource(options.materials)\n\n function render(): void {\n if (!mounted || !document) return\n const result = validateLayoutDocument(document)\n if (!result.valid) {\n options.onError?.(\n new Error(result.issues[0]?.message ?? 'Invalid layout document')\n )\n return\n }\n const breakpointId =\n manualBreakpoint ?? activeBreakpoint ?? document.defaultBreakpointId\n activeBreakpoint = breakpointId\n const gridStyle = resolveGridStyle(document, breakpointId)\n Object.assign(options.container.style, gridStyle)\n const currentIds = new Set(document.items.map(item => item.id))\n for (const [id, handle] of handles) {\n if (currentIds.has(id)) continue\n disposeHandle(id, handle)\n const shell = shells.get(id)\n if (shell && ownedShells.has(shell)) shell.remove()\n if (shell) ownedShells.delete(shell)\n shells.delete(id)\n }\n for (const item of document.items) {\n const placement = item.layout[breakpointId]\n let shell = shells.get(item.id)\n if (!shell) {\n shell = options.getItemShell?.(item)\n if (!shell) {\n shell = options.container.ownerDocument.createElement('div')\n shell.className = 'ldesign-layout-item'\n shell.dataset.layoutItemId = item.id\n shell.dataset.layoutType = item.type\n ownedShells.add(shell)\n options.container.appendChild(shell)\n }\n shells.set(item.id, shell)\n }\n Object.assign(shell.style, resolveItemStyle(item, breakpointId))\n if (!placement) {\n const handle = handles.get(item.id)\n if (handle) disposeHandle(item.id, handle)\n continue\n }\n const context = { shell, item, placement, breakpointId }\n try {\n let handle = handles.get(item.id)\n if (!handle) {\n handle = options.renderItem\n ? (options.renderItem(context) ?? {})\n : (renderRuntimeMaterial(context, materials) ?? {})\n handles.set(item.id, handle)\n } else handle.update?.(context)\n } catch (error) {\n options.onError?.(error)\n }\n }\n }\n\n function disposeHandle(id: string, handle = handles.get(id)): void {\n if (!handle) return\n handles.delete(id)\n try {\n handle.destroy?.()\n } catch (error) {\n options.onError?.(error)\n }\n }\n\n function updateBreakpoint(width: number): void {\n if (!document || manualBreakpoint) return\n const next = resolveBreakpoint(document, width).id\n if (next !== activeBreakpoint) {\n const previous = activeBreakpoint\n activeBreakpoint = next\n options.onBreakpointChange?.(next)\n if (previous !== next) render()\n }\n }\n\n function updateBreakpointFromContainer(): void {\n if (!document || manualBreakpoint) return\n const width =\n options.container.getBoundingClientRect().width ||\n options.container.clientWidth\n // A hidden or detached container has no measurable width. Keep the\n // document default until the first real resize instead of selecting the\n // smallest breakpoint for width 0.\n if (width > 0) updateBreakpoint(width)\n else if (activeBreakpoint !== document.defaultBreakpointId) {\n const previous = activeBreakpoint\n activeBreakpoint = document.defaultBreakpointId\n if (previous) options.onBreakpointChange?.(activeBreakpoint)\n }\n }\n\n return {\n mount() {\n if (mounted) return\n mounted = true\n if (\n options.observeResize !== false &&\n typeof ResizeObserver !== 'undefined'\n ) {\n observer = new ResizeObserver(entries => {\n const width =\n entries[0]?.contentRect.width ?? options.container.clientWidth\n if (width > 0) updateBreakpoint(width)\n else updateBreakpointFromContainer()\n })\n observer.observe(options.container)\n }\n updateBreakpointFromContainer()\n render()\n },\n update(next) {\n document = next\n render()\n },\n setBreakpoint(id) {\n if (id && document && !document.breakpoints.some(bp => bp.id === id)) {\n options.onError?.(new Error(`Unknown breakpoint ${id}.`))\n return\n }\n manualBreakpoint = id\n if (id) activeBreakpoint = id\n else updateBreakpointFromContainer()\n render()\n },\n getBreakpoint() {\n return activeBreakpoint\n },\n getContainer() {\n return options.container\n },\n destroy() {\n if (!mounted) return\n observer?.disconnect()\n observer = undefined\n for (const [id, handle] of handles) disposeHandle(id, handle)\n handles.clear()\n for (const shell of shells.values())\n if (ownedShells.has(shell)) shell.remove()\n shells.clear()\n ownedShells.clear()\n options.container.style.removeProperty('display')\n options.container.style.removeProperty('grid-template-columns')\n options.container.style.removeProperty('grid-auto-rows')\n options.container.style.removeProperty('gap')\n options.container.style.removeProperty('direction')\n options.container.style.removeProperty('min-height')\n options.container.style.removeProperty('max-height')\n manualBreakpoint = undefined\n activeBreakpoint = undefined\n mounted = false\n },\n }\n}\n\nexport { resolveGridStyle, resolveItemStyle }\n"],"mappings":"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"}
@@ -32,4 +32,3 @@ export interface LayoutRuntime {
32
32
  }
33
33
  export declare function createLayoutRuntime(options: LayoutRuntimeOptions): LayoutRuntime;
34
34
  export { resolveGridStyle, resolveItemStyle };
35
- //# sourceMappingURL=runtime.d.ts.map