@composy/layout-runtime 0.0.1
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 +5 -0
- package/LICENSE +21 -0
- package/README.md +17 -0
- package/dist/index.js +243 -0
- package/dist/index.js.map +1 -0
- package/dist/index.min.js +1 -0
- package/es/index.d.ts +3 -0
- package/es/index.d.ts.map +1 -0
- package/es/index.js +13 -0
- package/es/material-renderer.d.ts +6 -0
- package/es/material-renderer.d.ts.map +1 -0
- package/es/material-renderer.js +69 -0
- package/es/material-renderer.js.map +1 -0
- package/es/runtime.d.ts +35 -0
- package/es/runtime.d.ts.map +1 -0
- package/es/runtime.js +156 -0
- package/es/runtime.js.map +1 -0
- package/es/styles.d.ts +13 -0
- package/es/styles.d.ts.map +1 -0
- package/es/styles.js +39 -0
- package/es/styles.js.map +1 -0
- package/lib/index.cjs +17 -0
- package/lib/index.d.ts +3 -0
- package/lib/material-renderer.cjs +72 -0
- package/lib/material-renderer.cjs.map +1 -0
- package/lib/material-renderer.d.ts +6 -0
- package/lib/package.json +3 -0
- package/lib/runtime.cjs +160 -0
- package/lib/runtime.cjs.map +1 -0
- package/lib/runtime.d.ts +35 -0
- package/lib/styles.cjs +42 -0
- package/lib/styles.cjs.map +1 -0
- package/lib/styles.d.ts +13 -0
- package/package.json +50 -0
package/CHANGELOG.md
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 LDesign Team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# @composy/layout-runtime
|
|
2
|
+
|
|
3
|
+
DOM runtime for rendering a `LayoutDocument` with CSS Grid. It owns breakpoint resolution,
|
|
4
|
+
responsive resize observation, item shell lifecycle, and renderer cleanup without importing
|
|
5
|
+
GridStack or a UI framework.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { createLayoutRuntime } from '@composy/layout-runtime'
|
|
9
|
+
|
|
10
|
+
const runtime = createLayoutRuntime({ container, document })
|
|
11
|
+
runtime.mount()
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
运行时也可接受 `materials`。没有提供 `renderItem` 时,优先创建物料声明的 Web
|
|
15
|
+
Component,其次使用 HTML 标签或 `html.render`;提供 `renderItem` 时由宿主完全接管渲染。
|
|
16
|
+
|
|
17
|
+
Use `update`, `setBreakpoint`, and `destroy` to control the runtime lifecycle.
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* ***********************************
|
|
3
|
+
* @composy/layout-runtime v0.0.1 *
|
|
4
|
+
* Built with rollup *
|
|
5
|
+
* Build time: 2024-08-17 11:42:14 *
|
|
6
|
+
* Build mode: production *
|
|
7
|
+
* Minified: No *
|
|
8
|
+
* ***********************************
|
|
9
|
+
*/
|
|
10
|
+
"use strict";
|
|
11
|
+
(function(global, factory) {
|
|
12
|
+
typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("@composy/layout-core")) : typeof define === "function" && define.amd ? define(["exports", "@composy/layout-core"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.LDesignLayoutRuntime = {}, global.ComposyLayoutCore));
|
|
13
|
+
})(this, function(exports, _composy_layout_core) {
|
|
14
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
15
|
+
//#region src/styles.ts
|
|
16
|
+
function resolveGridStyle(document, breakpointId) {
|
|
17
|
+
const grid = (0, _composy_layout_core.getEffectiveGrid)(document, breakpointId);
|
|
18
|
+
const breakpoint = (0, _composy_layout_core.getBreakpoint)(document, breakpointId);
|
|
19
|
+
const style = {
|
|
20
|
+
display: "grid",
|
|
21
|
+
gridTemplateColumns: `repeat(${grid.columns}, minmax(0, 1fr))`,
|
|
22
|
+
gridAutoRows: `${grid.rowSize}px`,
|
|
23
|
+
gap: `${grid.gap}px`,
|
|
24
|
+
direction: grid.direction
|
|
25
|
+
};
|
|
26
|
+
if (document.grid.minRows !== void 0) style.minHeight = `${document.grid.minRows * (grid.rowSize + grid.gap) - grid.gap}px`;
|
|
27
|
+
if (document.grid.maxRows !== void 0) style.maxHeight = `${document.grid.maxRows * (grid.rowSize + grid.gap) - grid.gap}px`;
|
|
28
|
+
if (breakpoint?.canvasWidth) style.gridTemplateColumns = `repeat(${grid.columns}, minmax(0, 1fr))`;
|
|
29
|
+
return style;
|
|
30
|
+
}
|
|
31
|
+
function resolveItemStyle(node, breakpointId) {
|
|
32
|
+
const placement = node.layout[breakpointId];
|
|
33
|
+
if (!placement) return { display: "none" };
|
|
34
|
+
return {
|
|
35
|
+
gridColumn: `${placement.x + 1} / span ${placement.w}`,
|
|
36
|
+
gridRow: `${placement.y + 1} / span ${placement.h}`,
|
|
37
|
+
display: placement.hidden ? "none" : ""
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/material-renderer.ts
|
|
42
|
+
function normalizeMaterialSource(source) {
|
|
43
|
+
if (!source) return void 0;
|
|
44
|
+
if (Array.isArray(source)) return (0, _composy_layout_core.createMaterialRegistry)(source);
|
|
45
|
+
return source;
|
|
46
|
+
}
|
|
47
|
+
function renderRuntimeMaterial(context, materials) {
|
|
48
|
+
const material = materials?.get(context.item.type);
|
|
49
|
+
const renderer = material?.renderers?.webcomponent ?? material?.renderers?.html;
|
|
50
|
+
if (!material || !renderer) return void 0;
|
|
51
|
+
const host = context.shell.ownerDocument;
|
|
52
|
+
let node;
|
|
53
|
+
if (renderer.tagName) node = host.createElement(renderer.tagName);
|
|
54
|
+
else if (renderer.render) {
|
|
55
|
+
const rendered = renderer.render({
|
|
56
|
+
item: context.item,
|
|
57
|
+
placement: context.placement,
|
|
58
|
+
breakpointId: context.breakpointId
|
|
59
|
+
});
|
|
60
|
+
if (rendered instanceof Node) node = rendered;
|
|
61
|
+
else if (typeof rendered === "string") {
|
|
62
|
+
const wrapper = host.createElement("div");
|
|
63
|
+
wrapper.innerHTML = sanitizeMarkup(rendered);
|
|
64
|
+
node = wrapper;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (!node) return void 0;
|
|
68
|
+
const applyProps = (item) => {
|
|
69
|
+
if (!(node instanceof HTMLElement)) return;
|
|
70
|
+
for (const [key, value] of Object.entries(item.props ?? {})) {
|
|
71
|
+
try {
|
|
72
|
+
node[key] = value;
|
|
73
|
+
} catch {}
|
|
74
|
+
if (value === null || value === void 0 || typeof value === "object" || typeof value === "function") {
|
|
75
|
+
if (value !== null && value !== void 0) node.dataset[`prop${toDataKey(key)}`] = JSON.stringify(value);
|
|
76
|
+
} else node.setAttribute(key, String(value));
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
applyProps(context.item);
|
|
80
|
+
context.shell.replaceChildren(node);
|
|
81
|
+
return {
|
|
82
|
+
update(next) {
|
|
83
|
+
applyProps(next.item);
|
|
84
|
+
},
|
|
85
|
+
destroy() {
|
|
86
|
+
if (node?.parentNode === context.shell) context.shell.removeChild(node);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function toDataKey(value) {
|
|
91
|
+
return value.replace(/[^a-zA-Z0-9]/g, "");
|
|
92
|
+
}
|
|
93
|
+
function sanitizeMarkup(value) {
|
|
94
|
+
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, "");
|
|
95
|
+
}
|
|
96
|
+
//#endregion
|
|
97
|
+
//#region src/runtime.ts
|
|
98
|
+
function createLayoutRuntime(options) {
|
|
99
|
+
let document = options.document;
|
|
100
|
+
let mounted = false;
|
|
101
|
+
let manualBreakpoint;
|
|
102
|
+
let activeBreakpoint;
|
|
103
|
+
let observer;
|
|
104
|
+
const handles = /* @__PURE__ */ new Map();
|
|
105
|
+
const shells = /* @__PURE__ */ new Map();
|
|
106
|
+
const ownedShells = /* @__PURE__ */ new Set();
|
|
107
|
+
const materials = normalizeMaterialSource(options.materials);
|
|
108
|
+
function render() {
|
|
109
|
+
if (!mounted || !document) return;
|
|
110
|
+
const result = (0, _composy_layout_core.validateLayoutDocument)(document);
|
|
111
|
+
if (!result.valid) {
|
|
112
|
+
options.onError?.(new Error(result.issues[0]?.message ?? "Invalid layout document"));
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const breakpointId = manualBreakpoint ?? activeBreakpoint ?? document.defaultBreakpointId;
|
|
116
|
+
activeBreakpoint = breakpointId;
|
|
117
|
+
const gridStyle = resolveGridStyle(document, breakpointId);
|
|
118
|
+
Object.assign(options.container.style, gridStyle);
|
|
119
|
+
const currentIds = new Set(document.items.map((item) => item.id));
|
|
120
|
+
for (const [id, handle] of handles) {
|
|
121
|
+
if (currentIds.has(id)) continue;
|
|
122
|
+
try {
|
|
123
|
+
handle.destroy?.();
|
|
124
|
+
} finally {
|
|
125
|
+
handles.delete(id);
|
|
126
|
+
const shell = shells.get(id);
|
|
127
|
+
if (shell && ownedShells.has(shell)) shell.remove();
|
|
128
|
+
if (shell) ownedShells.delete(shell);
|
|
129
|
+
shells.delete(id);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
for (const item of document.items) {
|
|
133
|
+
const placement = item.layout[breakpointId];
|
|
134
|
+
let shell = shells.get(item.id);
|
|
135
|
+
if (!shell) {
|
|
136
|
+
shell = options.getItemShell?.(item);
|
|
137
|
+
if (!shell) {
|
|
138
|
+
shell = options.container.ownerDocument.createElement("div");
|
|
139
|
+
shell.className = "ldesign-layout-item";
|
|
140
|
+
shell.dataset.layoutItemId = item.id;
|
|
141
|
+
shell.dataset.layoutType = item.type;
|
|
142
|
+
ownedShells.add(shell);
|
|
143
|
+
options.container.appendChild(shell);
|
|
144
|
+
}
|
|
145
|
+
shells.set(item.id, shell);
|
|
146
|
+
}
|
|
147
|
+
Object.assign(shell.style, resolveItemStyle(item, breakpointId));
|
|
148
|
+
if (!placement) continue;
|
|
149
|
+
const context = {
|
|
150
|
+
shell,
|
|
151
|
+
item,
|
|
152
|
+
placement,
|
|
153
|
+
breakpointId
|
|
154
|
+
};
|
|
155
|
+
try {
|
|
156
|
+
let handle = handles.get(item.id);
|
|
157
|
+
if (!handle) {
|
|
158
|
+
handle = options.renderItem ? options.renderItem(context) ?? {} : renderRuntimeMaterial(context, materials) ?? {};
|
|
159
|
+
handles.set(item.id, handle);
|
|
160
|
+
} else handle.update?.(context);
|
|
161
|
+
} catch (error) {
|
|
162
|
+
options.onError?.(error);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function updateBreakpoint(width) {
|
|
167
|
+
if (!document || manualBreakpoint) return;
|
|
168
|
+
const next = (0, _composy_layout_core.resolveBreakpoint)(document, width).id;
|
|
169
|
+
if (next !== activeBreakpoint) {
|
|
170
|
+
const previous = activeBreakpoint;
|
|
171
|
+
activeBreakpoint = next;
|
|
172
|
+
options.onBreakpointChange?.(next);
|
|
173
|
+
if (previous !== next) render();
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function updateBreakpointFromContainer() {
|
|
177
|
+
if (!document || manualBreakpoint) return;
|
|
178
|
+
const width = options.container.getBoundingClientRect().width || options.container.clientWidth;
|
|
179
|
+
if (width > 0) updateBreakpoint(width);
|
|
180
|
+
else if (activeBreakpoint !== document.defaultBreakpointId) {
|
|
181
|
+
const previous = activeBreakpoint;
|
|
182
|
+
activeBreakpoint = document.defaultBreakpointId;
|
|
183
|
+
if (previous) options.onBreakpointChange?.(activeBreakpoint);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
mount() {
|
|
188
|
+
if (mounted) return;
|
|
189
|
+
mounted = true;
|
|
190
|
+
if (options.observeResize !== false && typeof ResizeObserver !== "undefined") {
|
|
191
|
+
observer = new ResizeObserver((entries) => {
|
|
192
|
+
const width = entries[0]?.contentRect.width ?? options.container.clientWidth;
|
|
193
|
+
if (width > 0) updateBreakpoint(width);
|
|
194
|
+
else updateBreakpointFromContainer();
|
|
195
|
+
});
|
|
196
|
+
observer.observe(options.container);
|
|
197
|
+
}
|
|
198
|
+
updateBreakpointFromContainer();
|
|
199
|
+
render();
|
|
200
|
+
},
|
|
201
|
+
update(next) {
|
|
202
|
+
document = next;
|
|
203
|
+
render();
|
|
204
|
+
},
|
|
205
|
+
setBreakpoint(id) {
|
|
206
|
+
manualBreakpoint = id;
|
|
207
|
+
if (id) activeBreakpoint = id;
|
|
208
|
+
else updateBreakpointFromContainer();
|
|
209
|
+
render();
|
|
210
|
+
},
|
|
211
|
+
getBreakpoint() {
|
|
212
|
+
return activeBreakpoint;
|
|
213
|
+
},
|
|
214
|
+
getContainer() {
|
|
215
|
+
return options.container;
|
|
216
|
+
},
|
|
217
|
+
destroy() {
|
|
218
|
+
if (!mounted) return;
|
|
219
|
+
observer?.disconnect();
|
|
220
|
+
observer = void 0;
|
|
221
|
+
for (const handle of handles.values()) handle.destroy?.();
|
|
222
|
+
handles.clear();
|
|
223
|
+
for (const shell of shells.values()) if (ownedShells.has(shell)) shell.remove();
|
|
224
|
+
shells.clear();
|
|
225
|
+
ownedShells.clear();
|
|
226
|
+
options.container.style.removeProperty("display");
|
|
227
|
+
options.container.style.removeProperty("grid-template-columns");
|
|
228
|
+
options.container.style.removeProperty("grid-auto-rows");
|
|
229
|
+
options.container.style.removeProperty("gap");
|
|
230
|
+
options.container.style.removeProperty("direction");
|
|
231
|
+
options.container.style.removeProperty("min-height");
|
|
232
|
+
options.container.style.removeProperty("max-height");
|
|
233
|
+
mounted = false;
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
//#endregion
|
|
238
|
+
exports.createLayoutRuntime = createLayoutRuntime;
|
|
239
|
+
exports.resolveGridStyle = resolveGridStyle;
|
|
240
|
+
exports.resolveItemStyle = resolveItemStyle;
|
|
241
|
+
});
|
|
242
|
+
/*! End of @composy/layout-runtime | Powered by @composy/builder */
|
|
243
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/styles.ts","../src/material-renderer.ts","../src/runtime.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 = `${document.grid.minRows * (grid.rowSize + grid.gap) - grid.gap}px`\n if (document.grid.maxRows !== undefined)\n style.maxHeight = `${document.grid.maxRows * (grid.rowSize + grid.gap) - grid.gap}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","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","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":";;;;;;;;;;;;;;;CAkBO,SAAS,iBACd,UACA,cACiB;EACjB,MAAM,QAAA,GAAA,qBAAA,iBAAA,CAAwB,UAAU,YAAY;EACpD,MAAM,cAAA,GAAA,qBAAA,cAAA,CAA2B,UAAU,YAAY;EACvD,MAAM,QAAyB;GAC7B,SAAS;GACT,qBAAqB,UAAU,KAAK,QAAO;GAC3C,cAAc,GAAG,KAAK,QAAO;GAC7B,KAAK,GAAG,KAAK,IAAG;GAChB,WAAW,KAAK;EAClB;EACA,IAAI,SAAS,KAAK,YAAY,KAAA,GAC5B,MAAM,YAAY,GAAG,SAAS,KAAK,WAAW,KAAK,UAAU,KAAK,OAAO,KAAK,IAAG;EACnF,IAAI,SAAS,KAAK,YAAY,KAAA,GAC5B,MAAM,YAAY,GAAG,SAAS,KAAK,WAAW,KAAK,UAAU,KAAK,OAAO,KAAK,IAAG;EACnF,IAAI,YAAY,aACd,MAAM,sBAAsB,UAAU,KAAK,QAAO;EACpD,OAAO;CACT;CAEO,SAAS,iBACd,MACA,cACwB;EACxB,MAAM,YAAY,KAAK,OAAO;EAC9B,IAAI,CAAC,WAAW,OAAO,EAAE,SAAS,OAAO;EACzC,OAAO;GACL,YAAY,GAAG,UAAU,IAAI,EAAC,UAAW,UAAU;GACnD,SAAS,GAAG,UAAU,IAAI,EAAC,UAAW,UAAU;GAChD,SAAS,UAAU,SAAS,SAAS;EACvC;CACF;;;CCxCO,SAAS,wBACd,QAC8B;EAC9B,IAAI,CAAC,QAAQ,OAAO,KAAA;EACpB,IAAI,MAAM,QAAQ,MAAM,GAAG,QAAA,GAAA,qBAAA,uBAAA,CAA8B,MAAM;EAC/D,OAAO;CACT;CAEO,SAAS,sBACd,SACA,WACgC;EAChC,MAAM,WAAW,WAAW,IAAI,QAAQ,KAAK,IAAI;EACjD,MAAM,WACJ,UAAU,WAAW,gBAAgB,UAAU,WAAW;EAC5D,IAAI,CAAC,YAAY,CAAC,UAAU,OAAO,KAAA;EAEnC,MAAM,OAAO,QAAQ,MAAM;EAC3B,IAAI;EACJ,IAAI,SAAS,SACX,OAAO,KAAK,cAAc,SAAS,OAAO;OAC5C,IAAW,SAAS,QAAQ;GAC1B,MAAM,WAAW,SAAS,OAAO;IAC/B,MAAM,QAAQ;IACd,WAAW,QAAQ;IACnB,cAAc,QAAQ;GACxB,CAAC;GACD,IAAI,oBAAoB,MAAM,OAAO;QAAA,IAC5B,OAAO,aAAa,UAAU;IACrC,MAAM,UAAU,KAAK,cAAc,KAAK;IACxC,QAAQ,YAAY,eAAe,QAAQ;IAC3C,OAAO;GACT;EACF;EACA,IAAI,CAAC,MAAM,OAAO,KAAA;EAElB,MAAM,cAAc,SAA2B;GAC7C,IAAI,EAAE,gBAAgB,cAAc;GACpC,KAAA,MAAW,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,SAAS,CAAC,CAAC,GAAG;IAC3D,IAAI;KACA,KAA4C,OAAO;IACvD,QAAQ,CAGR;IACA,IACE,UAAU,QACV,UAAU,KAAA,KACV,OAAO,UAAU,YACjB,OAAO,UAAU;SAEb,UAAU,QAAQ,UAAU,KAAA,GAC9B,KAAK,QAAQ,OAAO,UAAU,GAAG,OAAO,KAAK,UAAU,KAAK;IAAA,OACzD,KAAK,aAAa,KAAK,OAAO,KAAK,CAAC;GAC7C;EACF;EACA,WAAW,QAAQ,IAAI;EACvB,QAAQ,MAAM,gBAAgB,IAAI;EAClC,OAAO;GACL,OAAO,MAAM;IACX,WAAW,KAAK,IAAI;GACtB;GACA,UAAU;IACR,IAAI,MAAM,eAAe,QAAQ,OAAO,QAAQ,MAAM,YAAY,IAAI;GACxE;EACF;CACF;CAEA,SAAS,UAAU,OAAuB;EACxC,OAAO,MAAM,QAAQ,iBAAiB,EAAE;CAC1C;CAEA,SAAS,eAAe,OAAuB;EAC7C,OAAO,MACJ,QAAQ,0CAA0C,EAAE,CAAA,CACpD,QAAQ,mDAAmD,EAAE,CAAA,CAC7D,QAAQ,uDAAuD,EAAE;CACtE;;;CCzCO,SAAS,oBACd,SACe;EACf,IAAI,WAAW,QAAQ;EACvB,IAAI,UAAU;EACd,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,MAAM,0BAAU,IAAI,IAAgC;EACpD,MAAM,yBAAS,IAAI,IAAyB;EAC5C,MAAM,8BAAc,IAAI,IAAiB;EACzC,MAAM,YAAY,wBAAwB,QAAQ,SAAS;EAE3D,SAAS,SAAe;GACtB,IAAI,CAAC,WAAW,CAAC,UAAU;GAC3B,MAAM,UAAA,GAAA,qBAAA,uBAAA,CAAgC,QAAQ;GAC9C,IAAI,CAAC,OAAO,OAAO;IACjB,QAAQ,UACN,IAAI,MAAM,OAAO,OAAO,EAAC,EAAG,WAAW,yBAAyB,CAClE;IACA;GACF;GACA,MAAM,eACJ,oBAAoB,oBAAoB,SAAS;GACnD,mBAAmB;GACnB,MAAM,YAAY,iBAAiB,UAAU,YAAY;GACzD,OAAO,OAAO,QAAQ,UAAU,OAAO,SAAS;GAChD,MAAM,aAAa,IAAI,IAAI,SAAS,MAAM,KAAI,SAAQ,KAAK,EAAE,CAAC;GAC9D,KAAA,MAAW,CAAC,IAAI,WAAW,SAAS;IAClC,IAAI,WAAW,IAAI,EAAE,GAAG;IACxB,IAAI;KACF,OAAO,UAAU;IACnB,UAAE;KACA,QAAQ,OAAO,EAAE;KACjB,MAAM,QAAQ,OAAO,IAAI,EAAE;KAC3B,IAAI,SAAS,YAAY,IAAI,KAAK,GAAG,MAAM,OAAO;KAClD,IAAI,OAAO,YAAY,OAAO,KAAK;KACnC,OAAO,OAAO,EAAE;IAClB;GACF;GACA,KAAA,MAAW,QAAQ,SAAS,OAAO;IACjC,MAAM,YAAY,KAAK,OAAO;IAC9B,IAAI,QAAQ,OAAO,IAAI,KAAK,EAAE;IAC9B,IAAI,CAAC,OAAO;KACV,QAAQ,QAAQ,eAAe,IAAI;KACnC,IAAI,CAAC,OAAO;MACV,QAAQ,QAAQ,UAAU,cAAc,cAAc,KAAK;MAC3D,MAAM,YAAY;MAClB,MAAM,QAAQ,eAAe,KAAK;MAClC,MAAM,QAAQ,aAAa,KAAK;MAChC,YAAY,IAAI,KAAK;MACrB,QAAQ,UAAU,YAAY,KAAK;KACrC;KACA,OAAO,IAAI,KAAK,IAAI,KAAK;IAC3B;IACA,OAAO,OAAO,MAAM,OAAO,iBAAiB,MAAM,YAAY,CAAC;IAC/D,IAAI,CAAC,WAAW;IAChB,MAAM,UAAU;KAAE;KAAO;KAAM;KAAW;IAAa;IACvD,IAAI;KACF,IAAI,SAAS,QAAQ,IAAI,KAAK,EAAE;KAChC,IAAI,CAAC,QAAQ;MACX,SAAS,QAAQ,aACZ,QAAQ,WAAW,OAAO,KAAK,CAAC,IAChC,sBAAsB,SAAS,SAAS,KAAK,CAAC;MACnD,QAAQ,IAAI,KAAK,IAAI,MAAM;KAC7B,OAAO,OAAO,SAAS,OAAO;IAChC,SAAS,OAAO;KACd,QAAQ,UAAU,KAAK;IACzB;GACF;EACF;EAEA,SAAS,iBAAiB,OAAqB;GAC7C,IAAI,CAAC,YAAY,kBAAkB;GACnC,MAAM,QAAA,GAAA,qBAAA,kBAAA,CAAyB,UAAU,KAAK,CAAA,CAAE;GAChD,IAAI,SAAS,kBAAkB;IAC7B,MAAM,WAAW;IACjB,mBAAmB;IACnB,QAAQ,qBAAqB,IAAI;IACjC,IAAI,aAAa,MAAM,OAAO;GAChC;EACF;EAEA,SAAS,gCAAsC;GAC7C,IAAI,CAAC,YAAY,kBAAkB;GACnC,MAAM,QACJ,QAAQ,UAAU,sBAAsB,CAAA,CAAE,SAC1C,QAAQ,UAAU;GAIpB,IAAI,QAAQ,GAAG,iBAAiB,KAAK;QAAA,IAC5B,qBAAqB,SAAS,qBAAqB;IAC1D,MAAM,WAAW;IACjB,mBAAmB,SAAS;IAC5B,IAAI,UAAU,QAAQ,qBAAqB,gBAAgB;GAC7D;EACF;EAEA,OAAO;GACL,QAAQ;IACN,IAAI,SAAS;IACb,UAAU;IACV,IACE,QAAQ,kBAAkB,SAC1B,OAAO,mBAAmB,aAC1B;KACA,WAAW,IAAI,gBAAe,YAAW;MACvC,MAAM,QACJ,QAAQ,EAAC,EAAG,YAAY,SAAS,QAAQ,UAAU;MACrD,IAAI,QAAQ,GAAG,iBAAiB,KAAK;WAChC,8BAA8B;KACrC,CAAC;KACD,SAAS,QAAQ,QAAQ,SAAS;IACpC;IACA,8BAA8B;IAC9B,OAAO;GACT;GACA,OAAO,MAAM;IACX,WAAW;IACX,OAAO;GACT;GACA,cAAc,IAAI;IAChB,mBAAmB;IACnB,IAAI,IAAI,mBAAmB;SACtB,8BAA8B;IACnC,OAAO;GACT;GACA,gBAAgB;IACd,OAAO;GACT;GACA,eAAe;IACb,OAAO,QAAQ;GACjB;GACA,UAAU;IACR,IAAI,CAAC,SAAS;IACd,UAAU,WAAW;IACrB,WAAW,KAAA;IACX,KAAA,MAAW,UAAU,QAAQ,OAAO,GAAG,OAAO,UAAU;IACxD,QAAQ,MAAM;IACd,KAAA,MAAW,SAAS,OAAO,OAAO,GAChC,IAAI,YAAY,IAAI,KAAK,GAAG,MAAM,OAAO;IAC3C,OAAO,MAAM;IACb,YAAY,MAAM;IAClB,QAAQ,UAAU,MAAM,eAAe,SAAS;IAChD,QAAQ,UAAU,MAAM,eAAe,uBAAuB;IAC9D,QAAQ,UAAU,MAAM,eAAe,gBAAgB;IACvD,QAAQ,UAAU,MAAM,eAAe,KAAK;IAC5C,QAAQ,UAAU,MAAM,eAAe,WAAW;IAClD,QAAQ,UAAU,MAAM,eAAe,YAAY;IACnD,QAAQ,UAAU,MAAM,eAAe,YAAY;IACnD,UAAU;GACZ;EACF;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";(function(global,factory){typeof exports==="object"&&typeof module!=="undefined"?factory(exports,require("@composy/layout-core")):typeof define==="function"&&define.amd?define(["exports","@composy/layout-core"],factory):(global=typeof globalThis!=="undefined"?globalThis:global||self,factory(global.LDesignLayoutRuntime={},global.ComposyLayoutCore));})(this,function(exports,_composy_layout_core){Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function resolveGridStyle(document,breakpointId){const grid=(0,_composy_layout_core.getEffectiveGrid)(document,breakpointId);const breakpoint=(0,_composy_layout_core.getBreakpoint)(document,breakpointId);const style={display:"grid",gridTemplateColumns:`repeat(${grid.columns},minmax(0,1fr))`,gridAutoRows:`${grid.rowSize}px`,gap:`${grid.gap}px`,direction:grid.direction};if(document.grid.minRows!==void 0)style.minHeight=`${document.grid.minRows*(grid.rowSize+grid.gap)-grid.gap}px`;if(document.grid.maxRows!==void 0)style.maxHeight=`${document.grid.maxRows*(grid.rowSize+grid.gap)-grid.gap}px`;if(breakpoint?.canvasWidth)style.gridTemplateColumns=`repeat(${grid.columns},minmax(0,1fr))`;return style;}function resolveItemStyle(node,breakpointId){const placement=node.layout[breakpointId];if(!placement)return{display:"none"};return{gridColumn:`${placement.x+1}/span ${placement.w}`,gridRow:`${placement.y+1}/span ${placement.h}`,display:placement.hidden?"none":""};}function normalizeMaterialSource(source){if(!source)return void 0;if(Array.isArray(source))return(0,_composy_layout_core.createMaterialRegistry)(source);return source;}function renderRuntimeMaterial(context,materials){const material=materials?.get(context.item.type);const renderer=material?.renderers?.webcomponent??material?.renderers?.html;if(!material||!renderer)return void 0;const host=context.shell.ownerDocument;let node;if(renderer.tagName)node=host.createElement(renderer.tagName);else if(renderer.render){const rendered=renderer.render({item:context.item,placement:context.placement,breakpointId:context.breakpointId});if(rendered instanceof Node)node=rendered;else if(typeof rendered==="string"){const wrapper=host.createElement("div");wrapper.innerHTML=sanitizeMarkup(rendered);node=wrapper;}}if(!node)return void 0;const applyProps=(item)=>{if(!(node instanceof HTMLElement))return;for(const[key,value]of Object.entries(item.props??{})){try{node[key]=value;}catch{}if(value===null||value===void 0||typeof value==="object"||typeof value==="function"){if(value!==null&&value!==void 0)node.dataset[`prop${toDataKey(key)}`]=JSON.stringify(value);}else node.setAttribute(key,String(value));}};applyProps(context.item);context.shell.replaceChildren(node);return{update(next){applyProps(next.item);},destroy(){if(node?.parentNode===context.shell)context.shell.removeChild(node);}};}function toDataKey(value){return value.replace(/[^a-zA-Z0-9]/g,"");}function sanitizeMarkup(value){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,"");}function createLayoutRuntime(options){let document=options.document;let mounted=false;let manualBreakpoint;let activeBreakpoint;let observer;const handles=new Map();const shells=new Map();const ownedShells=new Set();const materials=normalizeMaterialSource(options.materials);function render(){if(!mounted||!document)return;const result=(0,_composy_layout_core.validateLayoutDocument)(document);if(!result.valid){options.onError?.(new Error(result.issues[0]?.message??"Invalid layout document"));return;}const breakpointId=manualBreakpoint??activeBreakpoint??document.defaultBreakpointId;activeBreakpoint=breakpointId;const gridStyle=resolveGridStyle(document,breakpointId);Object.assign(options.container.style,gridStyle);const currentIds=new Set(document.items.map((item)=>item.id));for(const[id,handle]of handles){if(currentIds.has(id))continue;try{handle.destroy?.();}finally{handles.delete(id);const shell=shells.get(id);if(shell&&ownedShells.has(shell))shell.remove();if(shell)ownedShells.delete(shell);shells.delete(id);}}for(const item of document.items){const placement=item.layout[breakpointId];let shell=shells.get(item.id);if(!shell){shell=options.getItemShell?.(item);if(!shell){shell=options.container.ownerDocument.createElement("div");shell.className="ldesign-layout-item";shell.dataset.layoutItemId=item.id;shell.dataset.layoutType=item.type;ownedShells.add(shell);options.container.appendChild(shell);}shells.set(item.id,shell);}Object.assign(shell.style,resolveItemStyle(item,breakpointId));if(!placement)continue;const context={shell,item,placement,breakpointId};try{let handle=handles.get(item.id);if(!handle){handle=options.renderItem?options.renderItem(context)??{}:renderRuntimeMaterial(context,materials)??{};handles.set(item.id,handle);}else handle.update?.(context);}catch(error){options.onError?.(error);}}}function updateBreakpoint(width){if(!document||manualBreakpoint)return;const next=(0,_composy_layout_core.resolveBreakpoint)(document,width).id;if(next!==activeBreakpoint){const previous=activeBreakpoint;activeBreakpoint=next;options.onBreakpointChange?.(next);if(previous!==next)render();}}function updateBreakpointFromContainer(){if(!document||manualBreakpoint)return;const width=options.container.getBoundingClientRect().width||options.container.clientWidth;if(width>0)updateBreakpoint(width);else if(activeBreakpoint!==document.defaultBreakpointId){const previous=activeBreakpoint;activeBreakpoint=document.defaultBreakpointId;if(previous)options.onBreakpointChange?.(activeBreakpoint);}}return{mount(){if(mounted)return;mounted=true;if(options.observeResize!==false&&typeof ResizeObserver!=="undefined"){observer=new ResizeObserver((entries)=>{const width=entries[0]?.contentRect.width??options.container.clientWidth;if(width>0)updateBreakpoint(width);else updateBreakpointFromContainer();});observer.observe(options.container);}updateBreakpointFromContainer();render();},update(next){document=next;render();},setBreakpoint(id){manualBreakpoint=id;if(id)activeBreakpoint=id;else updateBreakpointFromContainer();render();},getBreakpoint(){return activeBreakpoint;},getContainer(){return options.container;},destroy(){if(!mounted)return;observer?.disconnect();observer=void 0;for(const handle of handles.values())handle.destroy?.();handles.clear();for(const shell of shells.values())if(ownedShells.has(shell))shell.remove();shells.clear();ownedShells.clear();options.container.style.removeProperty("display");options.container.style.removeProperty("grid-template-columns");options.container.style.removeProperty("grid-auto-rows");options.container.style.removeProperty("gap");options.container.style.removeProperty("direction");options.container.style.removeProperty("min-height");options.container.style.removeProperty("max-height");mounted=false;}};}exports.createLayoutRuntime=createLayoutRuntime;exports.resolveGridStyle=resolveGridStyle;exports.resolveItemStyle=resolveItemStyle;});
|
package/es/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAA;AACxB,cAAc,WAAW,CAAA"}
|
package/es/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* ***********************************
|
|
3
|
+
* @composy/layout-runtime v0.0.1 *
|
|
4
|
+
* Built with rollup *
|
|
5
|
+
* Build time: 2024-08-17 11:42:14 *
|
|
6
|
+
* Build mode: production *
|
|
7
|
+
* Minified: No *
|
|
8
|
+
* ***********************************
|
|
9
|
+
*/
|
|
10
|
+
import { resolveGridStyle, resolveItemStyle } from "./styles.js";
|
|
11
|
+
import { createLayoutRuntime } from "./runtime.js";
|
|
12
|
+
export { createLayoutRuntime, resolveGridStyle, resolveItemStyle };
|
|
13
|
+
/*! End of @composy/layout-runtime | Powered by @composy/builder */
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type MaterialDefinition, type MaterialRegistry } from '@composy/layout-core';
|
|
2
|
+
import type { LayoutRenderContext, LayoutRenderHandle } from './runtime';
|
|
3
|
+
export type RuntimeMaterialSource = MaterialRegistry | readonly MaterialDefinition[] | undefined;
|
|
4
|
+
export declare function normalizeMaterialSource(source: RuntimeMaterialSource): MaterialRegistry | undefined;
|
|
5
|
+
export declare function renderRuntimeMaterial(context: LayoutRenderContext, materials: MaterialRegistry | undefined): LayoutRenderHandle | undefined;
|
|
6
|
+
//# sourceMappingURL=material-renderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"material-renderer.d.ts","sourceRoot":"","sources":["../src/material-renderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACtB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,KAAK,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAA;AAExE,MAAM,MAAM,qBAAqB,GAC/B,gBAAgB,GAAG,SAAS,kBAAkB,EAAE,GAAG,SAAS,CAAA;AAE9D,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,qBAAqB,GAC5B,gBAAgB,GAAG,SAAS,CAI9B;AAED,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,mBAAmB,EAC5B,SAAS,EAAE,gBAAgB,GAAG,SAAS,GACtC,kBAAkB,GAAG,SAAS,CAuDhC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* ***********************************
|
|
3
|
+
* @composy/layout-runtime v0.0.1 *
|
|
4
|
+
* Built with rollup *
|
|
5
|
+
* Build time: 2024-08-17 11:42:14 *
|
|
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 (rendered instanceof Node) node = rendered;
|
|
31
|
+
else if (typeof rendered === "string") {
|
|
32
|
+
const wrapper = host.createElement("div");
|
|
33
|
+
wrapper.innerHTML = sanitizeMarkup(rendered);
|
|
34
|
+
node = wrapper;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (!node) return void 0;
|
|
38
|
+
const applyProps = (item) => {
|
|
39
|
+
if (!(node instanceof HTMLElement)) return;
|
|
40
|
+
for (const [key, value] of Object.entries(item.props ?? {})) {
|
|
41
|
+
try {
|
|
42
|
+
node[key] = value;
|
|
43
|
+
} catch {}
|
|
44
|
+
if (value === null || value === void 0 || typeof value === "object" || typeof value === "function") {
|
|
45
|
+
if (value !== null && value !== void 0) node.dataset[`prop${toDataKey(key)}`] = JSON.stringify(value);
|
|
46
|
+
} else node.setAttribute(key, String(value));
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
applyProps(context.item);
|
|
50
|
+
context.shell.replaceChildren(node);
|
|
51
|
+
return {
|
|
52
|
+
update(next) {
|
|
53
|
+
applyProps(next.item);
|
|
54
|
+
},
|
|
55
|
+
destroy() {
|
|
56
|
+
if (node?.parentNode === context.shell) context.shell.removeChild(node);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function toDataKey(value) {
|
|
61
|
+
return value.replace(/[^a-zA-Z0-9]/g, "");
|
|
62
|
+
}
|
|
63
|
+
function sanitizeMarkup(value) {
|
|
64
|
+
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, "");
|
|
65
|
+
}
|
|
66
|
+
//#endregion
|
|
67
|
+
export { normalizeMaterialSource, renderRuntimeMaterial };
|
|
68
|
+
/*! End of @composy/layout-runtime | Powered by @composy/builder */
|
|
69
|
+
//# 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 (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,OAAO,uBAAuB,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"}
|
package/es/runtime.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { BreakpointId, LayoutDocument, LayoutNode, LayoutPlacement, MaterialDefinition, MaterialRegistry } from '@composy/layout-core';
|
|
2
|
+
import { resolveGridStyle, resolveItemStyle } from './styles';
|
|
3
|
+
export interface LayoutRenderContext {
|
|
4
|
+
shell: HTMLElement;
|
|
5
|
+
item: LayoutNode;
|
|
6
|
+
placement: LayoutPlacement;
|
|
7
|
+
breakpointId: BreakpointId;
|
|
8
|
+
}
|
|
9
|
+
export interface LayoutRenderHandle {
|
|
10
|
+
update?(context: LayoutRenderContext): void;
|
|
11
|
+
destroy?(): void;
|
|
12
|
+
}
|
|
13
|
+
export interface LayoutRuntimeOptions {
|
|
14
|
+
container: HTMLElement;
|
|
15
|
+
document?: LayoutDocument;
|
|
16
|
+
/** Lets a host adapter provide an existing light-DOM item shell. */
|
|
17
|
+
getItemShell?: (item: LayoutNode) => HTMLElement | undefined;
|
|
18
|
+
renderItem?: (context: LayoutRenderContext) => LayoutRenderHandle | void;
|
|
19
|
+
/** Material registry used by the framework-neutral default renderer. */
|
|
20
|
+
materials?: MaterialRegistry | readonly MaterialDefinition[];
|
|
21
|
+
observeResize?: boolean;
|
|
22
|
+
onBreakpointChange?: (id: BreakpointId) => void;
|
|
23
|
+
onError?: (error: unknown) => void;
|
|
24
|
+
}
|
|
25
|
+
export interface LayoutRuntime {
|
|
26
|
+
mount(): void;
|
|
27
|
+
update(document: LayoutDocument): void;
|
|
28
|
+
setBreakpoint(id?: BreakpointId): void;
|
|
29
|
+
getBreakpoint(): BreakpointId | undefined;
|
|
30
|
+
getContainer(): HTMLElement;
|
|
31
|
+
destroy(): void;
|
|
32
|
+
}
|
|
33
|
+
export declare function createLayoutRuntime(options: LayoutRuntimeOptions): LayoutRuntime;
|
|
34
|
+
export { resolveGridStyle, resolveItemStyle };
|
|
35
|
+
//# sourceMappingURL=runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,cAAc,EACd,UAAU,EACV,eAAe,EACf,kBAAkB,EAClB,gBAAgB,EACjB,MAAM,sBAAsB,CAAA;AAE7B,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AAM7D,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,WAAW,CAAA;IAClB,IAAI,EAAE,UAAU,CAAA;IAChB,SAAS,EAAE,eAAe,CAAA;IAC1B,YAAY,EAAE,YAAY,CAAA;CAC3B;AACD,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,CAAC,OAAO,EAAE,mBAAmB,GAAG,IAAI,CAAA;IAC3C,OAAO,CAAC,IAAI,IAAI,CAAA;CACjB;AACD,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,WAAW,CAAA;IACtB,QAAQ,CAAC,EAAE,cAAc,CAAA;IACzB,oEAAoE;IACpE,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,WAAW,GAAG,SAAS,CAAA;IAC5D,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,mBAAmB,KAAK,kBAAkB,GAAG,IAAI,CAAA;IACxE,wEAAwE;IACxE,SAAS,CAAC,EAAE,gBAAgB,GAAG,SAAS,kBAAkB,EAAE,CAAA;IAC5D,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,kBAAkB,CAAC,EAAE,CAAC,EAAE,EAAE,YAAY,KAAK,IAAI,CAAA;IAC/C,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;CACnC;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,IAAI,IAAI,CAAA;IACb,MAAM,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAA;IACtC,aAAa,CAAC,EAAE,CAAC,EAAE,YAAY,GAAG,IAAI,CAAA;IACtC,aAAa,IAAI,YAAY,GAAG,SAAS,CAAA;IACzC,YAAY,IAAI,WAAW,CAAA;IAC3B,OAAO,IAAI,IAAI,CAAA;CAChB;AAED,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,oBAAoB,GAC5B,aAAa,CAwJf;AAED,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,CAAA"}
|
package/es/runtime.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* ***********************************
|
|
3
|
+
* @composy/layout-runtime v0.0.1 *
|
|
4
|
+
* Built with rollup *
|
|
5
|
+
* Build time: 2024-08-17 11:42:14 *
|
|
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
|
+
try {
|
|
39
|
+
handle.destroy?.();
|
|
40
|
+
} finally {
|
|
41
|
+
handles.delete(id);
|
|
42
|
+
const shell = shells.get(id);
|
|
43
|
+
if (shell && ownedShells.has(shell)) shell.remove();
|
|
44
|
+
if (shell) ownedShells.delete(shell);
|
|
45
|
+
shells.delete(id);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
for (const item of document.items) {
|
|
49
|
+
const placement = item.layout[breakpointId];
|
|
50
|
+
let shell = shells.get(item.id);
|
|
51
|
+
if (!shell) {
|
|
52
|
+
shell = options.getItemShell?.(item);
|
|
53
|
+
if (!shell) {
|
|
54
|
+
shell = options.container.ownerDocument.createElement("div");
|
|
55
|
+
shell.className = "ldesign-layout-item";
|
|
56
|
+
shell.dataset.layoutItemId = item.id;
|
|
57
|
+
shell.dataset.layoutType = item.type;
|
|
58
|
+
ownedShells.add(shell);
|
|
59
|
+
options.container.appendChild(shell);
|
|
60
|
+
}
|
|
61
|
+
shells.set(item.id, shell);
|
|
62
|
+
}
|
|
63
|
+
Object.assign(shell.style, resolveItemStyle(item, breakpointId));
|
|
64
|
+
if (!placement) continue;
|
|
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 updateBreakpoint(width) {
|
|
83
|
+
if (!document || manualBreakpoint) return;
|
|
84
|
+
const next = resolveBreakpoint(document, width).id;
|
|
85
|
+
if (next !== activeBreakpoint) {
|
|
86
|
+
const previous = activeBreakpoint;
|
|
87
|
+
activeBreakpoint = next;
|
|
88
|
+
options.onBreakpointChange?.(next);
|
|
89
|
+
if (previous !== next) render();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function updateBreakpointFromContainer() {
|
|
93
|
+
if (!document || manualBreakpoint) return;
|
|
94
|
+
const width = options.container.getBoundingClientRect().width || options.container.clientWidth;
|
|
95
|
+
if (width > 0) updateBreakpoint(width);
|
|
96
|
+
else if (activeBreakpoint !== document.defaultBreakpointId) {
|
|
97
|
+
const previous = activeBreakpoint;
|
|
98
|
+
activeBreakpoint = document.defaultBreakpointId;
|
|
99
|
+
if (previous) options.onBreakpointChange?.(activeBreakpoint);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
mount() {
|
|
104
|
+
if (mounted) return;
|
|
105
|
+
mounted = true;
|
|
106
|
+
if (options.observeResize !== false && typeof ResizeObserver !== "undefined") {
|
|
107
|
+
observer = new ResizeObserver((entries) => {
|
|
108
|
+
const width = entries[0]?.contentRect.width ?? options.container.clientWidth;
|
|
109
|
+
if (width > 0) updateBreakpoint(width);
|
|
110
|
+
else updateBreakpointFromContainer();
|
|
111
|
+
});
|
|
112
|
+
observer.observe(options.container);
|
|
113
|
+
}
|
|
114
|
+
updateBreakpointFromContainer();
|
|
115
|
+
render();
|
|
116
|
+
},
|
|
117
|
+
update(next) {
|
|
118
|
+
document = next;
|
|
119
|
+
render();
|
|
120
|
+
},
|
|
121
|
+
setBreakpoint(id) {
|
|
122
|
+
manualBreakpoint = id;
|
|
123
|
+
if (id) activeBreakpoint = id;
|
|
124
|
+
else updateBreakpointFromContainer();
|
|
125
|
+
render();
|
|
126
|
+
},
|
|
127
|
+
getBreakpoint() {
|
|
128
|
+
return activeBreakpoint;
|
|
129
|
+
},
|
|
130
|
+
getContainer() {
|
|
131
|
+
return options.container;
|
|
132
|
+
},
|
|
133
|
+
destroy() {
|
|
134
|
+
if (!mounted) return;
|
|
135
|
+
observer?.disconnect();
|
|
136
|
+
observer = void 0;
|
|
137
|
+
for (const handle of handles.values()) handle.destroy?.();
|
|
138
|
+
handles.clear();
|
|
139
|
+
for (const shell of shells.values()) if (ownedShells.has(shell)) shell.remove();
|
|
140
|
+
shells.clear();
|
|
141
|
+
ownedShells.clear();
|
|
142
|
+
options.container.style.removeProperty("display");
|
|
143
|
+
options.container.style.removeProperty("grid-template-columns");
|
|
144
|
+
options.container.style.removeProperty("grid-auto-rows");
|
|
145
|
+
options.container.style.removeProperty("gap");
|
|
146
|
+
options.container.style.removeProperty("direction");
|
|
147
|
+
options.container.style.removeProperty("min-height");
|
|
148
|
+
options.container.style.removeProperty("max-height");
|
|
149
|
+
mounted = false;
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
//#endregion
|
|
154
|
+
export { createLayoutRuntime, resolveGridStyle, resolveItemStyle };
|
|
155
|
+
/*! End of @composy/layout-runtime | Powered by @composy/builder */
|
|
156
|
+
//# 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 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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"}
|
package/es/styles.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { BreakpointId, LayoutDocument, LayoutNode } from '@composy/layout-core';
|
|
2
|
+
export interface GridStyleResult {
|
|
3
|
+
display: 'grid';
|
|
4
|
+
gridTemplateColumns: string;
|
|
5
|
+
gridAutoRows: string;
|
|
6
|
+
gap: string;
|
|
7
|
+
direction: 'ltr' | 'rtl';
|
|
8
|
+
minHeight?: string;
|
|
9
|
+
maxHeight?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function resolveGridStyle(document: LayoutDocument, breakpointId: BreakpointId): GridStyleResult;
|
|
12
|
+
export declare function resolveItemStyle(node: LayoutNode, breakpointId: BreakpointId): Record<string, string>;
|
|
13
|
+
//# sourceMappingURL=styles.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../src/styles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,cAAc,EACd,UAAU,EAEX,MAAM,sBAAsB,CAAA;AAG7B,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAA;IACf,mBAAmB,EAAE,MAAM,CAAA;IAC3B,YAAY,EAAE,MAAM,CAAA;IACpB,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,KAAK,GAAG,KAAK,CAAA;IACxB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,cAAc,EACxB,YAAY,EAAE,YAAY,GACzB,eAAe,CAiBjB;AAED,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,UAAU,EAChB,YAAY,EAAE,YAAY,GACzB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAQxB"}
|
package/es/styles.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* ***********************************
|
|
3
|
+
* @composy/layout-runtime v0.0.1 *
|
|
4
|
+
* Built with rollup *
|
|
5
|
+
* Build time: 2024-08-17 11:42:14 *
|
|
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 = `${document.grid.minRows * (grid.rowSize + grid.gap) - grid.gap}px`;
|
|
23
|
+
if (document.grid.maxRows !== void 0) style.maxHeight = `${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
|
package/es/styles.js.map
ADDED
|
@@ -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 = `${document.grid.minRows * (grid.rowSize + grid.gap) - grid.gap}px`\n if (document.grid.maxRows !== undefined)\n style.maxHeight = `${document.grid.maxRows * (grid.rowSize + grid.gap) - grid.gap}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,SAAS,KAAK,WAAW,KAAK,UAAU,KAAK,OAAO,KAAK,IAAG;CACnF,IAAI,SAAS,KAAK,YAAY,KAAA,GAC5B,MAAM,YAAY,GAAG,SAAS,KAAK,WAAW,KAAK,UAAU,KAAK,OAAO,KAAK,IAAG;CACnF,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
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* ***********************************
|
|
3
|
+
* @composy/layout-runtime v0.0.1 *
|
|
4
|
+
* Built with rollup *
|
|
5
|
+
* Build time: 2024-08-17 11:42:14 *
|
|
6
|
+
* Build mode: production *
|
|
7
|
+
* Minified: No *
|
|
8
|
+
* ***********************************
|
|
9
|
+
*/
|
|
10
|
+
"use strict";
|
|
11
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
12
|
+
const require_styles = require("./styles.cjs");
|
|
13
|
+
const require_runtime = require("./runtime.cjs");
|
|
14
|
+
exports.createLayoutRuntime = require_runtime.createLayoutRuntime;
|
|
15
|
+
exports.resolveGridStyle = require_styles.resolveGridStyle;
|
|
16
|
+
exports.resolveItemStyle = require_styles.resolveItemStyle;
|
|
17
|
+
/*! End of @composy/layout-runtime | Powered by @composy/builder */
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* ***********************************
|
|
3
|
+
* @composy/layout-runtime v0.0.1 *
|
|
4
|
+
* Built with rollup *
|
|
5
|
+
* Build time: 2024-08-17 11:42:14 *
|
|
6
|
+
* Build mode: production *
|
|
7
|
+
* Minified: No *
|
|
8
|
+
* ***********************************
|
|
9
|
+
*/
|
|
10
|
+
"use strict";
|
|
11
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
12
|
+
let _composy_layout_core = require("@composy/layout-core");
|
|
13
|
+
//#region src/material-renderer.ts
|
|
14
|
+
function normalizeMaterialSource(source) {
|
|
15
|
+
if (!source) return void 0;
|
|
16
|
+
if (Array.isArray(source)) return (0, _composy_layout_core.createMaterialRegistry)(source);
|
|
17
|
+
return source;
|
|
18
|
+
}
|
|
19
|
+
function renderRuntimeMaterial(context, materials) {
|
|
20
|
+
const material = materials?.get(context.item.type);
|
|
21
|
+
const renderer = material?.renderers?.webcomponent ?? material?.renderers?.html;
|
|
22
|
+
if (!material || !renderer) return void 0;
|
|
23
|
+
const host = context.shell.ownerDocument;
|
|
24
|
+
let node;
|
|
25
|
+
if (renderer.tagName) node = host.createElement(renderer.tagName);
|
|
26
|
+
else if (renderer.render) {
|
|
27
|
+
const rendered = renderer.render({
|
|
28
|
+
item: context.item,
|
|
29
|
+
placement: context.placement,
|
|
30
|
+
breakpointId: context.breakpointId
|
|
31
|
+
});
|
|
32
|
+
if (rendered instanceof Node) node = rendered;
|
|
33
|
+
else if (typeof rendered === "string") {
|
|
34
|
+
const wrapper = host.createElement("div");
|
|
35
|
+
wrapper.innerHTML = sanitizeMarkup(rendered);
|
|
36
|
+
node = wrapper;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (!node) return void 0;
|
|
40
|
+
const applyProps = (item) => {
|
|
41
|
+
if (!(node instanceof HTMLElement)) return;
|
|
42
|
+
for (const [key, value] of Object.entries(item.props ?? {})) {
|
|
43
|
+
try {
|
|
44
|
+
node[key] = value;
|
|
45
|
+
} catch {}
|
|
46
|
+
if (value === null || value === void 0 || typeof value === "object" || typeof value === "function") {
|
|
47
|
+
if (value !== null && value !== void 0) node.dataset[`prop${toDataKey(key)}`] = JSON.stringify(value);
|
|
48
|
+
} else node.setAttribute(key, String(value));
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
applyProps(context.item);
|
|
52
|
+
context.shell.replaceChildren(node);
|
|
53
|
+
return {
|
|
54
|
+
update(next) {
|
|
55
|
+
applyProps(next.item);
|
|
56
|
+
},
|
|
57
|
+
destroy() {
|
|
58
|
+
if (node?.parentNode === context.shell) context.shell.removeChild(node);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function toDataKey(value) {
|
|
63
|
+
return value.replace(/[^a-zA-Z0-9]/g, "");
|
|
64
|
+
}
|
|
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, "");
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
exports.normalizeMaterialSource = normalizeMaterialSource;
|
|
70
|
+
exports.renderRuntimeMaterial = renderRuntimeMaterial;
|
|
71
|
+
/*! End of @composy/layout-runtime | Powered by @composy/builder */
|
|
72
|
+
//# sourceMappingURL=material-renderer.cjs.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type MaterialDefinition, type MaterialRegistry } from '@composy/layout-core';
|
|
2
|
+
import type { LayoutRenderContext, LayoutRenderHandle } from './runtime';
|
|
3
|
+
export type RuntimeMaterialSource = MaterialRegistry | readonly MaterialDefinition[] | undefined;
|
|
4
|
+
export declare function normalizeMaterialSource(source: RuntimeMaterialSource): MaterialRegistry | undefined;
|
|
5
|
+
export declare function renderRuntimeMaterial(context: LayoutRenderContext, materials: MaterialRegistry | undefined): LayoutRenderHandle | undefined;
|
|
6
|
+
//# sourceMappingURL=material-renderer.d.ts.map
|
package/lib/package.json
ADDED
package/lib/runtime.cjs
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* ***********************************
|
|
3
|
+
* @composy/layout-runtime v0.0.1 *
|
|
4
|
+
* Built with rollup *
|
|
5
|
+
* Build time: 2024-08-17 11:42:14 *
|
|
6
|
+
* Build mode: production *
|
|
7
|
+
* Minified: No *
|
|
8
|
+
* ***********************************
|
|
9
|
+
*/
|
|
10
|
+
"use strict";
|
|
11
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
12
|
+
const require_styles = require("./styles.cjs");
|
|
13
|
+
const require_material_renderer = require("./material-renderer.cjs");
|
|
14
|
+
let _composy_layout_core = require("@composy/layout-core");
|
|
15
|
+
//#region src/runtime.ts
|
|
16
|
+
function createLayoutRuntime(options) {
|
|
17
|
+
let document = options.document;
|
|
18
|
+
let mounted = false;
|
|
19
|
+
let manualBreakpoint;
|
|
20
|
+
let activeBreakpoint;
|
|
21
|
+
let observer;
|
|
22
|
+
const handles = /* @__PURE__ */ new Map();
|
|
23
|
+
const shells = /* @__PURE__ */ new Map();
|
|
24
|
+
const ownedShells = /* @__PURE__ */ new Set();
|
|
25
|
+
const materials = require_material_renderer.normalizeMaterialSource(options.materials);
|
|
26
|
+
function render() {
|
|
27
|
+
if (!mounted || !document) return;
|
|
28
|
+
const result = (0, _composy_layout_core.validateLayoutDocument)(document);
|
|
29
|
+
if (!result.valid) {
|
|
30
|
+
options.onError?.(new Error(result.issues[0]?.message ?? "Invalid layout document"));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const breakpointId = manualBreakpoint ?? activeBreakpoint ?? document.defaultBreakpointId;
|
|
34
|
+
activeBreakpoint = breakpointId;
|
|
35
|
+
const gridStyle = require_styles.resolveGridStyle(document, breakpointId);
|
|
36
|
+
Object.assign(options.container.style, gridStyle);
|
|
37
|
+
const currentIds = new Set(document.items.map((item) => item.id));
|
|
38
|
+
for (const [id, handle] of handles) {
|
|
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
|
+
}
|
|
49
|
+
}
|
|
50
|
+
for (const item of document.items) {
|
|
51
|
+
const placement = item.layout[breakpointId];
|
|
52
|
+
let shell = shells.get(item.id);
|
|
53
|
+
if (!shell) {
|
|
54
|
+
shell = options.getItemShell?.(item);
|
|
55
|
+
if (!shell) {
|
|
56
|
+
shell = options.container.ownerDocument.createElement("div");
|
|
57
|
+
shell.className = "ldesign-layout-item";
|
|
58
|
+
shell.dataset.layoutItemId = item.id;
|
|
59
|
+
shell.dataset.layoutType = item.type;
|
|
60
|
+
ownedShells.add(shell);
|
|
61
|
+
options.container.appendChild(shell);
|
|
62
|
+
}
|
|
63
|
+
shells.set(item.id, shell);
|
|
64
|
+
}
|
|
65
|
+
Object.assign(shell.style, require_styles.resolveItemStyle(item, breakpointId));
|
|
66
|
+
if (!placement) continue;
|
|
67
|
+
const context = {
|
|
68
|
+
shell,
|
|
69
|
+
item,
|
|
70
|
+
placement,
|
|
71
|
+
breakpointId
|
|
72
|
+
};
|
|
73
|
+
try {
|
|
74
|
+
let handle = handles.get(item.id);
|
|
75
|
+
if (!handle) {
|
|
76
|
+
handle = options.renderItem ? options.renderItem(context) ?? {} : require_material_renderer.renderRuntimeMaterial(context, materials) ?? {};
|
|
77
|
+
handles.set(item.id, handle);
|
|
78
|
+
} else handle.update?.(context);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
options.onError?.(error);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function updateBreakpoint(width) {
|
|
85
|
+
if (!document || manualBreakpoint) return;
|
|
86
|
+
const next = (0, _composy_layout_core.resolveBreakpoint)(document, width).id;
|
|
87
|
+
if (next !== activeBreakpoint) {
|
|
88
|
+
const previous = activeBreakpoint;
|
|
89
|
+
activeBreakpoint = next;
|
|
90
|
+
options.onBreakpointChange?.(next);
|
|
91
|
+
if (previous !== next) render();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function updateBreakpointFromContainer() {
|
|
95
|
+
if (!document || manualBreakpoint) return;
|
|
96
|
+
const width = options.container.getBoundingClientRect().width || options.container.clientWidth;
|
|
97
|
+
if (width > 0) updateBreakpoint(width);
|
|
98
|
+
else if (activeBreakpoint !== document.defaultBreakpointId) {
|
|
99
|
+
const previous = activeBreakpoint;
|
|
100
|
+
activeBreakpoint = document.defaultBreakpointId;
|
|
101
|
+
if (previous) options.onBreakpointChange?.(activeBreakpoint);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
mount() {
|
|
106
|
+
if (mounted) return;
|
|
107
|
+
mounted = true;
|
|
108
|
+
if (options.observeResize !== false && typeof ResizeObserver !== "undefined") {
|
|
109
|
+
observer = new ResizeObserver((entries) => {
|
|
110
|
+
const width = entries[0]?.contentRect.width ?? options.container.clientWidth;
|
|
111
|
+
if (width > 0) updateBreakpoint(width);
|
|
112
|
+
else updateBreakpointFromContainer();
|
|
113
|
+
});
|
|
114
|
+
observer.observe(options.container);
|
|
115
|
+
}
|
|
116
|
+
updateBreakpointFromContainer();
|
|
117
|
+
render();
|
|
118
|
+
},
|
|
119
|
+
update(next) {
|
|
120
|
+
document = next;
|
|
121
|
+
render();
|
|
122
|
+
},
|
|
123
|
+
setBreakpoint(id) {
|
|
124
|
+
manualBreakpoint = id;
|
|
125
|
+
if (id) activeBreakpoint = id;
|
|
126
|
+
else updateBreakpointFromContainer();
|
|
127
|
+
render();
|
|
128
|
+
},
|
|
129
|
+
getBreakpoint() {
|
|
130
|
+
return activeBreakpoint;
|
|
131
|
+
},
|
|
132
|
+
getContainer() {
|
|
133
|
+
return options.container;
|
|
134
|
+
},
|
|
135
|
+
destroy() {
|
|
136
|
+
if (!mounted) return;
|
|
137
|
+
observer?.disconnect();
|
|
138
|
+
observer = void 0;
|
|
139
|
+
for (const handle of handles.values()) handle.destroy?.();
|
|
140
|
+
handles.clear();
|
|
141
|
+
for (const shell of shells.values()) if (ownedShells.has(shell)) shell.remove();
|
|
142
|
+
shells.clear();
|
|
143
|
+
ownedShells.clear();
|
|
144
|
+
options.container.style.removeProperty("display");
|
|
145
|
+
options.container.style.removeProperty("grid-template-columns");
|
|
146
|
+
options.container.style.removeProperty("grid-auto-rows");
|
|
147
|
+
options.container.style.removeProperty("gap");
|
|
148
|
+
options.container.style.removeProperty("direction");
|
|
149
|
+
options.container.style.removeProperty("min-height");
|
|
150
|
+
options.container.style.removeProperty("max-height");
|
|
151
|
+
mounted = false;
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
//#endregion
|
|
156
|
+
exports.createLayoutRuntime = createLayoutRuntime;
|
|
157
|
+
exports.resolveGridStyle = require_styles.resolveGridStyle;
|
|
158
|
+
exports.resolveItemStyle = require_styles.resolveItemStyle;
|
|
159
|
+
/*! End of @composy/layout-runtime | Powered by @composy/builder */
|
|
160
|
+
//# sourceMappingURL=runtime.cjs.map
|
|
@@ -0,0 +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":";;;;;;;;;;;;;;;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,0BAAA,wBAAwB,QAAQ,SAAS;CAE3D,SAAS,SAAe;EACtB,IAAI,CAAC,WAAW,CAAC,UAAU;EAC3B,MAAM,UAAA,GAAA,qBAAA,uBAAA,CAAgC,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,eAAA,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,IAAI;IACF,OAAO,UAAU;GACnB,UAAE;IACA,QAAQ,OAAO,EAAE;IACjB,MAAM,QAAQ,OAAO,IAAI,EAAE;IAC3B,IAAI,SAAS,YAAY,IAAI,KAAK,GAAG,MAAM,OAAO;IAClD,IAAI,OAAO,YAAY,OAAO,KAAK;IACnC,OAAO,OAAO,EAAE;GAClB;EACF;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,eAAA,iBAAiB,MAAM,YAAY,CAAC;GAC/D,IAAI,CAAC,WAAW;GAChB,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,0BAAA,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,iBAAiB,OAAqB;EAC7C,IAAI,CAAC,YAAY,kBAAkB;EACnC,MAAM,QAAA,GAAA,qBAAA,kBAAA,CAAyB,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,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,UAAU,QAAQ,OAAO,GAAG,OAAO,UAAU;GACxD,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,UAAU;EACZ;CACF;AACF"}
|
package/lib/runtime.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { BreakpointId, LayoutDocument, LayoutNode, LayoutPlacement, MaterialDefinition, MaterialRegistry } from '@composy/layout-core';
|
|
2
|
+
import { resolveGridStyle, resolveItemStyle } from './styles';
|
|
3
|
+
export interface LayoutRenderContext {
|
|
4
|
+
shell: HTMLElement;
|
|
5
|
+
item: LayoutNode;
|
|
6
|
+
placement: LayoutPlacement;
|
|
7
|
+
breakpointId: BreakpointId;
|
|
8
|
+
}
|
|
9
|
+
export interface LayoutRenderHandle {
|
|
10
|
+
update?(context: LayoutRenderContext): void;
|
|
11
|
+
destroy?(): void;
|
|
12
|
+
}
|
|
13
|
+
export interface LayoutRuntimeOptions {
|
|
14
|
+
container: HTMLElement;
|
|
15
|
+
document?: LayoutDocument;
|
|
16
|
+
/** Lets a host adapter provide an existing light-DOM item shell. */
|
|
17
|
+
getItemShell?: (item: LayoutNode) => HTMLElement | undefined;
|
|
18
|
+
renderItem?: (context: LayoutRenderContext) => LayoutRenderHandle | void;
|
|
19
|
+
/** Material registry used by the framework-neutral default renderer. */
|
|
20
|
+
materials?: MaterialRegistry | readonly MaterialDefinition[];
|
|
21
|
+
observeResize?: boolean;
|
|
22
|
+
onBreakpointChange?: (id: BreakpointId) => void;
|
|
23
|
+
onError?: (error: unknown) => void;
|
|
24
|
+
}
|
|
25
|
+
export interface LayoutRuntime {
|
|
26
|
+
mount(): void;
|
|
27
|
+
update(document: LayoutDocument): void;
|
|
28
|
+
setBreakpoint(id?: BreakpointId): void;
|
|
29
|
+
getBreakpoint(): BreakpointId | undefined;
|
|
30
|
+
getContainer(): HTMLElement;
|
|
31
|
+
destroy(): void;
|
|
32
|
+
}
|
|
33
|
+
export declare function createLayoutRuntime(options: LayoutRuntimeOptions): LayoutRuntime;
|
|
34
|
+
export { resolveGridStyle, resolveItemStyle };
|
|
35
|
+
//# sourceMappingURL=runtime.d.ts.map
|
package/lib/styles.cjs
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* ***********************************
|
|
3
|
+
* @composy/layout-runtime v0.0.1 *
|
|
4
|
+
* Built with rollup *
|
|
5
|
+
* Build time: 2024-08-17 11:42:14 *
|
|
6
|
+
* Build mode: production *
|
|
7
|
+
* Minified: No *
|
|
8
|
+
* ***********************************
|
|
9
|
+
*/
|
|
10
|
+
"use strict";
|
|
11
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
12
|
+
let _composy_layout_core = require("@composy/layout-core");
|
|
13
|
+
//#region src/styles.ts
|
|
14
|
+
function resolveGridStyle(document, breakpointId) {
|
|
15
|
+
const grid = (0, _composy_layout_core.getEffectiveGrid)(document, breakpointId);
|
|
16
|
+
const breakpoint = (0, _composy_layout_core.getBreakpoint)(document, breakpointId);
|
|
17
|
+
const style = {
|
|
18
|
+
display: "grid",
|
|
19
|
+
gridTemplateColumns: `repeat(${grid.columns}, minmax(0, 1fr))`,
|
|
20
|
+
gridAutoRows: `${grid.rowSize}px`,
|
|
21
|
+
gap: `${grid.gap}px`,
|
|
22
|
+
direction: grid.direction
|
|
23
|
+
};
|
|
24
|
+
if (document.grid.minRows !== void 0) style.minHeight = `${document.grid.minRows * (grid.rowSize + grid.gap) - grid.gap}px`;
|
|
25
|
+
if (document.grid.maxRows !== void 0) style.maxHeight = `${document.grid.maxRows * (grid.rowSize + grid.gap) - grid.gap}px`;
|
|
26
|
+
if (breakpoint?.canvasWidth) style.gridTemplateColumns = `repeat(${grid.columns}, minmax(0, 1fr))`;
|
|
27
|
+
return style;
|
|
28
|
+
}
|
|
29
|
+
function resolveItemStyle(node, breakpointId) {
|
|
30
|
+
const placement = node.layout[breakpointId];
|
|
31
|
+
if (!placement) return { display: "none" };
|
|
32
|
+
return {
|
|
33
|
+
gridColumn: `${placement.x + 1} / span ${placement.w}`,
|
|
34
|
+
gridRow: `${placement.y + 1} / span ${placement.h}`,
|
|
35
|
+
display: placement.hidden ? "none" : ""
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
exports.resolveGridStyle = resolveGridStyle;
|
|
40
|
+
exports.resolveItemStyle = resolveItemStyle;
|
|
41
|
+
/*! End of @composy/layout-runtime | Powered by @composy/builder */
|
|
42
|
+
//# sourceMappingURL=styles.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"styles.cjs","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 = `${document.grid.minRows * (grid.rowSize + grid.gap) - grid.gap}px`\n if (document.grid.maxRows !== undefined)\n style.maxHeight = `${document.grid.maxRows * (grid.rowSize + grid.gap) - grid.gap}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,QAAA,GAAA,qBAAA,iBAAA,CAAwB,UAAU,YAAY;CACpD,MAAM,cAAA,GAAA,qBAAA,cAAA,CAA2B,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,SAAS,KAAK,WAAW,KAAK,UAAU,KAAK,OAAO,KAAK,IAAG;CACnF,IAAI,SAAS,KAAK,YAAY,KAAA,GAC5B,MAAM,YAAY,GAAG,SAAS,KAAK,WAAW,KAAK,UAAU,KAAK,OAAO,KAAK,IAAG;CACnF,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/styles.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { BreakpointId, LayoutDocument, LayoutNode } from '@composy/layout-core';
|
|
2
|
+
export interface GridStyleResult {
|
|
3
|
+
display: 'grid';
|
|
4
|
+
gridTemplateColumns: string;
|
|
5
|
+
gridAutoRows: string;
|
|
6
|
+
gap: string;
|
|
7
|
+
direction: 'ltr' | 'rtl';
|
|
8
|
+
minHeight?: string;
|
|
9
|
+
maxHeight?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function resolveGridStyle(document: LayoutDocument, breakpointId: BreakpointId): GridStyleResult;
|
|
12
|
+
export declare function resolveItemStyle(node: LayoutNode, breakpointId: BreakpointId): Record<string, string>;
|
|
13
|
+
//# sourceMappingURL=styles.d.ts.map
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@composy/layout-runtime",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "DOM-free CSS Grid style resolvers and runtime for LDesign Layout",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./es/index.d.ts",
|
|
11
|
+
"import": "./es/index.js",
|
|
12
|
+
"require": "./lib/index.cjs",
|
|
13
|
+
"default": "./es/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./package.json": "./package.json"
|
|
16
|
+
},
|
|
17
|
+
"main": "./lib/index.cjs",
|
|
18
|
+
"module": "./es/index.js",
|
|
19
|
+
"types": "./es/index.d.ts",
|
|
20
|
+
"unpkg": "./dist/index.min.js",
|
|
21
|
+
"jsdelivr": "./dist/index.min.js",
|
|
22
|
+
"files": [
|
|
23
|
+
"README.md",
|
|
24
|
+
"LICENSE",
|
|
25
|
+
"CHANGELOG.md",
|
|
26
|
+
"es",
|
|
27
|
+
"lib",
|
|
28
|
+
"dist"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "ldesign-builder build",
|
|
32
|
+
"type-check": "tsc --noEmit",
|
|
33
|
+
"test:run": "ltesting run:unit"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@composy/layout-core": "^0.0.1"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@composy/builder": "^0.0.1",
|
|
40
|
+
"@composy/testing": "^0.0.1",
|
|
41
|
+
"@composy/layout-shared-test": "^0.0.1",
|
|
42
|
+
"typescript": "5.9.3",
|
|
43
|
+
"vitest": "4.1.10"
|
|
44
|
+
},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"registry": "http://npm.longrise.cn:6286/"
|
|
47
|
+
},
|
|
48
|
+
"typings": "./es/index.d.ts",
|
|
49
|
+
"browser": "./dist/index.js"
|
|
50
|
+
}
|