@composy/layout-vue 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.
Files changed (109) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/LICENSE +21 -0
  3. package/README.md +75 -0
  4. package/dist/index.css +2383 -0
  5. package/dist/index.js +2665 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/index.min.css +1 -0
  8. package/dist/index.min.js +1 -0
  9. package/es/components/LGrid.d.ts +3 -0
  10. package/es/components/LGrid.d.ts.map +1 -0
  11. package/es/components/LGrid.js +91 -0
  12. package/es/components/LGrid.js.map +1 -0
  13. package/es/components/LGridItem.d.ts +52 -0
  14. package/es/components/LGridItem.d.ts.map +1 -0
  15. package/es/components/LGridItem.js +52 -0
  16. package/es/components/LGridItem.js.map +1 -0
  17. package/es/components/LLayoutEditor.d.ts +49 -0
  18. package/es/components/LLayoutEditor.d.ts.map +1 -0
  19. package/es/components/LLayoutEditor.js +81 -0
  20. package/es/components/LLayoutEditor.js.map +1 -0
  21. package/es/components/LLayoutWorkspace.js +17 -0
  22. package/es/components/LLayoutWorkspace.js.map +1 -0
  23. package/es/components/LLayoutWorkspace.vue_vue_type_script_setup_true_lang.js +2217 -0
  24. package/es/components/LLayoutWorkspace.vue_vue_type_script_setup_true_lang.js.map +1 -0
  25. package/es/components/LMaterialDragSource.d.ts +55 -0
  26. package/es/components/LMaterialDragSource.d.ts.map +1 -0
  27. package/es/components/LMaterialDragSource.js +92 -0
  28. package/es/components/LMaterialDragSource.js.map +1 -0
  29. package/es/components/LMaterialRenderer.d.ts +63 -0
  30. package/es/components/LMaterialRenderer.d.ts.map +1 -0
  31. package/es/components/LMaterialRenderer.js +48 -0
  32. package/es/components/LMaterialRenderer.js.map +1 -0
  33. package/es/components/LMetricCard.d.ts +29 -0
  34. package/es/components/LMetricCard.d.ts.map +1 -0
  35. package/es/components/LMetricCard.js +39 -0
  36. package/es/components/LMetricCard.js.map +1 -0
  37. package/es/components/LTrendCard.d.ts +17 -0
  38. package/es/components/LTrendCard.d.ts.map +1 -0
  39. package/es/components/LTrendCard.js +36 -0
  40. package/es/components/LTrendCard.js.map +1 -0
  41. package/es/components/index.d.ts +10 -0
  42. package/es/components/index.d.ts.map +1 -0
  43. package/es/components/index.js +19 -0
  44. package/es/components/material-renderer.d.ts +26 -0
  45. package/es/components/material-renderer.d.ts.map +1 -0
  46. package/es/components/material-renderer.js +96 -0
  47. package/es/components/material-renderer.js.map +1 -0
  48. package/es/composables/index.d.ts +2 -0
  49. package/es/composables/index.d.ts.map +1 -0
  50. package/es/composables/index.js +12 -0
  51. package/es/composables/useLayoutEditor.d.ts +3 -0
  52. package/es/composables/useLayoutEditor.d.ts.map +1 -0
  53. package/es/composables/useLayoutEditor.js +22 -0
  54. package/es/composables/useLayoutEditor.js.map +1 -0
  55. package/es/index.d.ts +7 -0
  56. package/es/index.d.ts.map +1 -0
  57. package/es/index.js +22 -0
  58. package/es/style/css.js +1 -0
  59. package/es/style/index.js +1 -0
  60. package/es/style/index.less +1 -0
  61. package/es/style/workspace.css +2370 -0
  62. package/es/types.d.ts +26 -0
  63. package/es/types.d.ts.map +1 -0
  64. package/es/types.js +10 -0
  65. package/lib/components/LGrid.cjs +92 -0
  66. package/lib/components/LGrid.cjs.map +1 -0
  67. package/lib/components/LGrid.d.ts +3 -0
  68. package/lib/components/LGridItem.cjs +53 -0
  69. package/lib/components/LGridItem.cjs.map +1 -0
  70. package/lib/components/LGridItem.d.ts +52 -0
  71. package/lib/components/LLayoutEditor.cjs +82 -0
  72. package/lib/components/LLayoutEditor.cjs.map +1 -0
  73. package/lib/components/LLayoutEditor.d.ts +49 -0
  74. package/lib/components/LLayoutWorkspace.cjs +21 -0
  75. package/lib/components/LLayoutWorkspace.cjs.map +1 -0
  76. package/lib/components/LLayoutWorkspace.vue_vue_type_script_setup_true_lang.cjs +2217 -0
  77. package/lib/components/LLayoutWorkspace.vue_vue_type_script_setup_true_lang.cjs.map +1 -0
  78. package/lib/components/LMaterialDragSource.cjs +93 -0
  79. package/lib/components/LMaterialDragSource.cjs.map +1 -0
  80. package/lib/components/LMaterialDragSource.d.ts +55 -0
  81. package/lib/components/LMaterialRenderer.cjs +49 -0
  82. package/lib/components/LMaterialRenderer.cjs.map +1 -0
  83. package/lib/components/LMaterialRenderer.d.ts +63 -0
  84. package/lib/components/LMetricCard.cjs +40 -0
  85. package/lib/components/LMetricCard.cjs.map +1 -0
  86. package/lib/components/LMetricCard.d.ts +29 -0
  87. package/lib/components/LTrendCard.cjs +37 -0
  88. package/lib/components/LTrendCard.cjs.map +1 -0
  89. package/lib/components/LTrendCard.d.ts +17 -0
  90. package/lib/components/index.cjs +27 -0
  91. package/lib/components/index.d.ts +10 -0
  92. package/lib/components/material-renderer.cjs +99 -0
  93. package/lib/components/material-renderer.cjs.map +1 -0
  94. package/lib/components/material-renderer.d.ts +26 -0
  95. package/lib/composables/index.cjs +13 -0
  96. package/lib/composables/index.d.ts +2 -0
  97. package/lib/composables/useLayoutEditor.cjs +23 -0
  98. package/lib/composables/useLayoutEditor.cjs.map +1 -0
  99. package/lib/composables/useLayoutEditor.d.ts +3 -0
  100. package/lib/index.cjs +29 -0
  101. package/lib/index.d.ts +7 -0
  102. package/lib/package.json +3 -0
  103. package/lib/style/css.js +1 -0
  104. package/lib/style/index.css +2 -0
  105. package/lib/style/index.js +1 -0
  106. package/lib/style/workspace.css +2378 -0
  107. package/lib/types.cjs +11 -0
  108. package/lib/types.d.ts +26 -0
  109. package/package.json +117 -0
