@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.
- package/CHANGELOG.md +35 -0
- package/README.md +101 -17
- package/dist/index.js +68 -20
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +191 -1
- package/dist/index.min.js.map +1 -0
- package/es/.dts-output-manifest.json +1 -0
- package/es/.dts.tsbuildinfo +1 -0
- package/es/index.d.ts +0 -1
- package/es/index.js +1 -1
- package/es/material-renderer.d.ts +0 -1
- package/es/material-renderer.js +40 -7
- package/es/material-renderer.js.map +1 -1
- package/es/runtime.d.ts +0 -1
- package/es/runtime.js +27 -12
- package/es/runtime.js.map +1 -1
- package/es/styles.d.ts +0 -1
- package/es/styles.js +3 -3
- package/es/styles.js.map +1 -1
- package/esm/index.js +13 -0
- package/esm/material-renderer.js +102 -0
- package/esm/material-renderer.js.map +1 -0
- package/esm/runtime.js +171 -0
- package/esm/runtime.js.map +1 -0
- package/esm/styles.js +39 -0
- package/esm/styles.js.map +1 -0
- package/lib/index.cjs +1 -1
- package/lib/{index.d.ts → index.d.cts} +0 -1
- package/lib/material-renderer.cjs +40 -7
- package/lib/material-renderer.cjs.map +1 -1
- package/lib/{material-renderer.d.ts → material-renderer.d.cts} +0 -1
- package/lib/runtime.cjs +27 -12
- package/lib/runtime.cjs.map +1 -1
- package/lib/{runtime.d.ts → runtime.d.cts} +0 -1
- package/lib/styles.cjs +3 -3
- package/lib/styles.cjs.map +1 -1
- package/lib/{styles.d.ts → styles.d.cts} +0 -1
- package/package.json +13 -7
- package/es/index.d.ts.map +0 -1
- package/es/material-renderer.d.ts.map +0 -1
- package/es/runtime.d.ts.map +0 -1
- 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":"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"}
|
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-
|
|
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
|
|
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
|
|
42
|
-
|
|
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
|
|
66
|
-
return value
|
|
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
|
|
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-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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)
|
|
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
|
|
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
|
};
|
package/lib/runtime.cjs.map
CHANGED
|
@@ -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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"}
|