package/dist/index.js ADDED
@@ -0,0 +1,2665 @@
1
+ /*!
2
+ * ***********************************
3
+ * @composy/layout-vue v0.0.1 *
4
+ * Built with rollup *
5
+ * Build time: 2024-08-17 11:47:09 *
6
+ * Build mode: production *
7
+ * Minified: No *
8
+ * ***********************************
9
+ */
10
+ (function(global, factory) {
11
+ typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("vue"), require("@composy/layout-runtime"), require("@composy/layout-core"), require("@composy/layout-editor"), require("gridstack"), require("gridstack/dist/gridstack.min.css"), require("lucide"), require("lucide-vue-next"), require("@composy/layout-compiler"), require("@composy/layout-gridstack")) : typeof define === "function" && define.amd ? define([
12
+ "exports",
13
+ "vue",
14
+ "@composy/layout-runtime",
15
+ "@composy/layout-core",
16
+ "@composy/layout-editor",
17
+ "gridstack",
18
+ "gridstack/dist/gridstack.min.css",
19
+ "lucide",
20
+ "lucide-vue-next",
21
+ "@composy/layout-compiler",
22
+ "@composy/layout-gridstack"
23
+ ], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.LDesignLayoutVue = {}, global.Vue, global.ComposyLayoutRuntime, global.ComposyLayoutCore, global.ComposyLayoutEditor, global.Gridstack, global["gridstack/dist/gridstack"].min.css, global.Lucide, global.LucideVueNext, global.ComposyLayoutCompiler, global.ComposyLayoutGridstack));
24
+ })(this, function(exports, vue, _composy_layout_runtime, _composy_layout_core, _composy_layout_editor, gridstack, gridstack_dist_gridstack_min_css, lucide, lucide_vue_next, _composy_layout_compiler, _composy_layout_gridstack) {
25
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
26
+ //#region src/components/material-renderer.ts
27
+ function resolveRegistry(source) {
28
+ if (!source) return void 0;
29
+ if (Array.isArray(source)) return (0, _composy_layout_core.createMaterialRegistry)(source);
30
+ return source;
31
+ }
32
+ function resolveMaterialDefinition(materials, type) {
33
+ return resolveRegistry(materials)?.get(type);
34
+ }
35
+ function renderMaterialFallback(materials, item, placement, breakpointId) {
36
+ const material = resolveMaterialDefinition(materials, item.type);
37
+ const context = {
38
+ item,
39
+ placement,
40
+ breakpointId
41
+ };
42
+ const vueRenderer = material?.renderers?.vue;
43
+ if (vueRenderer?.component) return (0, vue.h)(vueRenderer.component, {
44
+ ...item.props ?? {},
45
+ ...vueRenderer.tagName ? { is: vueRenderer.tagName } : {}
46
+ });
47
+ const webComponentRenderer = material?.renderers?.webcomponent;
48
+ if (webComponentRenderer?.tagName) return (0, vue.h)(webComponentRenderer.tagName, {
49
+ ...item.props ?? {},
50
+ materialProps: { ...item.props ?? {} }
51
+ });
52
+ const htmlRenderer = material?.renderers?.html;
53
+ if (htmlRenderer?.render) {
54
+ const rendered = htmlRenderer.render(context);
55
+ if (typeof rendered === "string") return (0, vue.h)("div", { innerHTML: sanitizeMarkup(rendered) });
56
+ return rendered;
57
+ }
58
+ if (htmlRenderer?.tagName) return (0, vue.h)(htmlRenderer.tagName, { ...item.props ?? {} });
59
+ }
60
+ function renderMaterialForTarget(materials, item, target, placement, breakpointId = "desktop") {
61
+ const material = resolveMaterialDefinition(materials, item.type);
62
+ const props = { ...item.props ?? {} };
63
+ if (target === "vue") {
64
+ const renderer = material?.renderers?.vue;
65
+ if (renderer?.component) return (0, vue.h)(renderer.component, props);
66
+ } else {
67
+ const renderer = material?.renderers?.webcomponent;
68
+ if (renderer?.tagName) return (0, vue.h)(renderer.tagName, {
69
+ ...props,
70
+ materialProps: props,
71
+ onVnodeMounted: (vnode) => {
72
+ const el = vnode.el;
73
+ if (el && "materialProps" in el) el.materialProps = props;
74
+ },
75
+ onVnodeUpdated: (vnode) => {
76
+ const el = vnode.el;
77
+ if (el && "materialProps" in el) el.materialProps = props;
78
+ }
79
+ });
80
+ }
81
+ const htmlRenderer = material?.renderers?.html;
82
+ if (htmlRenderer?.render) {
83
+ const rendered = htmlRenderer.render({
84
+ item,
85
+ placement: placement ?? item.layout[breakpointId] ?? {
86
+ x: 0,
87
+ y: 0,
88
+ w: 1,
89
+ h: 1
90
+ },
91
+ breakpointId
92
+ });
93
+ if (typeof rendered === "string") return (0, vue.h)("div", { innerHTML: sanitizeMarkup(rendered) });
94
+ return rendered;
95
+ }
96
+ if (htmlRenderer?.tagName) return (0, vue.h)(htmlRenderer.tagName, props);
97
+ return renderMaterialFallback(materials, item, placement ?? item.layout[breakpointId] ?? {
98
+ x: 0,
99
+ y: 0,
100
+ w: 1,
101
+ h: 1
102
+ }, breakpointId);
103
+ }
104
+ function sanitizeMarkup(value) {
105
+ 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, "");
106
+ }
107
+ //#endregion
108
+ //#region src/components/LGrid.ts
109
+ const LGrid = (0, vue.defineComponent)({
110
+ name: "LGrid",
111
+ props: {
112
+ document: {
113
+ type: Object,
114
+ required: true
115
+ },
116
+ breakpoint: {
117
+ type: String,
118
+ default: void 0
119
+ },
120
+ readonly: {
121
+ type: Boolean,
122
+ default: false
123
+ },
124
+ materials: {
125
+ type: [Object, Array],
126
+ default: void 0
127
+ },
128
+ target: {
129
+ type: String,
130
+ default: "vue"
131
+ }
132
+ },
133
+ setup(props, { slots, expose }) {
134
+ const root = (0, vue.ref)();
135
+ const activeBreakpoint = (0, vue.ref)(props.breakpoint ?? props.document.defaultBreakpointId);
136
+ const resolved = (0, vue.computed)(() => props.document.breakpoints.find((bp) => bp.id === activeBreakpoint.value) ?? props.document.breakpoints[0]);
137
+ const gridStyle = (0, vue.computed)(() => (0, _composy_layout_runtime.resolveGridStyle)(props.document, resolved.value.id));
138
+ const resize = () => {
139
+ if (props.breakpoint || !root.value) return;
140
+ const width = root.value.getBoundingClientRect().width;
141
+ const match = props.document.breakpoints.find((bp) => (bp.minWidth === void 0 || width >= bp.minWidth) && (bp.maxWidth === void 0 || width < bp.maxWidth));
142
+ activeBreakpoint.value = match?.id ?? props.document.defaultBreakpointId;
143
+ };
144
+ let observer;
145
+ (0, vue.onMounted)(() => {
146
+ if (typeof ResizeObserver !== "undefined" && root.value) {
147
+ observer = new ResizeObserver(resize);
148
+ observer.observe(root.value);
149
+ }
150
+ resize();
151
+ });
152
+ (0, vue.onBeforeUnmount)(() => observer?.disconnect());
153
+ (0, vue.watch)(() => props.breakpoint, (value) => {
154
+ activeBreakpoint.value = value ?? props.document.defaultBreakpointId;
155
+ });
156
+ expose({ getBreakpoint: () => activeBreakpoint.value });
157
+ return () => (0, vue.h)("div", {
158
+ ref: root,
159
+ class: "ldesign-layout-grid",
160
+ style: gridStyle.value,
161
+ dir: gridStyle.value.direction
162
+ }, props.document.items.map((item) => {
163
+ const placement = item.layout[activeBreakpoint.value];
164
+ if (!placement || placement.hidden) return null;
165
+ return (0, vue.h)("div", {
166
+ class: "ldesign-layout-item",
167
+ "data-layout-item-id": item.id,
168
+ "data-layout-type": item.type,
169
+ style: (0, _composy_layout_runtime.resolveItemStyle)(item, activeBreakpoint.value),
170
+ tabindex: props.readonly ? void 0 : 0
171
+ }, slots.item?.({
172
+ item,
173
+ placement,
174
+ breakpointId: activeBreakpoint.value
175
+ }) ?? slots.default?.({
176
+ item,
177
+ placement,
178
+ breakpointId: activeBreakpoint.value
179
+ }) ?? renderMaterialForTarget(props.materials, item, props.target, placement, activeBreakpoint.value));
180
+ }));
181
+ }
182
+ });
183
+ //#endregion
184
+ //#region src/components/LGridItem.ts
185
+ const LGridItem = (0, vue.defineComponent)({
186
+ name: "LGridItem",
187
+ props: {
188
+ item: {
189
+ type: Object,
190
+ required: true
191
+ },
192
+ placement: {
193
+ type: Object,
194
+ required: true
195
+ },
196
+ breakpointId: {
197
+ type: String,
198
+ required: true
199
+ },
200
+ materials: {
201
+ type: [Object, Array],
202
+ default: void 0
203
+ },
204
+ target: {
205
+ type: String,
206
+ default: "vue"
207
+ }
208
+ },
209
+ setup(props, { slots }) {
210
+ return () => (0, vue.h)("div", {
211
+ class: "ldesign-layout-item",
212
+ "data-layout-item-id": props.item.id,
213
+ "data-layout-type": props.item.type
214
+ }, slots.default?.({
215
+ item: props.item,
216
+ placement: props.placement,
217
+ breakpointId: props.breakpointId
218
+ }) ?? renderMaterialForTarget(props.materials, props.item, props.target, props.placement, props.breakpointId));
219
+ }
220
+ });
221
+ //#endregion
222
+ //#region src/components/LMaterialRenderer.ts
223
+ const LMaterialRenderer = (0, vue.defineComponent)({
224
+ name: "LMaterialRenderer",
225
+ props: {
226
+ item: {
227
+ type: Object,
228
+ required: true
229
+ },
230
+ materials: {
231
+ type: [Object, Array],
232
+ default: void 0
233
+ },
234
+ target: {
235
+ type: String,
236
+ default: "vue"
237
+ },
238
+ placement: {
239
+ type: Object,
240
+ default: void 0
241
+ },
242
+ breakpointId: {
243
+ type: String,
244
+ default: "desktop"
245
+ }
246
+ },
247
+ setup(props, { attrs }) {
248
+ return () => {
249
+ const rendered = renderMaterialForTarget(props.materials, props.item, props.target, props.placement, props.breakpointId);
250
+ if (!rendered) return null;
251
+ return (0, vue.h)(rendered, attrs);
252
+ };
253
+ }
254
+ });
255
+ //#endregion
256
+ //#region src/components/LLayoutEditor.ts
257
+ const LLayoutEditor = (0, vue.defineComponent)({
258
+ name: "LLayoutEditor",
259
+ props: {
260
+ document: {
261
+ type: Object,
262
+ required: true
263
+ },
264
+ engine: {
265
+ type: Object,
266
+ required: true
267
+ },
268
+ mode: {
269
+ type: String,
270
+ default: "edit"
271
+ },
272
+ preview: Object,
273
+ materials: {
274
+ type: Object,
275
+ default: void 0
276
+ }
277
+ },
278
+ emits: [
279
+ "update:document",
280
+ "change",
281
+ "ready"
282
+ ],
283
+ setup(props, { slots, emit, expose }) {
284
+ const editor = (0, vue.shallowRef)((0, _composy_layout_editor.createLayoutEditor)({
285
+ document: props.document,
286
+ engine: props.engine,
287
+ materials: props.materials,
288
+ preview: props.preview
289
+ }));
290
+ let lastEmittedDocument = JSON.stringify(props.document);
291
+ (0, vue.onMounted)(() => {
292
+ editor.value.on("document:change", (event) => {
293
+ lastEmittedDocument = JSON.stringify(event.document);
294
+ emit("update:document", event.document);
295
+ emit("change", event);
296
+ });
297
+ editor.value.mount();
298
+ editor.value.setMode(props.mode);
299
+ emit("ready", editor.value);
300
+ });
301
+ (0, vue.watch)(() => props.document, (value) => {
302
+ const next = JSON.stringify(value);
303
+ if (next !== lastEmittedDocument) {
304
+ editor.value.load(value);
305
+ lastEmittedDocument = next;
306
+ }
307
+ });
308
+ (0, vue.watch)(() => props.mode, (value) => editor.value.setMode(value));
309
+ const onKeydown = (event) => {
310
+ editor.value.handleKeyboard(event);
311
+ };
312
+ (0, vue.onBeforeUnmount)(() => editor.value.destroy());
313
+ expose({ editor });
314
+ return () => (0, vue.h)("div", {
315
+ class: "ldesign-layout-editor",
316
+ "data-layout-mode": props.mode,
317
+ "aria-keyshortcuts": "ArrowLeft ArrowRight ArrowUp ArrowDown Delete Control+C Control+V Control+X Control+D",
318
+ onKeydown
319
+ }, slots.default?.({ editor: editor.value }));
320
+ }
321
+ });
322
+ //#endregion
323
+ //#region src/components/LMetricCard.ts
324
+ const LMetricCard = (0, vue.defineComponent)({
325
+ name: "LMetricCard",
326
+ props: {
327
+ title: {
328
+ type: String,
329
+ default: "数据指标"
330
+ },
331
+ value: {
332
+ type: [String, Number],
333
+ default: void 0
334
+ }
335
+ },
336
+ setup(props) {
337
+ return () => (0, vue.h)("article", {
338
+ class: "layout-card",
339
+ "data-layout-card": ""
340
+ }, [
341
+ (0, vue.h)("div", { class: "layout-card__meta" }, [(0, vue.h)("span", { class: "layout-card__type" }, "数据指标"), (0, vue.h)("span", { class: "layout-card__status" }, "已配置")]),
342
+ (0, vue.h)("h3", { class: "layout-card__title" }, props.title),
343
+ props.value === void 0 ? null : (0, vue.h)("strong", { class: "layout-card__value" }, Number(props.value).toLocaleString("zh-CN")),
344
+ (0, vue.h)("div", { class: "layout-card__footer" }, [(0, vue.h)("span", null, "实时数据"), (0, vue.h)("span", { class: "layout-card__spark" }, "趋势稳定")])
345
+ ]);
346
+ }
347
+ });
348
+ //#endregion
349
+ //#region src/components/LTrendCard.ts
350
+ const LTrendCard = (0, vue.defineComponent)({
351
+ name: "LTrendCard",
352
+ props: { title: {
353
+ type: String,
354
+ default: "趋势图表"
355
+ } },
356
+ setup(props) {
357
+ return () => (0, vue.h)("article", {
358
+ class: "layout-card",
359
+ "data-layout-card": ""
360
+ }, [
361
+ (0, vue.h)("div", { class: "layout-card__meta" }, [(0, vue.h)("span", { class: "layout-card__type" }, "趋势图表"), (0, vue.h)("span", { class: "layout-card__status" }, "已配置")]),
362
+ (0, vue.h)("h3", { class: "layout-card__title" }, props.title),
363
+ (0, vue.h)("span", {
364
+ class: "layout-card__chart",
365
+ "aria-hidden": "true"
366
+ }, Array.from({ length: 5 }, (_, index) => (0, vue.h)("i", { key: index }))),
367
+ (0, vue.h)("div", { class: "layout-card__footer" }, [(0, vue.h)("span", null, "实时数据"), (0, vue.h)("span", { class: "layout-card__spark" }, "趋势稳定")])
368
+ ]);
369
+ }
370
+ });
371
+ //#endregion
372
+ //#region src/components/LLayoutWorkspace.vue?vue&type=script&setup=true&lang.ts
373
+ const _hoisted_1 = ["data-theme", "data-development-framework"];
374
+ const _hoisted_2 = { class: "topbar" };
375
+ const _hoisted_3 = { class: "brand" };
376
+ const _hoisted_4 = {
377
+ class: "brand-mark",
378
+ "aria-hidden": "true"
379
+ };
380
+ const _hoisted_5 = ["aria-expanded", "title"];
381
+ const _hoisted_6 = {
382
+ key: 0,
383
+ class: "custom-select__menu development-mode__menu",
384
+ role: "listbox"
385
+ };
386
+ const _hoisted_7 = ["aria-selected", "onClick"];
387
+ const _hoisted_8 = {
388
+ class: "topbar-center",
389
+ "aria-hidden": "true",
390
+ style: { "display": "none" }
391
+ };
392
+ const _hoisted_9 = {
393
+ class: "route-tabs",
394
+ "aria-label": "工作区模式"
395
+ };
396
+ const _hoisted_10 = [
397
+ "aria-current",
398
+ "aria-label",
399
+ "title",
400
+ "onClick"
401
+ ];
402
+ const _hoisted_11 = {
403
+ class: "breakpoint-tabs",
404
+ "aria-label": "画布尺寸"
405
+ };
406
+ const _hoisted_12 = [
407
+ "aria-pressed",
408
+ "aria-label",
409
+ "title",
410
+ "onClick"
411
+ ];
412
+ const _hoisted_13 = { class: "actions" };
413
+ const _hoisted_14 = ["disabled"];
414
+ const _hoisted_15 = ["disabled"];
415
+ const _hoisted_16 = ["disabled"];
416
+ const _hoisted_17 = [
417
+ "aria-label",
418
+ "title",
419
+ "disabled"
420
+ ];
421
+ const _hoisted_18 = [
422
+ "aria-label",
423
+ "title",
424
+ "disabled"
425
+ ];
426
+ const _hoisted_19 = [
427
+ "aria-label",
428
+ "title",
429
+ "disabled"
430
+ ];
431
+ const _hoisted_20 = ["aria-label", "title"];
432
+ const _hoisted_21 = [
433
+ "aria-pressed",
434
+ "aria-label",
435
+ "title"
436
+ ];
437
+ const _hoisted_22 = { class: "workspace" };
438
+ const _hoisted_23 = {
439
+ class: "materials panel-scroll",
440
+ "data-layout-panel": "materials",
441
+ "aria-label": "物料面板"
442
+ };
443
+ const _hoisted_24 = { class: "panel-heading" };
444
+ const _hoisted_25 = { class: "panel-count" };
445
+ const _hoisted_26 = { class: "material-toolbar" };
446
+ const _hoisted_27 = { class: "material-search" };
447
+ const _hoisted_28 = ["onKeydown"];
448
+ const _hoisted_29 = ["aria-expanded", "title"];
449
+ const _hoisted_30 = {
450
+ key: 0,
451
+ class: "custom-select__menu",
452
+ role: "listbox"
453
+ };
454
+ const _hoisted_31 = ["aria-selected"];
455
+ const _hoisted_32 = ["aria-selected", "onClick"];
456
+ const _hoisted_33 = { class: "material-result-summary" };
457
+ const _hoisted_34 = { key: 0 };
458
+ const _hoisted_35 = {
459
+ key: 0,
460
+ class: "material-empty"
461
+ };
462
+ const _hoisted_36 = [
463
+ "data-material-type",
464
+ "data-layout-material",
465
+ "data-layout-props",
466
+ "aria-label",
467
+ "onClick",
468
+ "onKeydown"
469
+ ];
470
+ const _hoisted_37 = {
471
+ class: "material-icon material-icon--metric",
472
+ "aria-hidden": "true"
473
+ };
474
+ const _hoisted_38 = { class: "material-copy" };
475
+ const _hoisted_39 = { class: "material-card-meta" };
476
+ const _hoisted_40 = { class: "material-size" };
477
+ const _hoisted_41 = { class: "material-renderer-badges" };
478
+ const _hoisted_42 = ["title"];
479
+ const _hoisted_43 = { class: "material-card-actions" };
480
+ const _hoisted_44 = ["onClick"];
481
+ const _hoisted_45 = {
482
+ class: "material-add",
483
+ "aria-hidden": "true"
484
+ };
485
+ const _hoisted_50 = {
486
+ class: "canvas panel-scroll",
487
+ "data-layout-panel": "canvas",
488
+ "aria-label": "布局画布"
489
+ };
490
+ const _hoisted_51 = {
491
+ class: "canvas-toolbar",
492
+ "aria-label": "画布操作"
493
+ };
494
+ const _hoisted_52 = ["aria-expanded"];
495
+ const _hoisted_53 = {
496
+ key: 0,
497
+ class: "custom-select__menu",
498
+ role: "listbox"
499
+ };
500
+ const _hoisted_54 = [
501
+ "aria-selected",
502
+ "aria-label",
503
+ "onClick"
504
+ ];
505
+ const _hoisted_55 = { class: "custom-select__option-meta" };
506
+ const _hoisted_56 = ["aria-label", "title"];
507
+ const _hoisted_57 = { class: "canvas-toolbar__group" };
508
+ const _hoisted_58 = {
509
+ class: "canvas-zoom-label",
510
+ "aria-live": "polite"
511
+ };
512
+ const _hoisted_59 = [
513
+ "data-canvas-width",
514
+ "data-canvas-height",
515
+ "data-canvas-orientation"
516
+ ];
517
+ const _hoisted_60 = {
518
+ class: "editor-stage",
519
+ "aria-label": "可视化编辑画布"
520
+ };
521
+ const _hoisted_61 = { class: "stage-header" };
522
+ const _hoisted_62 = { class: "stage-badge" };
523
+ const _hoisted_63 = {
524
+ class: "preview-stage",
525
+ "aria-label": "组件预览画布"
526
+ };
527
+ const _hoisted_64 = ["aria-label", "onClick"];
528
+ const _hoisted_65 = ["data-preview-framework"];
529
+ const _hoisted_66 = { class: "stage-header" };
530
+ const _hoisted_67 = { class: "stage-badge" };
531
+ const _hoisted_68 = ["data-preview-framework"];
532
+ const _hoisted_69 = { class: "stage-header" };
533
+ const _hoisted_70 = { class: "stage-badge" };
534
+ const _hoisted_71 = {
535
+ class: "inspectors panel-scroll",
536
+ "data-layout-panel": "inspector",
537
+ "aria-label": "代码检查面板"
538
+ };
539
+ const _hoisted_72 = { class: "inspector-heading" };
540
+ const _hoisted_73 = ["aria-label", "title"];
541
+ const _hoisted_74 = {
542
+ class: "code-block",
543
+ "data-code-output": ""
544
+ };
545
+ const _hoisted_75 = ["innerHTML"];
546
+ const _hoisted_76 = { class: "code-status" };
547
+ const _hoisted_77 = ["data-preview-framework", "data-theme"];
548
+ const _hoisted_78 = { class: "preview-dialog__panel" };
549
+ const _hoisted_79 = { class: "preview-dialog__header" };
550
+ const _hoisted_80 = { class: "preview-dialog__header-meta" };
551
+ const _hoisted_81 = { class: "stage-badge" };
552
+ const _hoisted_82 = { class: "preview-dialog__body" };
553
+ const _hoisted_83 = { class: "preview-dialog__canvas" };
554
+ const _hoisted_84 = [
555
+ "data-material-code-framework",
556
+ "data-material-code-type",
557
+ "data-theme"
558
+ ];
559
+ const _hoisted_85 = { class: "preview-dialog__panel material-code-dialog__panel" };
560
+ const _hoisted_86 = { class: "preview-dialog__header" };
561
+ const _hoisted_87 = { id: "material-code-title" };
562
+ const _hoisted_88 = { class: "material-code-dialog__subtitle" };
563
+ const _hoisted_89 = { class: "preview-dialog__header-meta" };
564
+ const _hoisted_90 = { class: "stage-badge" };
565
+ const _hoisted_91 = { class: "preview-dialog__body material-code-dialog__body" };
566
+ const _hoisted_92 = { class: "code-block material-code-dialog__code" };
567
+ const _hoisted_93 = ["innerHTML"];
568
+ const _hoisted_94 = { class: "preview-dialog__footer" };
569
+ const _hoisted_95 = { class: "material-code-dialog__actions" };
570
+ const _hoisted_96 = ["aria-label", "title"];
571
+ var LLayoutWorkspace_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ (0, vue.defineComponent)({
572
+ __name: "LLayoutWorkspace",
573
+ props: {
574
+ document: {
575
+ type: Object,
576
+ required: false
577
+ },
578
+ materials: {
579
+ type: [Object, Array],
580
+ required: false
581
+ },
582
+ theme: {
583
+ type: String,
584
+ required: false
585
+ }
586
+ },
587
+ emits: ["update:document", "change"],
588
+ setup(__props, { emit: __emit }) {
589
+ const workspaceProps = __props;
590
+ const workspaceEmit = __emit;
591
+ const routes = [
592
+ {
593
+ id: "editor",
594
+ label: "编辑",
595
+ description: "可视化编辑工作区"
596
+ },
597
+ {
598
+ id: "vanilla",
599
+ label: "原生",
600
+ description: "原生运行时渲染"
601
+ },
602
+ {
603
+ id: "runtime",
604
+ label: "预览",
605
+ description: "网格布局运行时预览"
606
+ },
607
+ {
608
+ id: "vue",
609
+ label: "组件",
610
+ description: "组件适配器"
611
+ }
612
+ ];
613
+ const breakpointIcons = {
614
+ desktop: lucide_vue_next.Monitor,
615
+ tablet: lucide_vue_next.Tablet,
616
+ mobile: lucide_vue_next.Smartphone
617
+ };
618
+ const routeIcons = {
619
+ editor: lucide_vue_next.Grid2X2,
620
+ vanilla: lucide_vue_next.Code2,
621
+ runtime: lucide_vue_next.Eye,
622
+ vue: lucide_vue_next.PanelsTopLeft
623
+ };
624
+ const developmentFrameworks = [{
625
+ id: "webcomponent",
626
+ label: "原生",
627
+ description: "原生 Web Component 物料",
628
+ icon: lucide_vue_next.Code2
629
+ }, {
630
+ id: "vue",
631
+ label: "Vue 3",
632
+ description: "Vue 3 组件物料",
633
+ icon: lucide_vue_next.PanelsTopLeft
634
+ }];
635
+ function developmentFrameworkLabel(framework) {
636
+ if (framework === "webcomponent") return "原生";
637
+ return "Vue 3";
638
+ }
639
+ function escapeHtml(value) {
640
+ return value.replace(/[&<>'"]/g, (character) => ({
641
+ "&": "&amp;",
642
+ "<": "&lt;",
643
+ ">": "&gt;",
644
+ "'": "&#39;",
645
+ "\"": "&quot;"
646
+ })[character] ?? character);
647
+ }
648
+ function itemTitle(item) {
649
+ return String(item.props?.title ?? item.id);
650
+ }
651
+ function materialLabel(type) {
652
+ return materials.get(type)?.title ?? materials.get(type)?.label ?? type;
653
+ }
654
+ function formatMarkup(value) {
655
+ const tokens = value.replace(/></g, ">\n<").split("\n");
656
+ let depth = 0;
657
+ return tokens.map((token) => {
658
+ const line = token.trim();
659
+ if (!line) return "";
660
+ if (line.startsWith("</")) depth = Math.max(0, depth - 1);
661
+ const formatted = `${" ".repeat(depth)}${line}`;
662
+ if (line.startsWith("<") && !line.startsWith("</") && !line.startsWith("<!") && !line.startsWith("<?") && !line.endsWith("/>") && !/^<(?:area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)\b/i.test(line)) depth += 1;
663
+ return formatted;
664
+ }).filter(Boolean).join("\n");
665
+ }
666
+ function formatCss(value) {
667
+ let depth = 0;
668
+ return value.replace(/\s*\{\s*/g, " {\n").replace(/\s*;\s*/g, ";\n").replace(/\s*}\s*/g, "\n}\n").split("\n").map((line) => line.trim()).filter(Boolean).map((line) => {
669
+ if (line.startsWith("}")) depth = Math.max(0, depth - 1);
670
+ const formatted = `${" ".repeat(depth)}${line}`;
671
+ if (line.endsWith("{")) depth += 1;
672
+ return formatted;
673
+ }).join("\n");
674
+ }
675
+ function breakpointLabel(id) {
676
+ return id === "desktop" ? "桌面" : id === "tablet" ? "平板" : "手机";
677
+ }
678
+ function lucideMarkup(name) {
679
+ if (typeof document === "undefined") return "";
680
+ const element = (0, lucide.createElement)(lucide.icons[name]);
681
+ element.setAttribute("width", "13");
682
+ element.setAttribute("height", "13");
683
+ element.setAttribute("stroke-width", "1.9");
684
+ element.setAttribute("aria-hidden", "true");
685
+ return element.outerHTML;
686
+ }
687
+ function renderCardMarkup(item, options = {}) {
688
+ const type = materialLabel(item.type);
689
+ const title = escapeHtml(itemTitle(item));
690
+ const value = item.props?.value;
691
+ const valueMarkup = value === void 0 ? `<span class="layout-card__chart" aria-hidden="true"><i></i><i></i><i></i><i></i><i></i></span>` : `<strong class="layout-card__value">${escapeHtml(new Intl.NumberFormat("zh-CN").format(Number(value)))}</strong>`;
692
+ return `<article class="layout-card" data-layout-card data-layout-type="${escapeHtml(item.type)}">
693
+ ${options.interactive ? `<div class="layout-card__actions" data-layout-actions>
694
+ <button class="layout-card__action" type="button" data-layout-action="copy" aria-disabled="false" aria-label="\u590D\u5236\u7269\u6599" title="\u590D\u5236\u7269\u6599">${lucideMarkup("Copy")}</button>
695
+ <button class="layout-card__action" type="button" data-layout-action="delete" aria-disabled="false" aria-label="\u5220\u9664\u7269\u6599" title="\u5220\u9664\u7269\u6599">${lucideMarkup("Trash2")}</button>
696
+ <button class="layout-card__action" type="button" data-layout-action="lock" aria-disabled="false" aria-label="${item.state?.locked ? "解锁物料" : "锁定物料"}" title="${item.state?.locked ? "解锁物料" : "锁定物料"}">${lucideMarkup(item.state?.locked ? "Unlock" : "Lock")}</button>
697
+ </div>` : ""}
698
+ <div class="layout-card__meta"><span class="layout-card__type">${escapeHtml(type)}</span><span class="layout-card__status">\u5DF2\u914D\u7F6E</span></div>
699
+ <h3 class="layout-card__title">${title}</h3>
700
+ ${valueMarkup}
701
+ <div class="layout-card__footer"><span>\u5B9E\u65F6\u6570\u636E</span><span class="layout-card__spark">\u8D8B\u52BF\u7A33\u5B9A</span></div>
702
+ </article>`;
703
+ }
704
+ function renderExportMaterialMarkup(item) {
705
+ const tagName = (materials.get(item.type)?.renderers?.webcomponent)?.tagName;
706
+ if (!tagName || !/^[a-z][a-z0-9.-]*$/i.test(tagName)) return renderCardMarkup(item);
707
+ const attributes = Object.entries(item.props ?? {}).filter(([, value]) => value !== void 0 && value !== null).filter(([, value]) => typeof value !== "object" && typeof value !== "function").map(([key, value]) => `${key.replace(/[^a-zA-Z0-9:_.-]/g, "-")}="${escapeHtml(String(value))}"`).join(" ");
708
+ return `<${tagName}${attributes ? ` ${attributes}` : ""}></${tagName}>`;
709
+ }
710
+ function ensureWorkspaceMaterialElement(tagName, type) {
711
+ if (typeof customElements === "undefined" || !/^[a-z][a-z0-9.-]*$/i.test(tagName) || customElements.get(tagName)) return;
712
+ class LayoutMaterialElement extends HTMLElement {
713
+ constructor() {
714
+ super(...arguments);
715
+ this.props = {};
716
+ }
717
+ static get observedAttributes() {
718
+ return ["title", "value"];
719
+ }
720
+ set materialProps(value) {
721
+ this.props = { ...value ?? {} };
722
+ this.render();
723
+ }
724
+ connectedCallback() {
725
+ this.render();
726
+ }
727
+ attributeChangedCallback() {
728
+ if (!Object.keys(this.props).length) this.render();
729
+ }
730
+ attributeProps() {
731
+ const result = {};
732
+ for (const attribute of Array.from(this.attributes)) {
733
+ if (attribute.name === "class" || attribute.name === "style") continue;
734
+ result[attribute.name] = attribute.name === "value" && attribute.value !== "" && !Number.isNaN(Number(attribute.value)) ? Number(attribute.value) : attribute.value;
735
+ }
736
+ return result;
737
+ }
738
+ render() {
739
+ const props = Object.keys(this.props).length ? this.props : this.attributeProps();
740
+ this.style.display = "block";
741
+ this.style.width = "100%";
742
+ this.style.height = "100%";
743
+ this.innerHTML = renderCardMarkup({
744
+ id: `native-${type}`,
745
+ type,
746
+ layout: {},
747
+ props
748
+ });
749
+ }
750
+ }
751
+ customElements.define(tagName, LayoutMaterialElement);
752
+ }
753
+ function setNativeRuntimeCard(shell, item) {
754
+ const tagName = (materials.get(item.type)?.renderers?.webcomponent)?.tagName;
755
+ shell.className = "layout-item-shell";
756
+ shell.dataset.layoutType = item.type;
757
+ shell.dataset.materialRenderer = "webcomponent";
758
+ if (!tagName || !/^[a-z][a-z0-9.-]*$/i.test(tagName)) {
759
+ shell.innerHTML = renderCardMarkup(item);
760
+ return;
761
+ }
762
+ ensureWorkspaceMaterialElement(tagName, item.type);
763
+ const element = shell.ownerDocument.createElement(tagName);
764
+ element.materialProps = { ...item.props ?? {} };
765
+ shell.replaceChildren(element);
766
+ }
767
+ function setRuntimeCard(shell, item) {
768
+ if (activeDevelopmentFramework.value === "webcomponent") {
769
+ setNativeRuntimeCard(shell, item);
770
+ return;
771
+ }
772
+ shell.className = "layout-item-shell";
773
+ shell.dataset.layoutType = item.type;
774
+ shell.dataset.materialRenderer = "vue";
775
+ shell.innerHTML = renderCardMarkup(item);
776
+ }
777
+ function parseRoute(value) {
778
+ const id = value.replace(/^#\/?/, "");
779
+ return routes.some((route) => route.id === id) ? id : "editor";
780
+ }
781
+ const builtinMaterials = [{
782
+ type: "metric",
783
+ order: 1,
784
+ title: "数据指标",
785
+ category: "data",
786
+ categoryLabel: "数据组件",
787
+ description: "数值与摘要",
788
+ keywords: [
789
+ "指标",
790
+ "数值",
791
+ "KPI"
792
+ ],
793
+ tags: ["数据", "卡片"],
794
+ defaultLayout: {
795
+ w: 4,
796
+ h: 8
797
+ },
798
+ size: {
799
+ default: {
800
+ w: 4,
801
+ h: 8
802
+ },
803
+ min: {
804
+ w: 2,
805
+ h: 4
806
+ },
807
+ max: {
808
+ w: 12,
809
+ h: 20
810
+ },
811
+ step: {
812
+ w: 1,
813
+ h: 1
814
+ }
815
+ },
816
+ constraints: {
817
+ movable: true,
818
+ resizable: true
819
+ },
820
+ defaults: {
821
+ title: "新建指标",
822
+ value: 0
823
+ },
824
+ renderers: {
825
+ html: {
826
+ kind: "html",
827
+ tagName: "article"
828
+ },
829
+ webcomponent: {
830
+ kind: "webcomponent",
831
+ tagName: "ldesign-metric-card"
832
+ },
833
+ vue: {
834
+ kind: "vue",
835
+ package: "@composy/layout-vue",
836
+ exportName: "LMetricCard",
837
+ component: LMetricCard
838
+ }
839
+ }
840
+ }, {
841
+ type: "chart",
842
+ order: 2,
843
+ title: "趋势图表",
844
+ category: "visual",
845
+ categoryLabel: "可视化组件",
846
+ description: "变化与走向",
847
+ keywords: [
848
+ "趋势",
849
+ "图表",
850
+ "分析"
851
+ ],
852
+ tags: ["可视化", "卡片"],
853
+ defaultLayout: {
854
+ w: 4,
855
+ h: 10
856
+ },
857
+ size: {
858
+ default: {
859
+ w: 4,
860
+ h: 10
861
+ },
862
+ min: {
863
+ w: 3,
864
+ h: 6
865
+ },
866
+ max: {
867
+ w: 12,
868
+ h: 24
869
+ },
870
+ step: {
871
+ w: 1,
872
+ h: 1
873
+ }
874
+ },
875
+ constraints: {
876
+ movable: true,
877
+ resizable: true
878
+ },
879
+ defaults: { title: "营收趋势" },
880
+ renderers: {
881
+ html: {
882
+ kind: "html",
883
+ tagName: "article"
884
+ },
885
+ webcomponent: {
886
+ kind: "webcomponent",
887
+ tagName: "ldesign-trend-card"
888
+ },
889
+ vue: {
890
+ kind: "vue",
891
+ package: "@composy/layout-vue",
892
+ exportName: "LTrendCard",
893
+ component: LTrendCard
894
+ }
895
+ }
896
+ }];
897
+ function resolveWorkspaceMaterials(input) {
898
+ if (!input) return (0, _composy_layout_core.createMaterialRegistry)(builtinMaterials);
899
+ if (!Array.isArray(input)) return input;
900
+ return (0, _composy_layout_core.createMaterialRegistry)(input);
901
+ }
902
+ const materials = resolveWorkspaceMaterials(workspaceProps.materials);
903
+ const initial = workspaceProps.document ?? (0, _composy_layout_core.createLayoutDocument)({
904
+ id: "layout-alpha-demo",
905
+ breakpoints: _composy_layout_core.responsiveWebBreakpoints.map((item) => ({ ...item })),
906
+ defaultBreakpointId: "desktop",
907
+ items: [
908
+ {
909
+ id: "sales",
910
+ type: "metric",
911
+ props: {
912
+ title: "月度销售额",
913
+ value: 12840
914
+ },
915
+ layout: {
916
+ desktop: {
917
+ x: 0,
918
+ y: 0,
919
+ w: 4,
920
+ h: 10
921
+ },
922
+ tablet: {
923
+ x: 0,
924
+ y: 0,
925
+ w: 4,
926
+ h: 10
927
+ },
928
+ mobile: {
929
+ x: 0,
930
+ y: 0,
931
+ w: 4,
932
+ h: 8
933
+ }
934
+ }
935
+ },
936
+ {
937
+ id: "orders",
938
+ type: "metric",
939
+ props: {
940
+ title: "待处理订单",
941
+ value: 86
942
+ },
943
+ layout: {
944
+ desktop: {
945
+ x: 4,
946
+ y: 0,
947
+ w: 4,
948
+ h: 10
949
+ },
950
+ tablet: {
951
+ x: 4,
952
+ y: 0,
953
+ w: 4,
954
+ h: 10
955
+ },
956
+ mobile: {
957
+ x: 0,
958
+ y: 8,
959
+ w: 4,
960
+ h: 8
961
+ }
962
+ }
963
+ },
964
+ {
965
+ id: "revenue",
966
+ type: "chart",
967
+ props: { title: "营收预测" },
968
+ layout: {
969
+ desktop: {
970
+ x: 8,
971
+ y: 0,
972
+ w: 4,
973
+ h: 18
974
+ },
975
+ tablet: {
976
+ x: 0,
977
+ y: 10,
978
+ w: 8,
979
+ h: 14
980
+ },
981
+ mobile: {
982
+ x: 0,
983
+ y: 16,
984
+ w: 4,
985
+ h: 12
986
+ }
987
+ }
988
+ }
989
+ ]
990
+ });
991
+ const editorGrid = (0, vue.ref)();
992
+ const canvasViewport = (0, vue.ref)();
993
+ const runtimePreview = (0, vue.ref)();
994
+ const vanillaRuntime = (0, vue.ref)();
995
+ const previewDialogRuntime = (0, vue.ref)();
996
+ const materialQuery = (0, vue.ref)("");
997
+ const activeMaterialCategory = (0, vue.ref)();
998
+ const activeDevelopmentFramework = (0, vue.ref)("vue");
999
+ const materialElements = /* @__PURE__ */ new Map();
1000
+ const materialSourceDisposers = /* @__PURE__ */ new Map();
1001
+ const materialCategories = (0, vue.computed)(() => materials.getCategories());
1002
+ const activeMaterialCategoryLabel = (0, vue.computed)(() => {
1003
+ if (!activeMaterialCategory.value) return "全部分类";
1004
+ return materialCategories.value.find((category) => category.id === activeMaterialCategory.value)?.label ?? "全部分类";
1005
+ });
1006
+ const activeDevelopmentFrameworkLabel = (0, vue.computed)(() => developmentFrameworkLabel(activeDevelopmentFramework.value));
1007
+ const materialCategoryFilter = (0, vue.computed)({
1008
+ get: () => activeMaterialCategory.value ?? "",
1009
+ set: (value) => {
1010
+ activeMaterialCategory.value = value || void 0;
1011
+ }
1012
+ });
1013
+ const hasMaterialFilters = (0, vue.computed)(() => Boolean(materialQuery.value.trim()) || Boolean(activeMaterialCategory.value));
1014
+ const visibleMaterials = (0, vue.computed)(() => materials.search({
1015
+ query: materialQuery.value,
1016
+ category: activeMaterialCategory.value,
1017
+ framework: activeDevelopmentFramework.value
1018
+ }));
1019
+ const layout = (0, vue.ref)(structuredClone(initial));
1020
+ const selected = (0, vue.ref)();
1021
+ const selectedIds = (0, vue.ref)([]);
1022
+ const activeBreakpoint = (0, vue.ref)("desktop");
1023
+ const canvasOrientation = (0, vue.ref)("landscape");
1024
+ const deviceMenuOpen = (0, vue.ref)(false);
1025
+ const categoryMenuOpen = (0, vue.ref)(false);
1026
+ const developmentModeMenuOpen = (0, vue.ref)(false);
1027
+ const copied = (0, vue.ref)(false);
1028
+ const previewMode = (0, vue.ref)(false);
1029
+ const previewDialogOpen = (0, vue.ref)(false);
1030
+ const materialCodeDialog = (0, vue.ref)();
1031
+ const materialCodeCopied = (0, vue.ref)(false);
1032
+ const activeRoute = (0, vue.ref)("editor");
1033
+ const theme = (0, vue.ref)(workspaceProps.theme ?? "light");
1034
+ const canvasZoom = (0, vue.ref)(1);
1035
+ const canvasPan = (0, vue.ref)({
1036
+ x: 0,
1037
+ y: 0
1038
+ });
1039
+ const canvasTool = (0, vue.ref)("select");
1040
+ const canvasPanning = (0, vue.ref)(false);
1041
+ const canvasSpacePressed = (0, vue.ref)(false);
1042
+ const canvasPanOrigin = (0, vue.ref)({
1043
+ x: 0,
1044
+ y: 0,
1045
+ panX: 0,
1046
+ panY: 0
1047
+ });
1048
+ let editor;
1049
+ let vanillaRuntimeInstance;
1050
+ let previewDialogRuntimeInstance;
1051
+ let previousGridStackRenderCallback;
1052
+ let interactionPreviewFrame;
1053
+ const disposers = [];
1054
+ const currentDocument = (0, vue.computed)(() => layout.value);
1055
+ const canvasSize = (0, vue.computed)(() => {
1056
+ return {
1057
+ desktop: {
1058
+ landscape: {
1059
+ width: 1200,
1060
+ height: 760
1061
+ },
1062
+ portrait: {
1063
+ width: 760,
1064
+ height: 1200
1065
+ }
1066
+ },
1067
+ tablet: {
1068
+ landscape: {
1069
+ width: 1024,
1070
+ height: 768
1071
+ },
1072
+ portrait: {
1073
+ width: 768,
1074
+ height: 1024
1075
+ }
1076
+ },
1077
+ mobile: {
1078
+ landscape: {
1079
+ width: 844,
1080
+ height: 390
1081
+ },
1082
+ portrait: {
1083
+ width: 390,
1084
+ height: 844
1085
+ }
1086
+ }
1087
+ }[activeBreakpoint.value]?.[canvasOrientation.value] ?? {
1088
+ width: 1200,
1089
+ height: 760
1090
+ };
1091
+ });
1092
+ const activeBreakpointIcon = (0, vue.computed)(() => breakpointIcons[activeBreakpoint.value] ?? lucide_vue_next.Monitor);
1093
+ (0, vue.computed)(() => JSON.stringify(currentDocument.value, null, 2));
1094
+ const compiledResult = (0, vue.computed)(() => (0, _composy_layout_compiler.compileLayout)(currentDocument.value, {
1095
+ target: "html-css",
1096
+ breakpointId: activeBreakpoint.value,
1097
+ materials,
1098
+ materialCompiler: {
1099
+ supports: () => true,
1100
+ compile: (item) => renderExportMaterialMarkup(item)
1101
+ }
1102
+ }));
1103
+ const compiled = (0, vue.computed)(() => formatCss(compiledResult.value.css ?? ""));
1104
+ const htmlSource = (0, vue.computed)(() => {
1105
+ const result = compiledResult.value;
1106
+ return `<style>
1107
+ ${compiled.value}
1108
+ </style>
1109
+
1110
+ ${formatMarkup(result.html ?? "")}`;
1111
+ });
1112
+ (0, vue.computed)(() => `<script setup lang="ts">
1113
+ import { LGrid } from '@composy/layout-vue'
1114
+ import type { LayoutDocument } from '@composy/layout-core'
1115
+
1116
+ const layoutDocument: LayoutDocument = ${JSON.stringify(currentDocument.value, null, 2)}
1117
+ <\/script>
1118
+
1119
+ <template>
1120
+ <LGrid :document="layoutDocument" breakpoint="${activeBreakpoint.value}">
1121
+ <template #item="{ item }">
1122
+ <article class="layout-card">
1123
+ <div class="layout-card__meta">
1124
+ <span class="layout-card__type">{{ item.type === 'metric' ? '\u6570\u636E\u6307\u6807' : '\u8D8B\u52BF\u56FE\u8868' }}</span>
1125
+ <span class="layout-card__status">\u5DF2\u914D\u7F6E</span>
1126
+ </div>
1127
+ <h3 class="layout-card__title">{{ item.props.title }}</h3>
1128
+ <strong v-if="item.props.value !== undefined" class="layout-card__value">
1129
+ {{ Number(item.props.value).toLocaleString('zh-CN') }}
1130
+ </strong>
1131
+ <span v-else class="layout-card__chart" aria-hidden="true">
1132
+
1133
+ <i v-for="bar in 5" :key="bar" />
1134
+ </span>
1135
+ <div class="layout-card__footer"><span>\u5B9E\u65F6\u6570\u636E</span><span class="layout-card__spark">\u8D8B\u52BF\u7A33\u5B9A</span></div>
1136
+ </article>
1137
+ </template>
1138
+ </LGrid>
1139
+ </template>`);
1140
+ (0, vue.computed)(() => `import { Grid } from '@composy/layout-react'
1141
+ import type { LayoutDocument } from '@composy/layout-core'
1142
+
1143
+ const layoutDocument: LayoutDocument = ${JSON.stringify(currentDocument.value, null, 2)}
1144
+
1145
+ export function LayoutPreview() {
1146
+ return (
1147
+ <Grid document={layoutDocument} breakpoint="${activeBreakpoint.value}">
1148
+ {({ item }) => (
1149
+ <article className="layout-card">
1150
+ <div className="layout-card__meta">
1151
+ <span className="layout-card__type">{item.type === 'metric' ? '\u6570\u636E\u6307\u6807' : '\u8D8B\u52BF\u56FE\u8868'}</span>
1152
+ <span className="layout-card__status">\u5DF2\u914D\u7F6E</span>
1153
+ </div>
1154
+ <h3 className="layout-card__title">{String(item.props.title)}</h3>
1155
+ {item.props.value !== undefined ? (
1156
+ <strong className="layout-card__value">{Number(item.props.value).toLocaleString('zh-CN')}</strong>
1157
+ ) : (
1158
+ <span className="layout-card__chart" aria-hidden="true">{[1, 2, 3, 4, 5].map(bar => <i key={bar} />)}</span>
1159
+ )}
1160
+ <div className="layout-card__footer"><span>\u5B9E\u65F6\u6570\u636E</span><span className="layout-card__spark">\u8D8B\u52BF\u7A33\u5B9A</span></div>
1161
+ </article>
1162
+ )}
1163
+ </Grid>
1164
+ )
1165
+ }`);
1166
+ const generatedVueSource = (0, vue.computed)(() => {
1167
+ const imports = /* @__PURE__ */ new Map();
1168
+ const bindings = [];
1169
+ const usedLocals = /* @__PURE__ */ new Set(["LGrid"]);
1170
+ for (const material of materials.getAll()) {
1171
+ const renderer = material.renderers?.vue;
1172
+ if (!renderer?.package || !renderer.exportName) continue;
1173
+ const exportName = renderer.exportName.trim();
1174
+ if (!/^[A-Za-z_$][\w$]*$/.test(exportName)) continue;
1175
+ let localName = exportName;
1176
+ let suffix = 2;
1177
+ while (usedLocals.has(localName)) localName = `${exportName}${suffix++}`;
1178
+ usedLocals.add(localName);
1179
+ const packageImports = imports.get(renderer.package) ?? [];
1180
+ packageImports.push(localName === exportName ? exportName : `${exportName} as ${localName}`);
1181
+ imports.set(renderer.package, packageImports);
1182
+ bindings.push(` ${JSON.stringify(material.type)}: ${localName}`);
1183
+ }
1184
+ const importLines = ["import { LGrid } from '@composy/layout-vue'", ...[...imports.entries()].map(([packageName, exports]) => `import { ${exports.join(", ")} } from '${packageName}'`)].join("\n");
1185
+ const componentMap = bindings.length ? `const materialComponents = {
1186
+ ${bindings.join(",\n")}
1187
+ } as const` : "const materialComponents: Record<string, never> = {}";
1188
+ return `<script setup lang="ts">
1189
+ ${importLines}
1190
+ import type { LayoutDocument } from '@composy/layout-core'
1191
+
1192
+ const layoutDocument: LayoutDocument = ${JSON.stringify(currentDocument.value, null, 2)}
1193
+ const breakpoint = '${activeBreakpoint.value}'
1194
+ ${componentMap}
1195
+ <\/script>
1196
+
1197
+ <template>
1198
+ <LGrid :document="layoutDocument" :breakpoint="breakpoint">
1199
+ <template #item="{ item }">
1200
+ <component
1201
+ :is="materialComponents[item.type as keyof typeof materialComponents]"
1202
+ v-bind="item.props"
1203
+ />
1204
+ </template>
1205
+ </LGrid>
1206
+ </template>
1207
+
1208
+ <style scoped>
1209
+ /* LGrid renders the ldesign-layout-grid CSS Grid container. */
1210
+ ${compiled.value}
1211
+ </style>`;
1212
+ });
1213
+ (0, vue.computed)(() => `import type { LayoutDocument, LayoutNode } from '@composy/layout-core'
1214
+
1215
+ const layoutDocument: LayoutDocument = ${JSON.stringify(currentDocument.value, null, 2)}
1216
+ const breakpoint = '${activeBreakpoint.value}'
1217
+ const itemClass = (item: LayoutNode) => \`ldesign-layout-item-\${item.id.replace(/[^a-zA-Z0-9_-]/g, '-') }\`
1218
+
1219
+ export function LayoutPreview() {
1220
+ return (
1221
+ <section className="ldesign-layout-grid">
1222
+ {layoutDocument.items.map(item => (
1223
+ <article key={item.id} className={\`\${itemClass(item)} layout-card\`}>
1224
+ <div className="layout-card__meta"><span className="layout-card__type">{item.type === 'metric' ? '\u6570\u636E\u6307\u6807' : '\u8D8B\u52BF\u56FE\u8868'}</span><span className="layout-card__status">\u5DF2\u914D\u7F6E</span></div>
1225
+ <h3 className="layout-card__title">{String(item.props.title)}</h3>
1226
+ {item.props.value !== undefined ? <strong className="layout-card__value">{Number(item.props.value).toLocaleString('zh-CN')}</strong> : <span className="layout-card__chart" aria-hidden="true">{[1, 2, 3, 4, 5].map(bar => <i key={bar} />)}</span>}
1227
+ <div className="layout-card__footer"><span>\u5B9E\u65F6\u6570\u636E</span><span className="layout-card__spark">\u8D8B\u52BF\u7A33\u5B9A</span></div>
1228
+ </article>
1229
+ ))}
1230
+ </section>
1231
+ )
1232
+ }
1233
+
1234
+ /* CSS Grid styles are emitted separately by the compiler. */`.replace(/<span className="layout-card__type">[\s\S]*?<\/span>/g, "<span className=\"layout-card__type\">{item.type}</span>").replace(/<span className="layout-card__status">[\s\S]*?<\/span>/g, "<span className=\"layout-card__status\">&#x5df2;&#x914d;&#x7f6e;</span>").replace(/<div className="layout-card__footer">[\s\S]*?<\/div>/g, "<div className=\"layout-card__footer\"><span>&#x5b9e;&#x65f6;&#x6570;&#x636e;</span><span className=\"layout-card__spark\">&#x7a33;&#x5b9a;</span></div>"));
1235
+ const activeCode = (0, vue.computed)(() => activeDevelopmentFramework.value === "vue" ? generatedVueSource.value : htmlSource.value);
1236
+ const activeCodeLabel = (0, vue.computed)(() => activeDevelopmentFramework.value === "vue" ? "Vue组件代码" : "原生代码");
1237
+ const activeCodeExtension = (0, vue.computed)(() => activeDevelopmentFramework.value === "vue" ? "vue" : "html");
1238
+ function materialCodeProps(material) {
1239
+ return { ...material.defaults ?? {} };
1240
+ }
1241
+ function materialVueCode(material) {
1242
+ const renderer = material.renderers?.vue;
1243
+ const exportedName = renderer?.exportName?.trim();
1244
+ const componentName = (exportedName && /^[A-Za-z_$][\w$]*$/.test(exportedName) ? exportedName : void 0) ?? renderer?.component?.name ?? material.type.replace(/[^A-Za-z0-9_$]/g, "");
1245
+ const importLine = renderer?.package && exportedName && /^[A-Za-z_$][\w$]*$/.test(exportedName) ? `import { ${componentName} } from '${renderer.package}'` : `// Vue \u7EC4\u4EF6\u7531\u7269\u6599\u6CE8\u518C\u8868\u63D0\u4F9B\uFF1A${componentName}`;
1246
+ const props = materialCodeProps(material);
1247
+ const bindings = Object.entries(props).filter(([, value]) => value !== void 0 && value !== null).map(([key, value]) => ` :${key}='${JSON.stringify(value)}'`).join("\n");
1248
+ return `<script setup lang="ts">
1249
+ ${importLine}
1250
+ <\/script>
1251
+
1252
+ <!-- \u6CE8\u518C\u8868\u7269\u6599\uFF1A${material.type} -->
1253
+ <template>
1254
+ <${componentName}${bindings ? `
1255
+ ${bindings}
1256
+ ` : ""}/>
1257
+ </template>`;
1258
+ }
1259
+ function materialWebComponentCode(material) {
1260
+ const tagName = (material.renderers?.webcomponent)?.tagName ?? `ldesign-${material.type.replace(/[^a-z0-9-]/gi, "-")}`;
1261
+ const attributes = Object.entries(materialCodeProps(material)).filter(([, value]) => value !== void 0 && value !== null).map(([key, value]) => `${key}="${String(value).replace(/"/g, "&quot;")}"`).join("\n ");
1262
+ return `<!-- \u6CE8\u518C\u8868\u7269\u6599\uFF1A${material.type} -->
1263
+ <!-- Web Component \u6807\u7B7E\uFF1A${tagName} -->
1264
+ <${tagName}${attributes ? `
1265
+ ${attributes}` : ""}></${tagName}>`;
1266
+ }
1267
+ const materialCode = (0, vue.computed)(() => {
1268
+ const material = materialCodeDialog.value;
1269
+ if (!material) return "";
1270
+ return activeDevelopmentFramework.value === "vue" ? materialVueCode(material) : materialWebComponentCode(material);
1271
+ });
1272
+ const highlightedMaterialCode = (0, vue.computed)(() => highlightCode(materialCode.value));
1273
+ const materialCodeLineCount = (0, vue.computed)(() => materialCode.value.split("\n").length);
1274
+ function highlightCode(value) {
1275
+ const fragments = [];
1276
+ const stash = (value2, className) => {
1277
+ return `__LDS_TOKEN_${fragments.push(`<span class="code-token-${className}">${value2}</span>`) - 1}__`;
1278
+ };
1279
+ let code = escapeHtml(value);
1280
+ code = code.replace(/(&quot;.*?&quot;|&#39;.*?&#39;|`.*?`)/g, (match) => stash(match, "string"));
1281
+ code = code.replace(/(\/\/[^\n]*|\/\*[\s\S]*?\*\/)/g, (match) => stash(match, "comment"));
1282
+ code = code.replace(/(^|[;{\n]\s*)([A-Za-z_$][\w$-]*)(?=\s*:)/g, (_, prefix, name) => `${prefix}${stash(name, "property")}`);
1283
+ code = code.replace(/\b(import|from|const|let|function|return|export|as|type|class|extends|new|true|false|null|undefined)\b/g, (match) => stash(match, /^(true|false|null|undefined)$/.test(match) ? "constant" : "keyword"));
1284
+ code = code.replace(/(&lt;\/?[A-Za-z][^&]*?&gt;)/g, (match) => stash(match, "tag"));
1285
+ let previous = "";
1286
+ while (previous !== code && /__LDS_TOKEN_\d+__/.test(code)) {
1287
+ previous = code;
1288
+ code = code.replace(/__LDS_TOKEN_(\d+)__/g, (_, index) => fragments[Number(index)] ?? "");
1289
+ }
1290
+ return code;
1291
+ }
1292
+ const highlightedCode = (0, vue.computed)(() => highlightCode(activeCode.value));
1293
+ const activeCodeLineCount = (0, vue.computed)(() => activeCode.value.split("\n").length);
1294
+ const canvasSceneStyle = (0, vue.computed)(() => ({
1295
+ width: `${canvasSize.value.width}px`,
1296
+ minHeight: `${canvasSize.value.height}px`,
1297
+ "--canvas-height": `${canvasSize.value.height}px`,
1298
+ "--canvas-grid-height": `${Math.max(180, canvasSize.value.height)}px`,
1299
+ transform: `translate3d(${canvasPan.value.x}px, ${canvasPan.value.y}px, 0) scale(${canvasZoom.value})`
1300
+ }));
1301
+ const canvasZoomLabel = (0, vue.computed)(() => `${Math.round(canvasZoom.value * 100)}%`);
1302
+ const selectedItem = (0, vue.computed)(() => layout.value.items.find((item) => item.id === selected.value));
1303
+ const selectedLocked = (0, vue.computed)(() => Boolean(selectedItem.value?.state?.locked));
1304
+ const selectedMovable = (0, vue.computed)(() => selectedItem.value?.constraints?.movable !== false);
1305
+ const selectedResizable = (0, vue.computed)(() => selectedItem.value?.constraints?.resizable !== false);
1306
+ function add(type) {
1307
+ if (!editor) return;
1308
+ const node = editor.addItem({ type });
1309
+ editor.updateItem(node.id, (item) => {
1310
+ for (const bp of Object.values(item.layout)) bp.y = 30;
1311
+ return item;
1312
+ });
1313
+ editor.select(node.id);
1314
+ }
1315
+ function materialProps(type) {
1316
+ return JSON.stringify(materials.get(type)?.defaults ?? {});
1317
+ }
1318
+ function openMaterialCode(type) {
1319
+ const material = materials.get(type);
1320
+ if (!material) return;
1321
+ materialCodeDialog.value = material;
1322
+ materialCodeCopied.value = false;
1323
+ closeFilterMenus();
1324
+ }
1325
+ function closeMaterialCode() {
1326
+ materialCodeDialog.value = void 0;
1327
+ materialCodeCopied.value = false;
1328
+ }
1329
+ async function copyMaterialCode() {
1330
+ if (!materialCode.value || typeof navigator === "undefined") return;
1331
+ try {
1332
+ await navigator.clipboard.writeText(materialCode.value);
1333
+ materialCodeCopied.value = true;
1334
+ window.setTimeout(() => {
1335
+ materialCodeCopied.value = false;
1336
+ }, 1500);
1337
+ } catch {
1338
+ materialCodeCopied.value = false;
1339
+ }
1340
+ }
1341
+ function handleMaterialClick(type) {
1342
+ add(type);
1343
+ }
1344
+ function toggleFilterMenu(menu) {
1345
+ categoryMenuOpen.value = menu === "category" ? !categoryMenuOpen.value : false;
1346
+ deviceMenuOpen.value = menu === "device" ? !deviceMenuOpen.value : false;
1347
+ developmentModeMenuOpen.value = false;
1348
+ }
1349
+ function toggleDevelopmentModeMenu() {
1350
+ developmentModeMenuOpen.value = !developmentModeMenuOpen.value;
1351
+ categoryMenuOpen.value = false;
1352
+ deviceMenuOpen.value = false;
1353
+ }
1354
+ function closeFilterMenus() {
1355
+ categoryMenuOpen.value = false;
1356
+ deviceMenuOpen.value = false;
1357
+ developmentModeMenuOpen.value = false;
1358
+ }
1359
+ function handleFilterMenuKeydown(event) {
1360
+ if (event.key !== "Escape") return;
1361
+ if (!categoryMenuOpen.value && !developmentModeMenuOpen.value && !deviceMenuOpen.value) return;
1362
+ closeFilterMenus();
1363
+ event.target?.blur();
1364
+ }
1365
+ function handleFilterOutsidePointerdown(event) {
1366
+ const target = event.target;
1367
+ if (!(target instanceof Element)) return;
1368
+ if (target.closest("[data-layout-filter-menu]")) return;
1369
+ closeFilterMenus();
1370
+ }
1371
+ function selectMaterialCategory(category) {
1372
+ materialCategoryFilter.value = category;
1373
+ closeFilterMenus();
1374
+ }
1375
+ function selectDevelopmentFramework(framework) {
1376
+ activeDevelopmentFramework.value = framework;
1377
+ if (typeof window !== "undefined") window.localStorage.setItem("layout-development-framework", framework);
1378
+ closeFilterMenus();
1379
+ (0, vue.nextTick)(() => {
1380
+ syncVanillaRuntime();
1381
+ syncPreviewDialogRuntime();
1382
+ if (previewMode.value && editor) {
1383
+ editor.setMode("edit");
1384
+ (0, vue.nextTick)(() => editor?.setMode("preview"));
1385
+ }
1386
+ });
1387
+ }
1388
+ function resetMaterialFilters() {
1389
+ materialQuery.value = "";
1390
+ activeMaterialCategory.value = void 0;
1391
+ closeFilterMenus();
1392
+ }
1393
+ function materialIcon(type) {
1394
+ return type === "chart" ? lucide_vue_next.ChartSpline : lucide_vue_next.ChartNoAxesColumnIncreasing;
1395
+ }
1396
+ function setMaterialElement(type, value) {
1397
+ if (value instanceof HTMLElement) materialElements.set(type, value);
1398
+ else materialElements.delete(type);
1399
+ }
1400
+ function syncMaterialSources() {
1401
+ if (!editor) return;
1402
+ const visibleTypes = new Set(visibleMaterials.value.map((material) => material.type));
1403
+ for (const material of visibleMaterials.value) {
1404
+ const element = materialElements.get(material.type);
1405
+ if (!element || materialSourceDisposers.has(material.type)) continue;
1406
+ const size = material.defaultLayout ?? material.size?.default;
1407
+ const dispose = editor.registerExternalDragSource({
1408
+ element,
1409
+ type: material.type,
1410
+ props: { ...material.defaults ?? {} },
1411
+ placement: {
1412
+ x: 0,
1413
+ y: 0,
1414
+ w: size?.w ?? 1,
1415
+ h: size?.h ?? 1
1416
+ }
1417
+ });
1418
+ materialSourceDisposers.set(material.type, dispose);
1419
+ }
1420
+ for (const [type, dispose] of materialSourceDisposers) if (!visibleTypes.has(type)) {
1421
+ dispose();
1422
+ materialSourceDisposers.delete(type);
1423
+ }
1424
+ }
1425
+ (0, vue.watch)([
1426
+ materialQuery,
1427
+ activeMaterialCategory,
1428
+ activeDevelopmentFramework
1429
+ ], () => (0, vue.nextTick)(syncMaterialSources));
1430
+ function removeSelected() {
1431
+ editor?.handleKeyboard({ key: "Delete" });
1432
+ }
1433
+ function toggleSelectedLock() {
1434
+ if (!editor || !selected.value) return;
1435
+ if (selectedLocked.value) editor.unlockItem(selected.value);
1436
+ else editor.lockItem(selected.value);
1437
+ }
1438
+ function toggleSelectedConstraint(key) {
1439
+ if (!editor || !selected.value) return;
1440
+ editor.updateItem(selected.value, (item) => {
1441
+ item.constraints = {
1442
+ ...item.constraints ?? {},
1443
+ [key]: item.constraints?.[key] === false
1444
+ };
1445
+ return item;
1446
+ });
1447
+ }
1448
+ function undo() {
1449
+ editor?.undo();
1450
+ }
1451
+ function redo() {
1452
+ editor?.redo();
1453
+ }
1454
+ function togglePreview() {
1455
+ const nextMode = previewMode.value ? "edit" : "preview";
1456
+ editor?.setMode(nextMode);
1457
+ navigate(nextMode === "preview" ? "runtime" : "editor");
1458
+ }
1459
+ function openPreviewDialog() {
1460
+ previewDialogOpen.value = true;
1461
+ (0, vue.nextTick)(syncPreviewDialogRuntime);
1462
+ }
1463
+ function closePreviewDialog() {
1464
+ previewDialogOpen.value = false;
1465
+ previewDialogRuntimeInstance?.destroy();
1466
+ previewDialogRuntimeInstance = void 0;
1467
+ }
1468
+ function syncPreviewDialogRuntime() {
1469
+ if (activeDevelopmentFramework.value !== "webcomponent" || !previewDialogOpen.value || !previewDialogRuntime.value) {
1470
+ previewDialogRuntimeInstance?.destroy();
1471
+ previewDialogRuntimeInstance = void 0;
1472
+ return;
1473
+ }
1474
+ const runtimeDocument = editor?.exportRuntimeDocument() ?? layout.value;
1475
+ if (!previewDialogRuntimeInstance) {
1476
+ previewDialogRuntimeInstance = (0, _composy_layout_runtime.createLayoutRuntime)({
1477
+ container: previewDialogRuntime.value,
1478
+ document: runtimeDocument,
1479
+ materials,
1480
+ observeResize: false,
1481
+ renderItem: ({ shell, item }) => {
1482
+ const render = () => setNativeRuntimeCard(shell, item);
1483
+ render();
1484
+ return { update: render };
1485
+ }
1486
+ });
1487
+ previewDialogRuntimeInstance.mount();
1488
+ } else previewDialogRuntimeInstance.update(runtimeDocument);
1489
+ previewDialogRuntimeInstance.setBreakpoint(activeBreakpoint.value);
1490
+ }
1491
+ (0, vue.watch)([
1492
+ previewDialogOpen,
1493
+ activeDevelopmentFramework,
1494
+ activeBreakpoint,
1495
+ layout
1496
+ ], () => (0, vue.nextTick)(syncPreviewDialogRuntime));
1497
+ function syncVanillaRuntime() {
1498
+ if (activeDevelopmentFramework.value !== "webcomponent" || activeRoute.value !== "vanilla" || !vanillaRuntime.value || !editor) {
1499
+ vanillaRuntimeInstance?.destroy();
1500
+ vanillaRuntimeInstance = void 0;
1501
+ return;
1502
+ }
1503
+ const document2 = editor.exportRuntimeDocument();
1504
+ if (!vanillaRuntimeInstance) {
1505
+ vanillaRuntimeInstance = (0, _composy_layout_runtime.createLayoutRuntime)({
1506
+ container: vanillaRuntime.value,
1507
+ document: document2,
1508
+ materials,
1509
+ observeResize: false,
1510
+ renderItem: ({ shell, item }) => {
1511
+ const render = () => {
1512
+ setRuntimeCard(shell, item);
1513
+ };
1514
+ render();
1515
+ return { update: render };
1516
+ }
1517
+ });
1518
+ vanillaRuntimeInstance.mount();
1519
+ } else vanillaRuntimeInstance.update(document2);
1520
+ vanillaRuntimeInstance.setBreakpoint(activeBreakpoint.value);
1521
+ }
1522
+ function navigate(route) {
1523
+ activeRoute.value = route;
1524
+ if (typeof window !== "undefined") window.location.hash = `/${route}`;
1525
+ if (route === "runtime") {
1526
+ if (editor?.getMode() === "preview") editor.setMode("edit");
1527
+ editor?.setMode("preview");
1528
+ const clearEditorWidgets = () => {
1529
+ if (activeRoute.value !== "runtime") return;
1530
+ editorGrid.value?.querySelectorAll(".grid-stack-item").forEach((element) => element.remove());
1531
+ };
1532
+ clearEditorWidgets();
1533
+ (0, vue.nextTick)(clearEditorWidgets);
1534
+ } else if (previewMode.value) editor?.setMode("edit");
1535
+ (0, vue.nextTick)(syncVanillaRuntime);
1536
+ }
1537
+ function select(id) {
1538
+ editor?.select(id);
1539
+ }
1540
+ function changeBreakpoint(id) {
1541
+ activeBreakpoint.value = id;
1542
+ canvasOrientation.value = id === "mobile" ? "portrait" : "landscape";
1543
+ editor?.setBreakpoint(id);
1544
+ vanillaRuntimeInstance?.setBreakpoint(id);
1545
+ deviceMenuOpen.value = false;
1546
+ }
1547
+ function selectBreakpoint(id) {
1548
+ changeBreakpoint(id);
1549
+ }
1550
+ function toggleCanvasOrientation() {
1551
+ canvasOrientation.value = canvasOrientation.value === "portrait" ? "landscape" : "portrait";
1552
+ }
1553
+ function clampCanvasZoom(value) {
1554
+ return Math.min(2, Math.max(.5, Math.round(value * 20) / 20));
1555
+ }
1556
+ function setCanvasZoom(value, anchor) {
1557
+ const viewport = canvasViewport.value;
1558
+ const previousZoom = canvasZoom.value;
1559
+ const nextZoom = clampCanvasZoom(value);
1560
+ if (!viewport || previousZoom === nextZoom) return;
1561
+ const rect = viewport.getBoundingClientRect();
1562
+ const anchorX = anchor?.x ?? rect.left + rect.width / 2;
1563
+ const anchorY = anchor?.y ?? rect.top + rect.height / 2;
1564
+ const localX = (anchorX - rect.left - canvasPan.value.x) / previousZoom;
1565
+ const localY = (anchorY - rect.top - canvasPan.value.y) / previousZoom;
1566
+ canvasZoom.value = nextZoom;
1567
+ canvasPan.value = {
1568
+ x: anchorX - rect.left - localX * nextZoom,
1569
+ y: anchorY - rect.top - localY * nextZoom
1570
+ };
1571
+ }
1572
+ function zoomCanvas(direction) {
1573
+ setCanvasZoom(canvasZoom.value + direction * .1);
1574
+ }
1575
+ function resetCanvasView() {
1576
+ canvasZoom.value = 1;
1577
+ canvasPan.value = {
1578
+ x: 0,
1579
+ y: 0
1580
+ };
1581
+ }
1582
+ function resetCanvasPan() {
1583
+ canvasPan.value = {
1584
+ x: 0,
1585
+ y: 0
1586
+ };
1587
+ }
1588
+ function toggleTheme() {
1589
+ theme.value = theme.value === "light" ? "dark" : "light";
1590
+ if (typeof window !== "undefined") window.localStorage.setItem("layout-theme", theme.value);
1591
+ }
1592
+ function handleCanvasWheel(event) {
1593
+ event.preventDefault();
1594
+ const direction = event.deltaY < 0 ? 1 : -1;
1595
+ setCanvasZoom(canvasZoom.value + direction * .08, {
1596
+ x: event.clientX,
1597
+ y: event.clientY
1598
+ });
1599
+ }
1600
+ function handleCanvasPointerDown(event) {
1601
+ if (!(canvasTool.value === "pan" || canvasSpacePressed.value || event.button === 1) || event.button === 2) return;
1602
+ event.preventDefault();
1603
+ event.stopPropagation();
1604
+ event.currentTarget.setPointerCapture?.(event.pointerId);
1605
+ canvasPanning.value = true;
1606
+ canvasPanOrigin.value = {
1607
+ x: event.clientX,
1608
+ y: event.clientY,
1609
+ panX: canvasPan.value.x,
1610
+ panY: canvasPan.value.y
1611
+ };
1612
+ }
1613
+ function handleCanvasPointerMove(event) {
1614
+ if (!canvasPanning.value) return;
1615
+ const origin = canvasPanOrigin.value;
1616
+ canvasPan.value = {
1617
+ x: origin.panX + event.clientX - origin.x,
1618
+ y: origin.panY + event.clientY - origin.y
1619
+ };
1620
+ }
1621
+ function finishCanvasPan(event) {
1622
+ if (!canvasPanning.value) return;
1623
+ canvasPanning.value = false;
1624
+ if (event) event.currentTarget.releasePointerCapture?.(event.pointerId);
1625
+ }
1626
+ function handleCanvasKeydown(event) {
1627
+ if (event.code === "Space") canvasSpacePressed.value = true;
1628
+ }
1629
+ function handleCanvasKeyup(event) {
1630
+ if (event.code === "Space") canvasSpacePressed.value = false;
1631
+ }
1632
+ function handleWorkspaceKeydown(event) {
1633
+ if (!editor || event.defaultPrevented) return;
1634
+ const root = editorGrid.value?.closest("[data-layout-workspace]");
1635
+ if (!root || !(event.target instanceof Node) || !root.contains(event.target)) return;
1636
+ const key = event.key.toLowerCase();
1637
+ if (!((event.ctrlKey || event.metaKey) && [
1638
+ "c",
1639
+ "v",
1640
+ "x",
1641
+ "d"
1642
+ ].includes(key)) && !["delete", "backspace"].includes(key)) return;
1643
+ editor.handleKeyboard({
1644
+ key: event.key,
1645
+ shiftKey: event.shiftKey,
1646
+ ctrlKey: event.ctrlKey,
1647
+ metaKey: event.metaKey,
1648
+ altKey: event.altKey,
1649
+ preventDefault: () => event.preventDefault()
1650
+ });
1651
+ }
1652
+ async function copyActiveCode() {
1653
+ const value = activeCode.value;
1654
+ try {
1655
+ if (navigator.clipboard?.writeText) await navigator.clipboard.writeText(value);
1656
+ else {
1657
+ const textarea = document.createElement("textarea");
1658
+ textarea.value = value;
1659
+ textarea.style.position = "fixed";
1660
+ textarea.style.opacity = "0";
1661
+ document.body.appendChild(textarea);
1662
+ textarea.select();
1663
+ document.execCommand("copy");
1664
+ textarea.remove();
1665
+ }
1666
+ copied.value = true;
1667
+ window.setTimeout(() => {
1668
+ copied.value = false;
1669
+ }, 1600);
1670
+ } catch {
1671
+ copied.value = false;
1672
+ }
1673
+ }
1674
+ function downloadActiveCode() {
1675
+ const blob = new Blob([activeCode.value], { type: "text/plain;charset=utf-8" });
1676
+ const url = URL.createObjectURL(blob);
1677
+ const anchor = document.createElement("a");
1678
+ anchor.href = url;
1679
+ anchor.download = `layout-${activeDevelopmentFramework.value}.${activeCodeExtension.value}`;
1680
+ anchor.click();
1681
+ URL.revokeObjectURL(url);
1682
+ }
1683
+ function handleEditorGridAction(event) {
1684
+ const target = event.target;
1685
+ if (!(target instanceof Element)) return;
1686
+ const action = target.closest("[data-layout-action]");
1687
+ const widget = target.closest(".grid-stack-item");
1688
+ if (!action || !widget || action.dataset.layoutActionPointer === "true") {
1689
+ if (action?.dataset.layoutActionPointer === "true") delete action.dataset.layoutActionPointer;
1690
+ return;
1691
+ }
1692
+ if (event.type === "pointerdown") {
1693
+ action.dataset.layoutActionPointer = "true";
1694
+ event.preventDefault();
1695
+ }
1696
+ const id = widget.getAttribute("gs-id") ?? widget.getAttribute("data-layout-item-id");
1697
+ if (!id || !editor) return;
1698
+ editor.select(id);
1699
+ const kind = action.dataset.layoutAction;
1700
+ if (kind === "copy") editor.copySelection();
1701
+ else if (kind === "delete") editor.removeItem(id);
1702
+ else if (kind === "lock") if (layout.value.items.find((item) => item.id === id)?.state?.locked) editor.unlockItem(id);
1703
+ else editor.lockItem(id);
1704
+ }
1705
+ function syncSelection() {
1706
+ const root = editorGrid.value;
1707
+ if (!root) return;
1708
+ root.querySelectorAll(".grid-stack-item").forEach((item) => {
1709
+ const id = item.getAttribute("gs-id") ?? item.getAttribute("data-layout-item-id") ?? "";
1710
+ const node = layout.value.items.find((candidate) => candidate.id === id);
1711
+ const active = selectedIds.value.includes(id);
1712
+ item.classList.toggle("is-selected", active);
1713
+ item.tabIndex = 0;
1714
+ item.setAttribute("role", "button");
1715
+ if (node) item.setAttribute("aria-label", `${itemTitle(node)}\uFF0C${materialLabel(node.type)}`);
1716
+ item.setAttribute("aria-pressed", String(active));
1717
+ item.setAttribute("aria-selected", String(active));
1718
+ item.setAttribute("aria-disabled", String(Boolean(node?.state?.locked)));
1719
+ });
1720
+ }
1721
+ function syncInteractionPreview() {
1722
+ if (interactionPreviewFrame !== void 0) return;
1723
+ const flush = () => {
1724
+ interactionPreviewFrame = void 0;
1725
+ if (!editor) return;
1726
+ layout.value = editor.exportRuntimeDocument();
1727
+ queueMicrotask(syncSelection);
1728
+ };
1729
+ if (typeof window === "undefined") flush();
1730
+ else interactionPreviewFrame = window.requestAnimationFrame(flush);
1731
+ }
1732
+ (0, vue.onMounted)(() => {
1733
+ if (typeof window !== "undefined") {
1734
+ const savedTheme = window.localStorage.getItem("layout-theme");
1735
+ if (savedTheme === "light" || savedTheme === "dark") theme.value = savedTheme;
1736
+ const savedFramework = window.localStorage.getItem("layout-development-framework");
1737
+ if (savedFramework === "webcomponent" || savedFramework === "vue") activeDevelopmentFramework.value = savedFramework;
1738
+ }
1739
+ if (!editorGrid.value) return;
1740
+ previousGridStackRenderCallback = gridstack.GridStack.renderCB;
1741
+ gridstack.GridStack.renderCB = (element, widget) => {
1742
+ if (element && widget?.content !== void 0) element.innerHTML = String(widget.content);
1743
+ };
1744
+ const engine = (0, _composy_layout_gridstack.createGridStackEditorEngine)({
1745
+ container: editorGrid.value,
1746
+ gridOptions: {
1747
+ acceptWidgets: true,
1748
+ animate: false
1749
+ },
1750
+ createGrid: (options) => gridstack.GridStack.init(options, editorGrid.value),
1751
+ renderItem: ({ item }) => renderCardMarkup(item, { interactive: true })
1752
+ });
1753
+ editor = (0, _composy_layout_editor.createLayoutEditor)({
1754
+ document: initial,
1755
+ engine,
1756
+ materials,
1757
+ preview: runtimePreview.value ? {
1758
+ container: runtimePreview.value,
1759
+ renderItem: ({ shell, item }) => {
1760
+ const render = () => {
1761
+ setRuntimeCard(shell, item);
1762
+ };
1763
+ render();
1764
+ return { update: render };
1765
+ }
1766
+ } : void 0
1767
+ });
1768
+ editor.on("document:change", (event) => {
1769
+ layout.value = event.document;
1770
+ workspaceEmit("update:document", event.document);
1771
+ workspaceEmit("change", event.document);
1772
+ queueMicrotask(syncSelection);
1773
+ syncVanillaRuntime();
1774
+ });
1775
+ editor.on("interaction:move", syncInteractionPreview);
1776
+ editor.on("interaction:resize", syncInteractionPreview);
1777
+ editor.on("selection:change", (event) => {
1778
+ selected.value = event.primaryId;
1779
+ selectedIds.value = [...event.selectedIds];
1780
+ syncSelection();
1781
+ });
1782
+ editor.on("mode:change", (event) => {
1783
+ previewMode.value = event.mode === "preview";
1784
+ if (event.mode === "preview" && activeRoute.value !== "runtime") activeRoute.value = "runtime";
1785
+ if (event.mode === "edit" && activeRoute.value === "runtime") activeRoute.value = "editor";
1786
+ (0, vue.nextTick)(syncVanillaRuntime);
1787
+ });
1788
+ editor.mount();
1789
+ editorGrid.value.addEventListener("pointerdown", handleEditorGridAction, true);
1790
+ editorGrid.value.addEventListener("click", handleEditorGridAction, true);
1791
+ disposers.push(() => editorGrid.value?.removeEventListener("pointerdown", handleEditorGridAction, true));
1792
+ disposers.push(() => editorGrid.value?.removeEventListener("click", handleEditorGridAction, true));
1793
+ (0, vue.nextTick)(syncMaterialSources);
1794
+ syncSelection();
1795
+ if (typeof window !== "undefined") {
1796
+ activeRoute.value = parseRoute(window.location.hash);
1797
+ const onHashChange = () => {
1798
+ const next = parseRoute(window.location.hash);
1799
+ if (next !== activeRoute.value) navigate(next);
1800
+ else if (next === "runtime" && editor?.getMode() !== "preview") editor.setMode("preview");
1801
+ else if (next !== "runtime" && editor?.getMode() === "preview") editor.setMode("edit");
1802
+ };
1803
+ window.addEventListener("hashchange", onHashChange);
1804
+ disposers.push(() => window.removeEventListener("hashchange", onHashChange));
1805
+ window.addEventListener("keydown", handleCanvasKeydown);
1806
+ window.addEventListener("keyup", handleCanvasKeyup);
1807
+ window.addEventListener("keydown", handleWorkspaceKeydown);
1808
+ document.addEventListener("pointerdown", handleFilterOutsidePointerdown);
1809
+ document.addEventListener("keydown", handleFilterMenuKeydown);
1810
+ disposers.push(() => window.removeEventListener("keydown", handleCanvasKeydown));
1811
+ disposers.push(() => window.removeEventListener("keyup", handleCanvasKeyup));
1812
+ disposers.push(() => window.removeEventListener("keydown", handleWorkspaceKeydown));
1813
+ disposers.push(() => document.removeEventListener("pointerdown", handleFilterOutsidePointerdown));
1814
+ disposers.push(() => document.removeEventListener("keydown", handleFilterMenuKeydown));
1815
+ }
1816
+ if (activeRoute.value === "runtime") editor.setMode("preview");
1817
+ syncVanillaRuntime();
1818
+ });
1819
+ (0, vue.onBeforeUnmount)(() => {
1820
+ if (interactionPreviewFrame !== void 0 && typeof window !== "undefined") window.cancelAnimationFrame(interactionPreviewFrame);
1821
+ interactionPreviewFrame = void 0;
1822
+ materialSourceDisposers.forEach((dispose) => dispose());
1823
+ materialSourceDisposers.clear();
1824
+ disposers.splice(0).forEach((dispose) => dispose());
1825
+ vanillaRuntimeInstance?.destroy();
1826
+ vanillaRuntimeInstance = void 0;
1827
+ previewDialogRuntimeInstance?.destroy();
1828
+ previewDialogRuntimeInstance = void 0;
1829
+ editor?.destroy();
1830
+ if (previousGridStackRenderCallback) {
1831
+ gridstack.GridStack.renderCB = previousGridStackRenderCallback;
1832
+ previousGridStackRenderCallback = void 0;
1833
+ }
1834
+ });
1835
+ return (_ctx, _cache) => {
1836
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)("main", {
1837
+ class: "layout-demo",
1838
+ "data-layout-workspace": "",
1839
+ "data-theme": theme.value,
1840
+ "data-development-framework": activeDevelopmentFramework.value
1841
+ }, [
1842
+ (0, vue.createElementVNode)("header", _hoisted_2, [
1843
+ (0, vue.createElementVNode)("div", _hoisted_3, [
1844
+ (0, vue.createElementVNode)("span", _hoisted_4, [(0, vue.createVNode)((0, vue.unref)(lucide_vue_next.Grid2X2), {
1845
+ size: 18,
1846
+ "stroke-width": 1.8
1847
+ })]),
1848
+ _cache[20] || (_cache[20] = (0, vue.createElementVNode)("div", { class: "brand-copy" }, [(0, vue.createElementVNode)("strong", null, "布局工作台"), (0, vue.createElementVNode)("span", null, "所见即所得 · 网格布局")], -1)),
1849
+ (0, vue.createElementVNode)("div", {
1850
+ class: (0, vue.normalizeClass)(["development-mode custom-select", { "is-open": developmentModeMenuOpen.value }]),
1851
+ "data-layout-filter-menu": "",
1852
+ onPointerdown: _cache[0] || (_cache[0] = (0, vue.withModifiers)(() => {}, ["stop"]))
1853
+ }, [(0, vue.createElementVNode)("button", {
1854
+ class: "custom-select__trigger",
1855
+ type: "button",
1856
+ "aria-haspopup": "listbox",
1857
+ "aria-expanded": developmentModeMenuOpen.value,
1858
+ "aria-label": "开发框架",
1859
+ title: `\u5F53\u524D\u5F00\u53D1\u6846\u67B6\uFF1A${activeDevelopmentFrameworkLabel.value}`,
1860
+ onClick: (0, vue.withModifiers)(toggleDevelopmentModeMenu, ["stop"])
1861
+ }, [
1862
+ ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)(developmentFrameworks.find((framework) => framework.id === activeDevelopmentFramework.value)?.icon ?? (0, vue.unref)(lucide_vue_next.PanelsTopLeft)), {
1863
+ size: 14,
1864
+ "stroke-width": 1.9,
1865
+ "aria-hidden": "true"
1866
+ })),
1867
+ (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)(activeDevelopmentFrameworkLabel.value), 1),
1868
+ (0, vue.createVNode)((0, vue.unref)(lucide_vue_next.ChevronDown), {
1869
+ size: 12,
1870
+ "stroke-width": 1.9,
1871
+ "aria-hidden": "true"
1872
+ })
1873
+ ], 8, _hoisted_5), developmentModeMenuOpen.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_6, [((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(developmentFrameworks, (framework) => {
1874
+ return (0, vue.createElementVNode)("button", {
1875
+ key: framework.id,
1876
+ class: "custom-select__option",
1877
+ type: "button",
1878
+ "aria-selected": activeDevelopmentFramework.value === framework.id,
1879
+ onClick: ($event) => selectDevelopmentFramework(framework.id)
1880
+ }, [((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)(framework.icon), {
1881
+ size: 14,
1882
+ "stroke-width": 1.8,
1883
+ "aria-hidden": "true"
1884
+ })), (0, vue.createElementVNode)("span", null, [(0, vue.createElementVNode)("strong", null, (0, vue.toDisplayString)(framework.label), 1), (0, vue.createElementVNode)("small", null, (0, vue.toDisplayString)(framework.description), 1)])], 8, _hoisted_7);
1885
+ }), 64))])) : (0, vue.createCommentVNode)("v-if", true)], 34)
1886
+ ]),
1887
+ (0, vue.createElementVNode)("div", _hoisted_8, [(0, vue.createElementVNode)("nav", _hoisted_9, [((0, vue.openBlock)(), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(routes, (demoRoute) => {
1888
+ return (0, vue.createElementVNode)("button", {
1889
+ key: demoRoute.id,
1890
+ type: "button",
1891
+ "aria-current": activeRoute.value === demoRoute.id ? "page" : void 0,
1892
+ "aria-label": `${demoRoute.label}\u6A21\u5F0F`,
1893
+ title: demoRoute.description,
1894
+ onClick: ($event) => navigate(demoRoute.id)
1895
+ }, [((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)(routeIcons[demoRoute.id]), {
1896
+ class: "route-icon",
1897
+ size: 15,
1898
+ "stroke-width": 1.8,
1899
+ "aria-hidden": "true"
1900
+ })), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)(demoRoute.label), 1)], 8, _hoisted_10);
1901
+ }), 64))]), (0, vue.createElementVNode)("div", _hoisted_11, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(layout.value.breakpoints, (breakpoint) => {
1902
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
1903
+ key: breakpoint.id,
1904
+ class: "breakpoint-tab",
1905
+ type: "button",
1906
+ "aria-pressed": activeBreakpoint.value === breakpoint.id,
1907
+ "aria-label": `\u5207\u6362\u5230${breakpointLabel(breakpoint.id)}\u753B\u5E03`,
1908
+ title: `${breakpointLabel(breakpoint.id)}\u753B\u5E03`,
1909
+ onClick: ($event) => changeBreakpoint(breakpoint.id)
1910
+ }, [((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)(breakpointIcons[breakpoint.id] ?? (0, vue.unref)(lucide_vue_next.Monitor)), {
1911
+ size: 14,
1912
+ "stroke-width": 1.8,
1913
+ "aria-hidden": "true"
1914
+ })), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)(breakpointLabel(breakpoint.id)), 1)], 8, _hoisted_12);
1915
+ }), 128)), (0, vue.createCommentVNode)("v-if", true)])]),
1916
+ (0, vue.createElementVNode)("div", _hoisted_13, [
1917
+ (0, vue.createElementVNode)("button", {
1918
+ class: "icon-button",
1919
+ type: "button",
1920
+ "aria-label": "撤销",
1921
+ title: "撤销",
1922
+ disabled: !(0, vue.unref)(editor)?.canUndo(),
1923
+ onClick: undo
1924
+ }, [(0, vue.createVNode)((0, vue.unref)(lucide_vue_next.Undo2), {
1925
+ size: 16,
1926
+ "stroke-width": 1.9,
1927
+ "aria-hidden": "true"
1928
+ })], 8, _hoisted_14),
1929
+ (0, vue.createElementVNode)("button", {
1930
+ class: "icon-button",
1931
+ type: "button",
1932
+ "aria-label": "重做",
1933
+ title: "重做",
1934
+ disabled: !(0, vue.unref)(editor)?.canRedo(),
1935
+ onClick: redo
1936
+ }, [(0, vue.createVNode)((0, vue.unref)(lucide_vue_next.Redo2), {
1937
+ size: 16,
1938
+ "stroke-width": 1.9,
1939
+ "aria-hidden": "true"
1940
+ })], 8, _hoisted_15),
1941
+ (0, vue.createElementVNode)("button", {
1942
+ class: "icon-button",
1943
+ type: "button",
1944
+ "aria-label": "删除选中组件",
1945
+ title: "删除选中组件",
1946
+ disabled: !selected.value,
1947
+ onClick: removeSelected
1948
+ }, [(0, vue.createVNode)((0, vue.unref)(lucide_vue_next.Trash2), {
1949
+ size: 16,
1950
+ "stroke-width": 1.9,
1951
+ "aria-hidden": "true"
1952
+ })], 8, _hoisted_16),
1953
+ (0, vue.createElementVNode)("button", {
1954
+ class: "icon-button",
1955
+ type: "button",
1956
+ "aria-label": selectedLocked.value ? "解锁选中组件" : "锁定选中组件",
1957
+ title: selectedLocked.value ? "解锁选中组件" : "锁定选中组件",
1958
+ disabled: !selected.value,
1959
+ onClick: toggleSelectedLock
1960
+ }, [((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)(selectedLocked.value ? (0, vue.unref)(lucide_vue_next.Unlock) : (0, vue.unref)(lucide_vue_next.Lock)), {
1961
+ size: 16,
1962
+ "stroke-width": 1.9,
1963
+ "aria-hidden": "true"
1964
+ }))], 8, _hoisted_17),
1965
+ (0, vue.createElementVNode)("button", {
1966
+ class: "icon-button",
1967
+ type: "button",
1968
+ "aria-label": selectedMovable.value ? "禁止移动" : "允许移动",
1969
+ title: selectedMovable.value ? "禁止移动" : "允许移动",
1970
+ disabled: !selected.value || selectedLocked.value,
1971
+ onClick: _cache[1] || (_cache[1] = ($event) => toggleSelectedConstraint("movable"))
1972
+ }, [(0, vue.createVNode)((0, vue.unref)(lucide_vue_next.Move), {
1973
+ size: 16,
1974
+ "stroke-width": 1.9,
1975
+ "aria-hidden": "true"
1976
+ })], 8, _hoisted_18),
1977
+ (0, vue.createElementVNode)("button", {
1978
+ class: "icon-button",
1979
+ type: "button",
1980
+ "aria-label": selectedResizable.value ? "禁止调整大小" : "允许调整大小",
1981
+ title: selectedResizable.value ? "禁止调整大小" : "允许调整大小",
1982
+ disabled: !selected.value || selectedLocked.value,
1983
+ onClick: _cache[2] || (_cache[2] = ($event) => toggleSelectedConstraint("resizable"))
1984
+ }, [(0, vue.createVNode)((0, vue.unref)(lucide_vue_next.Maximize2), {
1985
+ size: 16,
1986
+ "stroke-width": 1.9,
1987
+ "aria-hidden": "true"
1988
+ })], 8, _hoisted_19),
1989
+ (0, vue.createElementVNode)("button", {
1990
+ class: "icon-button theme-button",
1991
+ type: "button",
1992
+ "aria-label": theme.value === "dark" ? "切换到亮色主题" : "切换到暗色主题",
1993
+ title: theme.value === "dark" ? "切换到亮色主题" : "切换到暗色主题",
1994
+ onClick: toggleTheme
1995
+ }, [((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)(theme.value === "dark" ? (0, vue.unref)(lucide_vue_next.Sun) : (0, vue.unref)(lucide_vue_next.Moon)), {
1996
+ size: 16,
1997
+ "stroke-width": 1.9,
1998
+ "aria-hidden": "true"
1999
+ }))], 8, _hoisted_20),
2000
+ (0, vue.createElementVNode)("button", {
2001
+ class: "preview-open-button",
2002
+ type: "button",
2003
+ "aria-label": "打开实时预览",
2004
+ title: "打开实时预览",
2005
+ onClick: openPreviewDialog
2006
+ }, [(0, vue.createVNode)((0, vue.unref)(lucide_vue_next.Eye), {
2007
+ size: 15,
2008
+ "stroke-width": 1.9,
2009
+ "aria-hidden": "true"
2010
+ }), _cache[21] || (_cache[21] = (0, vue.createElementVNode)("span", null, "实时预览", -1))]),
2011
+ (0, vue.createElementVNode)("button", {
2012
+ class: "mode-button",
2013
+ type: "button",
2014
+ "aria-pressed": previewMode.value,
2015
+ "aria-label": previewMode.value ? "返回编辑模式" : "进入预览模式",
2016
+ title: previewMode.value ? "返回编辑模式" : "进入预览模式",
2017
+ onClick: togglePreview
2018
+ }, [((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)(previewMode.value ? (0, vue.unref)(lucide_vue_next.Pencil) : (0, vue.unref)(lucide_vue_next.Eye)), {
2019
+ size: 16,
2020
+ "stroke-width": 1.9,
2021
+ "aria-hidden": "true"
2022
+ })), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)(previewMode.value ? "编辑" : "预览"), 1)], 8, _hoisted_21)
2023
+ ])
2024
+ ]),
2025
+ (0, vue.createElementVNode)("section", _hoisted_22, [
2026
+ (0, vue.createElementVNode)("aside", _hoisted_23, [
2027
+ (0, vue.createElementVNode)("div", _hoisted_24, [_cache[22] || (_cache[22] = (0, vue.createElementVNode)("div", null, [(0, vue.createElementVNode)("span", { class: "eyebrow" }, "工作区"), (0, vue.createElementVNode)("h2", null, "物料")], -1)), (0, vue.createElementVNode)("span", _hoisted_25, (0, vue.toDisplayString)((0, vue.unref)(materials).getAll().length), 1)]),
2028
+ (0, vue.createElementVNode)("div", _hoisted_26, [
2029
+ (0, vue.createElementVNode)("label", _hoisted_27, [(0, vue.createVNode)((0, vue.unref)(lucide_vue_next.Search), {
2030
+ size: 15,
2031
+ "stroke-width": 1.8,
2032
+ "aria-hidden": "true"
2033
+ }), (0, vue.withDirectives)((0, vue.createElementVNode)("input", {
2034
+ "onUpdate:modelValue": _cache[3] || (_cache[3] = ($event) => materialQuery.value = $event),
2035
+ type: "search",
2036
+ placeholder: "搜索物料",
2037
+ "aria-label": "搜索物料"
2038
+ }, null, 512), [[vue.vModelText, materialQuery.value]])]),
2039
+ (0, vue.createElementVNode)("div", {
2040
+ class: "material-filter-row",
2041
+ "data-layout-filter-menu": "",
2042
+ onKeydown: (0, vue.withKeys)((0, vue.withModifiers)(handleFilterMenuKeydown, ["stop", "prevent"]), ["esc"])
2043
+ }, [(0, vue.createElementVNode)("div", {
2044
+ class: (0, vue.normalizeClass)(["material-select custom-select", { "is-open": categoryMenuOpen.value }]),
2045
+ onPointerdown: _cache[6] || (_cache[6] = (0, vue.withModifiers)(() => {}, ["stop"]))
2046
+ }, [(0, vue.createElementVNode)("button", {
2047
+ class: "custom-select__trigger",
2048
+ type: "button",
2049
+ "aria-haspopup": "listbox",
2050
+ "aria-expanded": categoryMenuOpen.value,
2051
+ "aria-label": "物料分类",
2052
+ title: `\u7269\u6599\u5206\u7C7B\uFF1A${activeMaterialCategoryLabel.value}`,
2053
+ onClick: _cache[4] || (_cache[4] = (0, vue.withModifiers)(($event) => toggleFilterMenu("category"), ["stop"]))
2054
+ }, [
2055
+ (0, vue.createVNode)((0, vue.unref)(lucide_vue_next.Filter), {
2056
+ size: 13,
2057
+ "stroke-width": 1.9,
2058
+ "aria-hidden": "true"
2059
+ }),
2060
+ (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)(activeMaterialCategoryLabel.value), 1),
2061
+ (0, vue.createVNode)((0, vue.unref)(lucide_vue_next.ChevronDown), {
2062
+ size: 12,
2063
+ "stroke-width": 1.9,
2064
+ "aria-hidden": "true"
2065
+ })
2066
+ ], 8, _hoisted_29), categoryMenuOpen.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_30, [(0, vue.createElementVNode)("button", {
2067
+ class: "custom-select__option",
2068
+ type: "button",
2069
+ "aria-selected": !materialCategoryFilter.value,
2070
+ onClick: _cache[5] || (_cache[5] = ($event) => selectMaterialCategory(""))
2071
+ }, " 全部分类 ", 8, _hoisted_31), ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(materialCategories.value, (category) => {
2072
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
2073
+ key: category.id,
2074
+ class: "custom-select__option",
2075
+ type: "button",
2076
+ "aria-selected": materialCategoryFilter.value === category.id,
2077
+ onClick: ($event) => selectMaterialCategory(category.id)
2078
+ }, (0, vue.toDisplayString)(category.label), 9, _hoisted_32);
2079
+ }), 128))])) : (0, vue.createCommentVNode)("v-if", true)], 34), hasMaterialFilters.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
2080
+ key: 0,
2081
+ class: "material-filter-reset",
2082
+ type: "button",
2083
+ "aria-label": "清除物料筛选",
2084
+ title: "清除筛选",
2085
+ onClick: resetMaterialFilters
2086
+ }, [(0, vue.createVNode)((0, vue.unref)(lucide_vue_next.X), {
2087
+ size: 13,
2088
+ "stroke-width": 1.9,
2089
+ "aria-hidden": "true"
2090
+ })])) : (0, vue.createCommentVNode)("v-if", true)], 40, _hoisted_28),
2091
+ (0, vue.createElementVNode)("div", _hoisted_33, [(0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)(visibleMaterials.value.length) + " / " + (0, vue.toDisplayString)((0, vue.unref)(materials).getAll().length) + " 个物料", 1), hasMaterialFilters.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("span", _hoisted_34, "已筛选")) : (0, vue.createCommentVNode)("v-if", true)])
2092
+ ]),
2093
+ !visibleMaterials.value.length ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_35, [
2094
+ (0, vue.createVNode)((0, vue.unref)(lucide_vue_next.Search), {
2095
+ size: 17,
2096
+ "stroke-width": 1.7,
2097
+ "aria-hidden": "true"
2098
+ }),
2099
+ _cache[23] || (_cache[23] = (0, vue.createElementVNode)("strong", null, "没有匹配的物料", -1)),
2100
+ _cache[24] || (_cache[24] = (0, vue.createElementVNode)("span", null, "试试更换关键词或分类", -1)),
2101
+ hasMaterialFilters.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
2102
+ key: 0,
2103
+ type: "button",
2104
+ onClick: resetMaterialFilters
2105
+ }, " 清除筛选 ")) : (0, vue.createCommentVNode)("v-if", true)
2106
+ ])) : (0, vue.createCommentVNode)("v-if", true),
2107
+ ((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(visibleMaterials.value, (material) => {
2108
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
2109
+ key: material.type,
2110
+ ref_for: true,
2111
+ ref: (element) => setMaterialElement(material.type, element),
2112
+ class: "material-source grid-stack-item",
2113
+ role: "button",
2114
+ tabindex: "0",
2115
+ "data-material-type": material.type,
2116
+ "data-layout-material": material.type,
2117
+ "data-layout-props": materialProps(material.type),
2118
+ "aria-label": `\u6DFB\u52A0${material.title ?? material.label ?? material.type}`,
2119
+ onClick: ($event) => handleMaterialClick(material.type),
2120
+ onKeydown: [(0, vue.withKeys)(($event) => add(material.type), ["enter"]), (0, vue.withKeys)((0, vue.withModifiers)(($event) => add(material.type), ["prevent"]), ["space"])]
2121
+ }, [
2122
+ (0, vue.createElementVNode)("span", _hoisted_37, [((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)(materialIcon(material.type)), {
2123
+ size: 18,
2124
+ "stroke-width": 1.8
2125
+ }))]),
2126
+ (0, vue.createElementVNode)("span", _hoisted_38, [(0, vue.createElementVNode)("strong", null, (0, vue.toDisplayString)(material.title ?? material.label ?? material.type), 1), (0, vue.createElementVNode)("small", null, (0, vue.toDisplayString)(material.description ?? material.categoryLabel ?? "布局组件"), 1)]),
2127
+ (0, vue.createElementVNode)("span", _hoisted_39, [(0, vue.createElementVNode)("span", _hoisted_40, (0, vue.toDisplayString)(material.defaultLayout?.w ?? material.size?.default.w ?? 1) + " × " + (0, vue.toDisplayString)(material.defaultLayout?.h ?? material.size?.default.h ?? 1), 1), (0, vue.createElementVNode)("span", _hoisted_41, [(0, vue.createElementVNode)("span", {
2128
+ class: (0, vue.normalizeClass)(["material-renderer-badge", { "material-renderer-badge--vue": activeDevelopmentFramework.value === "vue" }]),
2129
+ title: `\u5F53\u524D\u5F00\u53D1\u76EE\u6807\uFF1A${activeDevelopmentFrameworkLabel.value}`
2130
+ }, [((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)(activeDevelopmentFramework.value === "vue" ? (0, vue.unref)(lucide_vue_next.PanelsTopLeft) : (0, vue.unref)(lucide_vue_next.Code2)), {
2131
+ size: 11,
2132
+ "stroke-width": 1.8,
2133
+ "aria-hidden": "true"
2134
+ })), (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)(activeDevelopmentFrameworkLabel.value), 1)], 10, _hoisted_42)])]),
2135
+ (0, vue.createElementVNode)("span", _hoisted_43, [(0, vue.createElementVNode)("button", {
2136
+ class: "material-code-button",
2137
+ type: "button",
2138
+ "aria-label": "查看当前物料代码",
2139
+ title: "查看当前物料代码",
2140
+ onPointerdown: _cache[7] || (_cache[7] = (0, vue.withModifiers)(() => {}, ["stop"])),
2141
+ onClick: (0, vue.withModifiers)(($event) => openMaterialCode(material.type), ["stop"])
2142
+ }, [(0, vue.createVNode)((0, vue.unref)(lucide_vue_next.Code2), {
2143
+ size: 14,
2144
+ "stroke-width": 1.8,
2145
+ "aria-hidden": "true"
2146
+ })], 40, _hoisted_44), (0, vue.createElementVNode)("span", _hoisted_45, [(0, vue.createVNode)((0, vue.unref)(lucide_vue_next.Plus), {
2147
+ size: 16,
2148
+ "stroke-width": 1.8
2149
+ })])])
2150
+ ], 40, _hoisted_36);
2151
+ }), 128)),
2152
+ (0, vue.createCommentVNode)("v-if", true),
2153
+ (0, vue.createCommentVNode)("v-if", true)
2154
+ ]),
2155
+ (0, vue.createElementVNode)("section", _hoisted_50, [(0, vue.createElementVNode)("div", _hoisted_51, [
2156
+ (0, vue.createElementVNode)("div", {
2157
+ class: (0, vue.normalizeClass)(["canvas-device-control custom-select", { "is-open": deviceMenuOpen.value }]),
2158
+ onPointerdown: _cache[15] || (_cache[15] = (0, vue.withModifiers)(() => {}, ["stop"]))
2159
+ }, [(0, vue.createElementVNode)("button", {
2160
+ class: "custom-select__trigger",
2161
+ type: "button",
2162
+ "aria-haspopup": "listbox",
2163
+ "aria-expanded": deviceMenuOpen.value,
2164
+ "aria-label": "设备画布",
2165
+ onClick: _cache[14] || (_cache[14] = (0, vue.withModifiers)(($event) => toggleFilterMenu("device"), ["stop"]))
2166
+ }, [
2167
+ ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)(activeBreakpointIcon.value), {
2168
+ size: 13,
2169
+ "stroke-width": 1.9,
2170
+ "aria-hidden": "true"
2171
+ })),
2172
+ (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)(breakpointLabel(activeBreakpoint.value)), 1),
2173
+ (0, vue.createVNode)((0, vue.unref)(lucide_vue_next.ChevronDown), {
2174
+ size: 12,
2175
+ "stroke-width": 1.9,
2176
+ "aria-hidden": "true"
2177
+ })
2178
+ ], 8, _hoisted_52), deviceMenuOpen.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", _hoisted_53, [((0, vue.openBlock)(true), (0, vue.createElementBlock)(vue.Fragment, null, (0, vue.renderList)(layout.value.breakpoints, (breakpoint) => {
2179
+ return (0, vue.openBlock)(), (0, vue.createElementBlock)("button", {
2180
+ key: breakpoint.id,
2181
+ class: "custom-select__option",
2182
+ type: "button",
2183
+ "aria-selected": activeBreakpoint.value === breakpoint.id,
2184
+ "aria-label": breakpointLabel(breakpoint.id) + " / " + breakpoint.columns + " 列",
2185
+ onClick: ($event) => selectBreakpoint(breakpoint.id)
2186
+ }, [
2187
+ ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)(breakpointIcons[breakpoint.id] ?? (0, vue.unref)(lucide_vue_next.Monitor)), {
2188
+ size: 13,
2189
+ "stroke-width": 1.8,
2190
+ "aria-hidden": "true"
2191
+ })),
2192
+ (0, vue.createElementVNode)("span", null, (0, vue.toDisplayString)(breakpointLabel(breakpoint.id)), 1),
2193
+ (0, vue.createElementVNode)("span", _hoisted_55, (0, vue.toDisplayString)(breakpoint.columns) + " 列", 1)
2194
+ ], 8, _hoisted_54);
2195
+ }), 128))])) : (0, vue.createCommentVNode)("v-if", true)], 34),
2196
+ (0, vue.createElementVNode)("button", {
2197
+ class: "orientation-button",
2198
+ type: "button",
2199
+ "aria-label": canvasOrientation.value === "portrait" ? "切换为横屏" : "切换为竖屏",
2200
+ title: canvasOrientation.value === "portrait" ? "切换为横屏" : "切换为竖屏",
2201
+ onClick: toggleCanvasOrientation
2202
+ }, [((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)(canvasOrientation.value === "portrait" ? (0, vue.unref)(lucide_vue_next.ArrowLeftRight) : (0, vue.unref)(lucide_vue_next.ArrowUpDown)), {
2203
+ size: 14,
2204
+ "stroke-width": 1.9,
2205
+ "aria-hidden": "true"
2206
+ }))], 8, _hoisted_56),
2207
+ _cache[27] || (_cache[27] = (0, vue.createElementVNode)("span", {
2208
+ class: "canvas-toolbar__device-divider",
2209
+ "aria-hidden": "true"
2210
+ }, null, -1)),
2211
+ (0, vue.createElementVNode)("div", _hoisted_57, [(0, vue.createElementVNode)("button", {
2212
+ class: (0, vue.normalizeClass)(["canvas-tool-button", { active: canvasTool.value === "select" }]),
2213
+ type: "button",
2214
+ "aria-label": "选择组件",
2215
+ title: "选择组件",
2216
+ onClick: _cache[16] || (_cache[16] = ($event) => canvasTool.value = "select")
2217
+ }, [(0, vue.createVNode)((0, vue.unref)(lucide_vue_next.MousePointer2), {
2218
+ size: 15,
2219
+ "stroke-width": 1.9,
2220
+ "aria-hidden": "true"
2221
+ })], 2), (0, vue.createElementVNode)("button", {
2222
+ class: (0, vue.normalizeClass)(["canvas-tool-button", { active: canvasTool.value === "pan" }]),
2223
+ type: "button",
2224
+ "aria-label": "移动画布",
2225
+ title: "移动画布(也可按住空格拖动)",
2226
+ onClick: _cache[17] || (_cache[17] = ($event) => canvasTool.value = canvasTool.value === "pan" ? "select" : "pan")
2227
+ }, [(0, vue.createVNode)((0, vue.unref)(lucide_vue_next.Hand), {
2228
+ size: 15,
2229
+ "stroke-width": 1.9,
2230
+ "aria-hidden": "true"
2231
+ })], 2)]),
2232
+ _cache[28] || (_cache[28] = (0, vue.createElementVNode)("div", {
2233
+ class: "canvas-toolbar__divider",
2234
+ "aria-hidden": "true"
2235
+ }, null, -1)),
2236
+ (0, vue.createElementVNode)("button", {
2237
+ class: "canvas-tool-button",
2238
+ type: "button",
2239
+ "aria-label": "缩小画布",
2240
+ title: "缩小画布",
2241
+ onClick: _cache[18] || (_cache[18] = ($event) => zoomCanvas(-1))
2242
+ }, [(0, vue.createVNode)((0, vue.unref)(lucide_vue_next.ZoomOut), {
2243
+ size: 15,
2244
+ "stroke-width": 1.9,
2245
+ "aria-hidden": "true"
2246
+ })]),
2247
+ (0, vue.createElementVNode)("span", _hoisted_58, (0, vue.toDisplayString)(canvasZoomLabel.value), 1),
2248
+ (0, vue.createElementVNode)("button", {
2249
+ class: "canvas-tool-button",
2250
+ type: "button",
2251
+ "aria-label": "放大画布",
2252
+ title: "放大画布",
2253
+ onClick: _cache[19] || (_cache[19] = ($event) => zoomCanvas(1))
2254
+ }, [(0, vue.createVNode)((0, vue.unref)(lucide_vue_next.ZoomIn), {
2255
+ size: 15,
2256
+ "stroke-width": 1.9,
2257
+ "aria-hidden": "true"
2258
+ })]),
2259
+ (0, vue.createElementVNode)("button", {
2260
+ class: "canvas-tool-button",
2261
+ type: "button",
2262
+ "aria-label": "适配画布",
2263
+ title: "适配画布",
2264
+ onClick: resetCanvasView
2265
+ }, [(0, vue.createVNode)((0, vue.unref)(lucide_vue_next.Scan), {
2266
+ size: 15,
2267
+ "stroke-width": 1.9,
2268
+ "aria-hidden": "true"
2269
+ })]),
2270
+ (0, vue.createElementVNode)("button", {
2271
+ class: "canvas-tool-button",
2272
+ type: "button",
2273
+ "aria-label": "重置画布位置",
2274
+ title: "重置画布位置",
2275
+ onClick: resetCanvasPan
2276
+ }, [(0, vue.createVNode)((0, vue.unref)(lucide_vue_next.RotateCcw), {
2277
+ size: 15,
2278
+ "stroke-width": 1.9,
2279
+ "aria-hidden": "true"
2280
+ })])
2281
+ ]), (0, vue.createElementVNode)("div", {
2282
+ ref_key: "canvasViewport",
2283
+ ref: canvasViewport,
2284
+ class: (0, vue.normalizeClass)(["canvas-viewport", {
2285
+ "is-panning": canvasPanning.value,
2286
+ "is-pan-tool": canvasTool.value === "pan"
2287
+ }]),
2288
+ onWheel: handleCanvasWheel,
2289
+ onPointerdownCapture: handleCanvasPointerDown,
2290
+ onPointermove: handleCanvasPointerMove,
2291
+ onPointerup: finishCanvasPan,
2292
+ onPointercancel: finishCanvasPan
2293
+ }, [(0, vue.createElementVNode)("div", {
2294
+ class: (0, vue.normalizeClass)(["canvas-scene", `is-${canvasOrientation.value}`]),
2295
+ "data-canvas-width": canvasSize.value.width,
2296
+ "data-canvas-height": canvasSize.value.height,
2297
+ "data-canvas-orientation": canvasOrientation.value,
2298
+ style: (0, vue.normalizeStyle)(canvasSceneStyle.value)
2299
+ }, [
2300
+ (0, vue.withDirectives)((0, vue.createElementVNode)("section", _hoisted_60, [(0, vue.createElementVNode)("header", _hoisted_61, [_cache[30] || (_cache[30] = (0, vue.createElementVNode)("div", null, [(0, vue.createElementVNode)("span", { class: "stage-kicker" }, "编辑模式"), (0, vue.createElementVNode)("h2", null, "拖拽布局")], -1)), (0, vue.createElementVNode)("span", _hoisted_62, [_cache[29] || (_cache[29] = (0, vue.createElementVNode)("i", { "aria-hidden": "true" }, null, -1)), (0, vue.createTextVNode)((0, vue.toDisplayString)(breakpointLabel(activeBreakpoint.value)), 1)])]), (0, vue.createElementVNode)("div", {
2301
+ ref_key: "editorGrid",
2302
+ ref: editorGrid,
2303
+ class: "grid-stack layout-editor-grid",
2304
+ role: "grid",
2305
+ "aria-label": "网格布局编辑器",
2306
+ "aria-multiselectable": "true"
2307
+ }, null, 512)], 512), [[vue.vShow, activeRoute.value === "editor" && !previewMode.value]]),
2308
+ (0, vue.withDirectives)((0, vue.createElementVNode)("section", _hoisted_63, [_cache[31] || (_cache[31] = (0, vue.createElementVNode)("header", { class: "stage-header" }, [(0, vue.createElementVNode)("div", null, [(0, vue.createElementVNode)("span", { class: "stage-kicker" }, "预览模式"), (0, vue.createElementVNode)("h2", null, "组件渲染")]), (0, vue.createElementVNode)("span", { class: "stage-badge" }, [(0, vue.createElementVNode)("i", { "aria-hidden": "true" }), (0, vue.createTextVNode)("同步布局")])], -1)), (0, vue.createVNode)((0, vue.unref)(LGrid), {
2309
+ document: layout.value,
2310
+ breakpoint: activeBreakpoint.value,
2311
+ materials: (0, vue.unref)(materials),
2312
+ target: activeDevelopmentFramework.value
2313
+ }, {
2314
+ item: (0, vue.withCtx)(({ item }) => [(0, vue.createElementVNode)("button", {
2315
+ class: (0, vue.normalizeClass)(["widget layout-item-shell layout-item-shell--interactive", { selected: selected.value === item.id }]),
2316
+ type: "button",
2317
+ "aria-label": `${itemTitle(item)}\uFF0C${materialLabel(item.type)}`,
2318
+ onClick: ($event) => select(item.id)
2319
+ }, [(0, vue.createVNode)((0, vue.unref)(LMaterialRenderer), (0, vue.mergeProps)({
2320
+ item,
2321
+ materials: (0, vue.unref)(materials),
2322
+ target: activeDevelopmentFramework.value
2323
+ }, item.props), null, 16, [
2324
+ "item",
2325
+ "materials",
2326
+ "target"
2327
+ ])], 10, _hoisted_64)]),
2328
+ _: 1
2329
+ }, 8, [
2330
+ "document",
2331
+ "breakpoint",
2332
+ "materials",
2333
+ "target"
2334
+ ])], 512), [[vue.vShow, activeRoute.value === "vue"]]),
2335
+ (0, vue.withDirectives)((0, vue.createElementVNode)("section", {
2336
+ class: "runtime-preview-stage",
2337
+ "data-preview-framework": activeDevelopmentFramework.value,
2338
+ "aria-label": "运行时预览画布"
2339
+ }, [
2340
+ (0, vue.createElementVNode)("header", _hoisted_66, [_cache[33] || (_cache[33] = (0, vue.createElementVNode)("div", null, [(0, vue.createElementVNode)("span", { class: "stage-kicker" }, "预览模式"), (0, vue.createElementVNode)("h2", null, "运行时预览")], -1)), (0, vue.createElementVNode)("span", _hoisted_67, [_cache[32] || (_cache[32] = (0, vue.createElementVNode)("i", { "aria-hidden": "true" }, null, -1)), (0, vue.createTextVNode)((0, vue.toDisplayString)(activeDevelopmentFrameworkLabel.value) + " · 只读", 1)])]),
2341
+ activeDevelopmentFramework.value === "vue" ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(LGrid), {
2342
+ key: 0,
2343
+ document: layout.value,
2344
+ breakpoint: activeBreakpoint.value,
2345
+ materials: (0, vue.unref)(materials),
2346
+ target: activeDevelopmentFramework.value
2347
+ }, {
2348
+ item: (0, vue.withCtx)(({ item }) => [(0, vue.createVNode)((0, vue.unref)(LMaterialRenderer), (0, vue.mergeProps)({
2349
+ item,
2350
+ materials: (0, vue.unref)(materials),
2351
+ target: activeDevelopmentFramework.value,
2352
+ class: "preview-dialog__card"
2353
+ }, item.props), null, 16, [
2354
+ "item",
2355
+ "materials",
2356
+ "target"
2357
+ ])]),
2358
+ _: 1
2359
+ }, 8, [
2360
+ "document",
2361
+ "breakpoint",
2362
+ "materials",
2363
+ "target"
2364
+ ])) : (0, vue.createCommentVNode)("v-if", true),
2365
+ (0, vue.withDirectives)((0, vue.createElementVNode)("div", {
2366
+ ref_key: "runtimePreview",
2367
+ ref: runtimePreview,
2368
+ class: "runtime-preview-grid",
2369
+ "data-preview-renderer": "webcomponent"
2370
+ }, null, 512), [[vue.vShow, activeDevelopmentFramework.value === "webcomponent"]])
2371
+ ], 8, _hoisted_65), [[vue.vShow, activeRoute.value === "runtime" && previewMode.value]]),
2372
+ (0, vue.withDirectives)((0, vue.createElementVNode)("section", {
2373
+ class: "vanilla-runtime-stage",
2374
+ "data-preview-framework": activeDevelopmentFramework.value,
2375
+ "aria-label": "原生运行时画布"
2376
+ }, [
2377
+ (0, vue.createElementVNode)("header", _hoisted_69, [_cache[35] || (_cache[35] = (0, vue.createElementVNode)("div", null, [(0, vue.createElementVNode)("span", { class: "stage-kicker" }, "预览模式"), (0, vue.createElementVNode)("h2", null, "原生运行时")], -1)), (0, vue.createElementVNode)("span", _hoisted_70, [_cache[34] || (_cache[34] = (0, vue.createElementVNode)("i", { "aria-hidden": "true" }, null, -1)), (0, vue.createTextVNode)((0, vue.toDisplayString)(activeDevelopmentFrameworkLabel.value) + " · 只读", 1)])]),
2378
+ activeDevelopmentFramework.value === "vue" ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(LGrid), {
2379
+ key: 0,
2380
+ document: layout.value,
2381
+ breakpoint: activeBreakpoint.value,
2382
+ materials: (0, vue.unref)(materials),
2383
+ target: activeDevelopmentFramework.value
2384
+ }, {
2385
+ item: (0, vue.withCtx)(({ item }) => [(0, vue.createVNode)((0, vue.unref)(LMaterialRenderer), (0, vue.mergeProps)({
2386
+ item,
2387
+ materials: (0, vue.unref)(materials),
2388
+ target: activeDevelopmentFramework.value,
2389
+ class: "preview-dialog__card"
2390
+ }, item.props), null, 16, [
2391
+ "item",
2392
+ "materials",
2393
+ "target"
2394
+ ])]),
2395
+ _: 1
2396
+ }, 8, [
2397
+ "document",
2398
+ "breakpoint",
2399
+ "materials",
2400
+ "target"
2401
+ ])) : (0, vue.createCommentVNode)("v-if", true),
2402
+ (0, vue.withDirectives)((0, vue.createElementVNode)("div", {
2403
+ ref_key: "vanillaRuntime",
2404
+ ref: vanillaRuntime,
2405
+ class: "runtime-preview-grid",
2406
+ "data-preview-renderer": "webcomponent"
2407
+ }, null, 512), [[vue.vShow, activeDevelopmentFramework.value === "webcomponent"]])
2408
+ ], 8, _hoisted_68), [[vue.vShow, activeRoute.value === "vanilla"]])
2409
+ ], 14, _hoisted_59)], 34)]),
2410
+ (0, vue.createElementVNode)("aside", _hoisted_71, [
2411
+ (0, vue.createElementVNode)("div", _hoisted_72, [
2412
+ (0, vue.createElementVNode)("div", null, [_cache[36] || (_cache[36] = (0, vue.createElementVNode)("span", { class: "eyebrow" }, "实时输出", -1)), (0, vue.createElementVNode)("h2", null, (0, vue.toDisplayString)(activeCodeLabel.value), 1)]),
2413
+ (0, vue.createElementVNode)("button", {
2414
+ class: "copy-code-button",
2415
+ type: "button",
2416
+ "aria-label": copied.value ? "代码已复制" : "复制当前代码",
2417
+ title: copied.value ? "代码已复制" : "复制当前代码",
2418
+ onClick: copyActiveCode
2419
+ }, [((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)(copied.value ? (0, vue.unref)(lucide_vue_next.CopyCheck) : (0, vue.unref)(lucide_vue_next.Copy)), {
2420
+ size: 15,
2421
+ "stroke-width": 1.8,
2422
+ "aria-hidden": "true"
2423
+ }))], 8, _hoisted_73),
2424
+ (0, vue.createElementVNode)("button", {
2425
+ class: "download-code-button",
2426
+ type: "button",
2427
+ "aria-label": "下载当前代码",
2428
+ title: "下载当前代码",
2429
+ onClick: downloadActiveCode
2430
+ }, [(0, vue.createVNode)((0, vue.unref)(lucide_vue_next.Download), {
2431
+ size: 15,
2432
+ "stroke-width": 1.8,
2433
+ "aria-hidden": "true"
2434
+ })])
2435
+ ]),
2436
+ (0, vue.createElementVNode)("pre", _hoisted_74, [(0, vue.createElementVNode)("code", { innerHTML: highlightedCode.value }, null, 8, _hoisted_75)]),
2437
+ (0, vue.createElementVNode)("div", _hoisted_76, [_cache[37] || (_cache[37] = (0, vue.createElementVNode)("span", {
2438
+ class: "code-status__dot",
2439
+ "aria-hidden": "true"
2440
+ }, null, -1)), (0, vue.createElementVNode)("span", null, "已根据当前布局、" + (0, vue.toDisplayString)(breakpointLabel(activeBreakpoint.value)) + "断点实时生成 · 共 " + (0, vue.toDisplayString)(activeCodeLineCount.value) + " 行,可滚动查看完整输出", 1)])
2441
+ ])
2442
+ ]),
2443
+ (0, vue.createVNode)(vue.Transition, { name: "preview-dialog" }, {
2444
+ default: (0, vue.withCtx)(() => [previewDialogOpen.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
2445
+ key: 0,
2446
+ class: "preview-dialog",
2447
+ "data-preview-framework": activeDevelopmentFramework.value,
2448
+ "data-theme": theme.value,
2449
+ role: "dialog",
2450
+ "aria-modal": "true",
2451
+ "aria-labelledby": "layout-preview-title"
2452
+ }, [(0, vue.createElementVNode)("button", {
2453
+ class: "preview-dialog__backdrop",
2454
+ type: "button",
2455
+ "aria-label": "关闭实时预览",
2456
+ onClick: closePreviewDialog
2457
+ }), (0, vue.createElementVNode)("section", _hoisted_78, [
2458
+ (0, vue.createElementVNode)("header", _hoisted_79, [_cache[39] || (_cache[39] = (0, vue.createElementVNode)("div", null, [(0, vue.createElementVNode)("span", { class: "stage-kicker" }, "真实渲染"), (0, vue.createElementVNode)("h2", { id: "layout-preview-title" }, "实时预览")], -1)), (0, vue.createElementVNode)("div", _hoisted_80, [(0, vue.createElementVNode)("span", _hoisted_81, [_cache[38] || (_cache[38] = (0, vue.createElementVNode)("i", { "aria-hidden": "true" }, null, -1)), (0, vue.createTextVNode)((0, vue.toDisplayString)(breakpointLabel(activeBreakpoint.value)), 1)]), (0, vue.createElementVNode)("button", {
2459
+ class: "icon-button",
2460
+ type: "button",
2461
+ "aria-label": "关闭实时预览",
2462
+ title: "关闭实时预览",
2463
+ onClick: closePreviewDialog
2464
+ }, [(0, vue.createVNode)((0, vue.unref)(lucide_vue_next.X), {
2465
+ size: 16,
2466
+ "stroke-width": 1.9,
2467
+ "aria-hidden": "true"
2468
+ })])])]),
2469
+ (0, vue.createElementVNode)("div", _hoisted_82, [(0, vue.createElementVNode)("div", _hoisted_83, [activeDevelopmentFramework.value === "vue" ? ((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.unref)(LGrid), {
2470
+ key: 0,
2471
+ document: layout.value,
2472
+ breakpoint: activeBreakpoint.value,
2473
+ materials: (0, vue.unref)(materials),
2474
+ target: activeDevelopmentFramework.value
2475
+ }, {
2476
+ item: (0, vue.withCtx)(({ item }) => [(0, vue.createVNode)((0, vue.unref)(LMaterialRenderer), (0, vue.mergeProps)({
2477
+ item,
2478
+ materials: (0, vue.unref)(materials),
2479
+ target: activeDevelopmentFramework.value,
2480
+ class: "preview-dialog__card"
2481
+ }, item.props), null, 16, [
2482
+ "item",
2483
+ "materials",
2484
+ "target"
2485
+ ])]),
2486
+ _: 1
2487
+ }, 8, [
2488
+ "document",
2489
+ "breakpoint",
2490
+ "materials",
2491
+ "target"
2492
+ ])) : ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
2493
+ key: 1,
2494
+ ref_key: "previewDialogRuntime",
2495
+ ref: previewDialogRuntime,
2496
+ class: "runtime-preview-grid preview-dialog__runtime-grid",
2497
+ "data-preview-renderer": "webcomponent"
2498
+ }, null, 512))])]),
2499
+ (0, vue.createElementVNode)("footer", { class: "preview-dialog__footer" }, [_cache[40] || (_cache[40] = (0, vue.createElementVNode)("span", null, "此处展示当前布局的真实 CSS Grid 渲染结果", -1)), (0, vue.createElementVNode)("button", {
2500
+ class: "preview-dialog__close-button",
2501
+ type: "button",
2502
+ onClick: closePreviewDialog
2503
+ }, " 关闭预览 ")])
2504
+ ])], 8, _hoisted_77)) : (0, vue.createCommentVNode)("v-if", true)]),
2505
+ _: 1
2506
+ }),
2507
+ (0, vue.createVNode)(vue.Transition, { name: "preview-dialog" }, {
2508
+ default: (0, vue.withCtx)(() => [materialCodeDialog.value ? ((0, vue.openBlock)(), (0, vue.createElementBlock)("div", {
2509
+ key: 0,
2510
+ class: "preview-dialog material-code-dialog",
2511
+ "data-material-code-dialog": "true",
2512
+ "data-material-code-framework": activeDevelopmentFramework.value,
2513
+ "data-material-code-type": materialCodeDialog.value.type,
2514
+ "data-theme": theme.value,
2515
+ role: "dialog",
2516
+ "aria-modal": "true",
2517
+ "aria-labelledby": "material-code-title"
2518
+ }, [(0, vue.createElementVNode)("button", {
2519
+ class: "preview-dialog__backdrop",
2520
+ type: "button",
2521
+ "aria-label": "关闭物料代码",
2522
+ onClick: closeMaterialCode
2523
+ }), (0, vue.createElementVNode)("section", _hoisted_85, [
2524
+ (0, vue.createElementVNode)("header", _hoisted_86, [(0, vue.createElementVNode)("div", null, [
2525
+ _cache[41] || (_cache[41] = (0, vue.createElementVNode)("span", { class: "stage-kicker" }, "注册表物料", -1)),
2526
+ (0, vue.createElementVNode)("h2", _hoisted_87, (0, vue.toDisplayString)(materialCodeDialog.value.title ?? materialCodeDialog.value.label ?? materialCodeDialog.value.type), 1),
2527
+ (0, vue.createElementVNode)("p", _hoisted_88, (0, vue.toDisplayString)(activeDevelopmentFramework.value === "vue" ? "Vue 3 真实组件用法" : "原生 Web Component 标签用法"), 1)
2528
+ ]), (0, vue.createElementVNode)("div", _hoisted_89, [(0, vue.createElementVNode)("span", _hoisted_90, [_cache[42] || (_cache[42] = (0, vue.createElementVNode)("i", { "aria-hidden": "true" }, null, -1)), (0, vue.createTextVNode)((0, vue.toDisplayString)(activeDevelopmentFrameworkLabel.value), 1)]), (0, vue.createElementVNode)("button", {
2529
+ class: "icon-button",
2530
+ type: "button",
2531
+ "aria-label": "关闭物料代码",
2532
+ title: "关闭物料代码",
2533
+ onClick: closeMaterialCode
2534
+ }, [(0, vue.createVNode)((0, vue.unref)(lucide_vue_next.X), {
2535
+ size: 16,
2536
+ "stroke-width": 1.9,
2537
+ "aria-hidden": "true"
2538
+ })])])]),
2539
+ (0, vue.createElementVNode)("div", _hoisted_91, [(0, vue.createElementVNode)("pre", _hoisted_92, [(0, vue.createElementVNode)("code", { innerHTML: highlightedMaterialCode.value }, null, 8, _hoisted_93)])]),
2540
+ (0, vue.createElementVNode)("footer", _hoisted_94, [(0, vue.createElementVNode)("span", null, "来自当前注册表的 " + (0, vue.toDisplayString)(activeDevelopmentFramework.value === "vue" ? "组件导出" : "自定义元素标签") + ",共 " + (0, vue.toDisplayString)(materialCodeLineCount.value) + " 行", 1), (0, vue.createElementVNode)("div", _hoisted_95, [(0, vue.createElementVNode)("button", {
2541
+ class: "copy-code-button",
2542
+ type: "button",
2543
+ "aria-label": materialCodeCopied.value ? "代码已复制" : "复制物料代码",
2544
+ title: materialCodeCopied.value ? "代码已复制" : "复制物料代码",
2545
+ onClick: copyMaterialCode
2546
+ }, [((0, vue.openBlock)(), (0, vue.createBlock)((0, vue.resolveDynamicComponent)(materialCodeCopied.value ? (0, vue.unref)(lucide_vue_next.CopyCheck) : (0, vue.unref)(lucide_vue_next.Copy)), {
2547
+ size: 15,
2548
+ "stroke-width": 1.8,
2549
+ "aria-hidden": "true"
2550
+ }))], 8, _hoisted_96), (0, vue.createElementVNode)("button", {
2551
+ class: "preview-dialog__close-button",
2552
+ type: "button",
2553
+ onClick: closeMaterialCode
2554
+ }, " 关闭 ")])])
2555
+ ])], 8, _hoisted_84)) : (0, vue.createCommentVNode)("v-if", true)]),
2556
+ _: 1
2557
+ })
2558
+ ], 8, _hoisted_1);
2559
+ };
2560
+ }
2561
+ });
2562
+ //#endregion
2563
+ //#region src/components/LLayoutWorkspace.vue
2564
+ LLayoutWorkspace_vue_vue_type_script_setup_true_lang_default.__file = "src/components/LLayoutWorkspace.vue";
2565
+ var LLayoutWorkspace_default = LLayoutWorkspace_vue_vue_type_script_setup_true_lang_default;
2566
+ //#endregion
2567
+ //#region src/components/LMaterialDragSource.ts
2568
+ const LMaterialDragSource = (0, vue.defineComponent)({
2569
+ name: "LMaterialDragSource",
2570
+ props: {
2571
+ /** Backwards-compatible shorthand when no full material definition is supplied. */
2572
+ type: {
2573
+ type: String,
2574
+ default: void 0
2575
+ },
2576
+ material: {
2577
+ type: Object,
2578
+ default: void 0
2579
+ },
2580
+ props: {
2581
+ type: Object,
2582
+ default: () => ({})
2583
+ },
2584
+ placement: {
2585
+ type: Object,
2586
+ default: void 0
2587
+ },
2588
+ editor: {
2589
+ type: Object,
2590
+ required: true
2591
+ }
2592
+ },
2593
+ setup(props, { slots }) {
2594
+ let unregister = () => {};
2595
+ let element;
2596
+ const definition = props.material;
2597
+ const materialType = definition?.type ?? props.type;
2598
+ if (!materialType) throw new Error("LMaterialDragSource requires type or material.");
2599
+ const materialProps = {
2600
+ ...definition?.defaults ?? {},
2601
+ ...props.props
2602
+ };
2603
+ const input = () => ({
2604
+ type: materialType,
2605
+ props: materialProps,
2606
+ placement: {
2607
+ x: props.placement?.x ?? 0,
2608
+ y: props.placement?.y ?? 0,
2609
+ w: props.placement?.w ?? definition?.defaultLayout?.w ?? definition?.size?.default.w ?? 1,
2610
+ h: props.placement?.h ?? definition?.defaultLayout?.h ?? definition?.size?.default.h ?? 1
2611
+ }
2612
+ });
2613
+ const start = (event) => {
2614
+ props.editor.beginExternalDrag({
2615
+ ...input(),
2616
+ element: event.currentTarget,
2617
+ dataTransfer: event.dataTransfer ?? void 0
2618
+ });
2619
+ };
2620
+ const end = () => props.editor.endExternalDrag();
2621
+ (0, vue.onMounted)(() => {
2622
+ if (element) unregister = props.editor.registerExternalDragSource({
2623
+ ...input(),
2624
+ element
2625
+ });
2626
+ if (typeof document !== "undefined") document.addEventListener("dragend", end);
2627
+ });
2628
+ (0, vue.onBeforeUnmount)(() => {
2629
+ unregister();
2630
+ if (typeof document !== "undefined") document.removeEventListener("dragend", end);
2631
+ });
2632
+ return () => (0, vue.h)("div", {
2633
+ ref: (value) => {
2634
+ element = value;
2635
+ },
2636
+ class: "ldesign-layout-material-drag-source grid-stack-item",
2637
+ "data-material-type": materialType,
2638
+ "data-material-category": definition?.category,
2639
+ "data-material-title": definition?.title ?? definition?.label,
2640
+ "aria-label": `\u6DFB\u52A0${definition?.title ?? definition?.label ?? materialType}`,
2641
+ onDragstart: start
2642
+ }, slots.default?.() ?? definition?.title ?? definition?.label ?? materialType);
2643
+ }
2644
+ });
2645
+ //#endregion
2646
+ //#region src/composables/useLayoutEditor.ts
2647
+ function useLayoutEditor(options) {
2648
+ const editor = (0, vue.shallowRef)((0, _composy_layout_editor.createLayoutEditor)(options));
2649
+ (0, vue.onMounted)(() => editor.value.mount());
2650
+ (0, vue.onBeforeUnmount)(() => editor.value.destroy());
2651
+ return editor;
2652
+ }
2653
+ //#endregion
2654
+ exports.LGrid = LGrid;
2655
+ exports.LGridItem = LGridItem;
2656
+ exports.LLayoutEditor = LLayoutEditor;
2657
+ exports.LLayoutWorkspace = LLayoutWorkspace_default;
2658
+ exports.LMaterialDragSource = LMaterialDragSource;
2659
+ exports.LMaterialRenderer = LMaterialRenderer;
2660
+ exports.LMetricCard = LMetricCard;
2661
+ exports.LTrendCard = LTrendCard;
2662
+ exports.useLayoutEditor = useLayoutEditor;
2663
+ });
2664
+ /*! End of @composy/layout-vue | Powered by @composy/builder */
2665
+ //# sourceMappingURL=index.js.map