vue-layerx 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,521 @@
1
+ // src/runtime/create-use-layer.ts
2
+ import {
3
+ getCurrentInstance,
4
+ onUnmounted,
5
+ reactive,
6
+ shallowRef
7
+ } from "vue";
8
+
9
+ // src/vue/instance/instance-registry.ts
10
+ var registry = /* @__PURE__ */ new WeakMap();
11
+ function attachInternal(instance, internal) {
12
+ registry.set(instance, internal);
13
+ }
14
+ function getInternal(instance) {
15
+ const internal = registry.get(instance);
16
+ if (!internal) {
17
+ throw new Error("[vue-layerx] Invalid LayerInstance passed to LayerBind");
18
+ }
19
+ return internal;
20
+ }
21
+
22
+ // src/vue/instance/internal-state.ts
23
+ import { ref } from "vue";
24
+ function warnDuplicate(name, scope) {
25
+ if (typeof process !== "undefined" && process.env?.NODE_ENV === "production") return;
26
+ console.warn(
27
+ `[vue-layerx] Duplicate LayerTemplate name="${name}" in ${scope} scope; latter wins`
28
+ );
29
+ }
30
+ function createLayerInternalState() {
31
+ const slotsVersion = ref(0);
32
+ const layerTemplates = {};
33
+ const contentTemplates = {};
34
+ const bumpSlots = () => {
35
+ slotsVersion.value++;
36
+ };
37
+ const registerLayerTemplate = (name, entry) => {
38
+ if (layerTemplates[name]) warnDuplicate(name, "layer");
39
+ layerTemplates[name] = entry;
40
+ bumpSlots();
41
+ };
42
+ const registerContentTemplate = (name, entry) => {
43
+ if (contentTemplates[name]) warnDuplicate(name, "content");
44
+ contentTemplates[name] = entry;
45
+ bumpSlots();
46
+ };
47
+ return {
48
+ layerTemplates,
49
+ contentTemplates,
50
+ slotsVersion,
51
+ bumpSlots,
52
+ registerLayerTemplate,
53
+ registerContentTemplate
54
+ };
55
+ }
56
+
57
+ // src/runtime/layer-root.ts
58
+ import { defineComponent, provide } from "vue";
59
+
60
+ // src/core/config/merge-node-config.ts
61
+ function mergeProps(...sources) {
62
+ const result = {};
63
+ for (const source of sources) {
64
+ if (!source) continue;
65
+ Object.assign(result, source);
66
+ }
67
+ return result;
68
+ }
69
+ function mergeNodeConfig(...sources) {
70
+ const result = {};
71
+ for (const source of sources) {
72
+ if (!source) continue;
73
+ if (source.component !== void 0) result.component = source.component;
74
+ result.props = mergeProps(result.props, source.props);
75
+ if (source.slots) {
76
+ result.slots = { ...result.slots, ...source.slots };
77
+ }
78
+ }
79
+ return result;
80
+ }
81
+
82
+ // src/core/config/merge-config.ts
83
+ function pickContentConfig(payload) {
84
+ if (!payload) return void 0;
85
+ const result = {};
86
+ if (payload.component !== void 0) result.component = payload.component;
87
+ if (payload.props !== void 0) result.props = payload.props;
88
+ if (payload.slots !== void 0) result.slots = payload.slots;
89
+ if (Object.keys(result).length === 0) return void 0;
90
+ return result;
91
+ }
92
+ function pickLayerConfig(payload) {
93
+ return payload?.layer;
94
+ }
95
+ function defineLayerToConfig(defineLayer2) {
96
+ if (!defineLayer2) return void 0;
97
+ const result = {
98
+ layer: mergeNodeConfig(
99
+ defineLayer2.props ? { props: defineLayer2.props } : void 0,
100
+ defineLayer2.layer
101
+ )
102
+ };
103
+ if (defineLayer2.hideOn) result.hideOn = defineLayer2.hideOn;
104
+ return result;
105
+ }
106
+ function mergeConfig(ctx) {
107
+ const defineLayerConfig = defineLayerToConfig(ctx.defineLayer);
108
+ const content = mergeNodeConfig(
109
+ ctx.layerDefaults.content,
110
+ pickContentConfig(ctx.useOptions),
111
+ pickContentConfig(ctx.partial),
112
+ pickContentConfig(ctx.showOptions)
113
+ );
114
+ const layer = mergeNodeConfig(
115
+ ctx.layerDefaults.layer,
116
+ defineLayerConfig?.layer,
117
+ pickLayerConfig(ctx.useOptions),
118
+ pickLayerConfig(ctx.partial),
119
+ pickLayerConfig(ctx.showOptions)
120
+ );
121
+ const hideOn = ctx.showOptions.hideOn ?? ctx.partial?.hideOn ?? ctx.useOptions.hideOn ?? defineLayerConfig?.hideOn;
122
+ return { content, layer, hideOn };
123
+ }
124
+
125
+ // src/core/config/bind-hide-on.ts
126
+ function bindHideOn(contentProps, events, hide) {
127
+ if (!events?.length) return contentProps;
128
+ const listeners = {};
129
+ for (const event of events) {
130
+ const key = `on${event.charAt(0).toUpperCase()}${event.slice(1)}`;
131
+ const prev = contentProps[key];
132
+ listeners[key] = (...args) => {
133
+ prev?.(...args);
134
+ hide();
135
+ };
136
+ }
137
+ return { ...contentProps, ...listeners };
138
+ }
139
+
140
+ // src/core/config/default-resolve.ts
141
+ function materializeTemplates(templates) {
142
+ const slots = {};
143
+ for (const [name, entry] of Object.entries(templates)) {
144
+ slots[name] = (slotProps) => entry.render(slotProps ?? {});
145
+ }
146
+ return slots;
147
+ }
148
+ function resolveNodeSlots(templates, mergedSlots) {
149
+ return {
150
+ ...materializeTemplates(templates),
151
+ ...mergedSlots
152
+ };
153
+ }
154
+ function defaultResolve(ctx) {
155
+ const { merged, LayerComponent, boundContent, layerTemplates, contentTemplates, hide } = ctx;
156
+ const contentComponent = merged.content.component ?? boundContent;
157
+ const layerNormalized = {
158
+ component: merged.layer.component ?? LayerComponent,
159
+ props: merged.layer.props ?? {},
160
+ slots: resolveNodeSlots(layerTemplates, merged.layer.slots)
161
+ };
162
+ if (!contentComponent) {
163
+ return {
164
+ layer: layerNormalized,
165
+ content: {
166
+ component: LayerComponent,
167
+ props: {},
168
+ slots: {}
169
+ }
170
+ };
171
+ }
172
+ const contentProps = bindHideOn(merged.content.props ?? {}, merged.hideOn, hide);
173
+ return {
174
+ layer: layerNormalized,
175
+ content: {
176
+ component: contentComponent,
177
+ props: contentProps,
178
+ slots: resolveNodeSlots(contentTemplates, merged.content.slots)
179
+ }
180
+ };
181
+ }
182
+ function hasContentComponent(ctx) {
183
+ return !!(ctx.merged.content.component ?? ctx.boundContent);
184
+ }
185
+
186
+ // src/vue/render/render-layer-tree.ts
187
+ import { h } from "vue";
188
+
189
+ // src/core/constants/markers.ts
190
+ var LAYERX_DIRECT_CONTENT = "__layerxDirect";
191
+
192
+ // src/vue/render/build-visible-props.ts
193
+ function buildVisibleProps(layerProps, visible, visibleProp, visibleEvent, hide) {
194
+ return {
195
+ ...layerProps,
196
+ [visibleProp]: visible,
197
+ [visibleEvent]: (value) => {
198
+ if (value === false || value === void 0) hide();
199
+ }
200
+ };
201
+ }
202
+
203
+ // src/vue/render/render-layer-tree.ts
204
+ function renderLayerTree({
205
+ plan,
206
+ hasContent,
207
+ contentMountKey
208
+ }) {
209
+ const layerProps = buildVisibleProps(
210
+ plan.layer.props,
211
+ plan.visible,
212
+ plan.visibleProp,
213
+ plan.visibleEvent,
214
+ plan.onHide
215
+ );
216
+ const defaultSlot = hasContent ? () => h(
217
+ plan.content.component,
218
+ {
219
+ ...plan.content.props,
220
+ key: contentMountKey,
221
+ [LAYERX_DIRECT_CONTENT]: true
222
+ },
223
+ plan.content.slots
224
+ ) : () => null;
225
+ return h(plan.layer.component, layerProps, {
226
+ default: defaultSlot,
227
+ ...plan.layer.slots
228
+ });
229
+ }
230
+
231
+ // src/vue/di/injection-keys.ts
232
+ var LAYER_DEFINE_KEY = /* @__PURE__ */ Symbol("vue-layerx-define");
233
+ var LAYER_TEMPLATE_REGISTRY_KEY = /* @__PURE__ */ Symbol("vue-layerx-layer-template");
234
+ var LAYER_BIND_REGISTRY_KEY = /* @__PURE__ */ Symbol("vue-layerx-content-template");
235
+
236
+ // src/runtime/layer-root.ts
237
+ function buildLayerRoot(ctx, opts, internal, state, defineLayerConfig, hide) {
238
+ return defineComponent({
239
+ name: `LayerRoot_${opts.Content ? opts.Content.name ?? "Anonymous" : "Shell"}`,
240
+ setup() {
241
+ provide(LAYER_DEFINE_KEY, {
242
+ register(config) {
243
+ defineLayerConfig.value = config;
244
+ internal.bumpSlots();
245
+ }
246
+ });
247
+ provide(LAYER_TEMPLATE_REGISTRY_KEY, {
248
+ registerLayerTemplate: internal.registerLayerTemplate
249
+ });
250
+ return () => {
251
+ if (!state.visible) return null;
252
+ void internal.slotsVersion.value;
253
+ const merged = mergeConfig({
254
+ layerDefaults: ctx.layerDefaults,
255
+ defineLayer: defineLayerConfig.value,
256
+ useOptions: opts.useOptions,
257
+ showOptions: state.showOptions,
258
+ partial: opts.partial
259
+ });
260
+ const resolveCtx = {
261
+ merged,
262
+ LayerComponent: ctx.LayerComponent,
263
+ boundContent: opts.Content,
264
+ layerTemplates: internal.layerTemplates,
265
+ contentTemplates: internal.contentTemplates,
266
+ hide
267
+ };
268
+ const resolved = defaultResolve(resolveCtx);
269
+ const normalized = ctx.adapt ? ctx.adapt(resolved) : resolved;
270
+ const contentPresent = hasContentComponent(resolveCtx);
271
+ const plan = {
272
+ ...normalized,
273
+ visible: true,
274
+ visibleProp: ctx.visibleProp,
275
+ visibleEvent: ctx.visibleEvent,
276
+ onHide: hide
277
+ };
278
+ return renderLayerTree({
279
+ plan,
280
+ hasContent: contentPresent,
281
+ contentMountKey: contentPresent ? state.contentMountKey : void 0
282
+ });
283
+ };
284
+ }
285
+ });
286
+ }
287
+
288
+ // src/runtime/layer-runtime.ts
289
+ import { h as h2, render } from "vue";
290
+ function createLayerRuntime(root, appContext) {
291
+ let container = null;
292
+ return {
293
+ get mounted() {
294
+ return container !== null;
295
+ },
296
+ mount() {
297
+ if (!container) {
298
+ container = document.createElement("div");
299
+ document.body.appendChild(container);
300
+ }
301
+ const vnode = h2(root);
302
+ if (appContext) vnode.appContext = appContext;
303
+ render(vnode, container);
304
+ },
305
+ unmount() {
306
+ if (!container) return;
307
+ render(null, container);
308
+ container.remove();
309
+ container = null;
310
+ }
311
+ };
312
+ }
313
+
314
+ // src/runtime/create-use-layer.ts
315
+ function createInstanceLifecycle() {
316
+ const disposers = [];
317
+ return {
318
+ register(dispose) {
319
+ disposers.push(dispose);
320
+ },
321
+ dispose() {
322
+ for (const dispose of disposers.splice(0)) dispose();
323
+ }
324
+ };
325
+ }
326
+ function createInstance(ctx, opts) {
327
+ const internal = createLayerInternalState();
328
+ const state = reactive({
329
+ visible: false,
330
+ showOptions: {},
331
+ contentMountKey: 0
332
+ });
333
+ const defineLayerConfig = shallowRef(null);
334
+ const hide = () => {
335
+ state.visible = false;
336
+ };
337
+ const LayerRoot = buildLayerRoot(ctx, opts, internal, state, defineLayerConfig, hide);
338
+ const runtime = createLayerRuntime(LayerRoot, opts.appContext);
339
+ const dispose = () => {
340
+ state.visible = false;
341
+ runtime.unmount();
342
+ };
343
+ const show = (payload) => {
344
+ defineLayerConfig.value = null;
345
+ if (payload) state.showOptions = payload;
346
+ state.contentMountKey++;
347
+ state.visible = true;
348
+ if (!runtime.mounted) runtime.mount();
349
+ };
350
+ const instance = {
351
+ show,
352
+ hide,
353
+ clone(partial) {
354
+ const bundle = createInstance(ctx, {
355
+ Content: opts.Content,
356
+ useOptions: opts.useOptions,
357
+ partial: partial ?? {},
358
+ appContext: opts.appContext,
359
+ lifecycle: opts.lifecycle
360
+ });
361
+ opts.lifecycle.register(bundle.dispose);
362
+ return bundle.instance;
363
+ },
364
+ get visible() {
365
+ return state.visible;
366
+ }
367
+ };
368
+ attachInternal(instance, internal);
369
+ return { instance, dispose };
370
+ }
371
+ function createUseLayer(ctx) {
372
+ return function useLayer(Content, useOptions = {}) {
373
+ const hostInstance = getCurrentInstance();
374
+ const appContext = hostInstance?.appContext ?? null;
375
+ const lifecycle = createInstanceLifecycle();
376
+ const { instance, dispose } = createInstance(ctx, {
377
+ Content,
378
+ useOptions,
379
+ partial: {},
380
+ appContext,
381
+ lifecycle
382
+ });
383
+ lifecycle.register(dispose);
384
+ if (hostInstance) {
385
+ onUnmounted(() => lifecycle.dispose());
386
+ }
387
+ return instance;
388
+ };
389
+ }
390
+
391
+ // src/api/create-layer.ts
392
+ var DEFAULT_VISIBLE = ["modelValue", "onUpdate:modelValue"];
393
+ function createLayer(LayerComponent, defaults = {}, adapt) {
394
+ const [visibleProp, visibleEvent] = defaults.visible ?? DEFAULT_VISIBLE;
395
+ return createUseLayer({
396
+ LayerComponent,
397
+ layerDefaults: defaults,
398
+ visibleProp,
399
+ visibleEvent,
400
+ adapt
401
+ });
402
+ }
403
+
404
+ // src/api/define-layer.ts
405
+ import { getCurrentInstance as getCurrentInstance2, inject } from "vue";
406
+
407
+ // src/vue/context/layer-marker.ts
408
+ function hasDirectLayerMarker(instance) {
409
+ if (!instance) return false;
410
+ if (instance.props?.[LAYERX_DIRECT_CONTENT] === true) return true;
411
+ const attrs = instance.attrs;
412
+ if (attrs?.[LAYERX_DIRECT_CONTENT] === true) return true;
413
+ const vnodeProps = instance.vnode?.props;
414
+ if (vnodeProps?.[LAYERX_DIRECT_CONTENT] === true) return true;
415
+ return false;
416
+ }
417
+
418
+ // src/api/define-layer.ts
419
+ function defineLayer(options = {}) {
420
+ const registry2 = inject(LAYER_DEFINE_KEY, null);
421
+ const instance = getCurrentInstance2();
422
+ if (!registry2 || !hasDirectLayerMarker(instance)) return;
423
+ registry2.register(options);
424
+ }
425
+
426
+ // src/components/layer-template.ts
427
+ import { computed, defineComponent as defineComponent2, getCurrentInstance as getCurrentInstance3, inject as inject2, onMounted } from "vue";
428
+
429
+ // src/vue/context/owning-content.ts
430
+ function getOwningContentInstance(instance) {
431
+ let cur = instance?.parent ?? null;
432
+ while (cur) {
433
+ const type = cur.type;
434
+ if (typeof type === "object" || typeof type === "function") {
435
+ return cur;
436
+ }
437
+ cur = cur.parent;
438
+ }
439
+ return null;
440
+ }
441
+
442
+ // src/vue/context/in-layer-content.ts
443
+ function isInDirectLayerContent(instance) {
444
+ return hasDirectLayerMarker(getOwningContentInstance(instance));
445
+ }
446
+
447
+ // src/components/layer-template.ts
448
+ function buildTemplateScope(slotProps, layer) {
449
+ return {
450
+ slotProps,
451
+ inLayer: layer.inLayer,
452
+ outsideLayer: layer.outsideLayer
453
+ };
454
+ }
455
+ var LayerTemplate = defineComponent2({
456
+ name: "LayerTemplate",
457
+ props: {
458
+ name: {
459
+ type: String,
460
+ required: true
461
+ },
462
+ visibleOutside: {
463
+ type: Boolean,
464
+ default: false
465
+ }
466
+ },
467
+ setup(props, { slots }) {
468
+ const layerRegistry = inject2(LAYER_TEMPLATE_REGISTRY_KEY, null);
469
+ const bindRegistry = inject2(LAYER_BIND_REGISTRY_KEY, null);
470
+ const instance = getCurrentInstance3();
471
+ const inLayer = computed(
472
+ () => layerRegistry !== null && isInDirectLayerContent(instance)
473
+ );
474
+ const inBind = computed(() => bindRegistry !== null);
475
+ const renderSlot = (templateScope) => slots.default?.(templateScope) ?? null;
476
+ onMounted(() => {
477
+ const renderWithScope = (layer) => (slotProps = {}) => renderSlot(buildTemplateScope(slotProps, layer));
478
+ if (inLayer.value && layerRegistry) {
479
+ layerRegistry.registerLayerTemplate(props.name, {
480
+ render: renderWithScope({ inLayer: true, outsideLayer: false })
481
+ });
482
+ return;
483
+ }
484
+ if (inBind.value && bindRegistry) {
485
+ bindRegistry.registerContentTemplate(props.name, {
486
+ render: renderWithScope({ inLayer: true, outsideLayer: false })
487
+ });
488
+ }
489
+ });
490
+ return () => {
491
+ if (inLayer.value || inBind.value) return null;
492
+ if (!props.visibleOutside) return null;
493
+ return renderSlot(buildTemplateScope({}, { inLayer: false, outsideLayer: true }));
494
+ };
495
+ }
496
+ });
497
+
498
+ // src/components/layer-bind.ts
499
+ import { defineComponent as defineComponent3, provide as provide2 } from "vue";
500
+ var LayerBind = defineComponent3({
501
+ name: "LayerBind",
502
+ props: {
503
+ to: {
504
+ type: Object,
505
+ required: true
506
+ }
507
+ },
508
+ setup(props, { slots }) {
509
+ const internal = getInternal(props.to);
510
+ provide2(LAYER_BIND_REGISTRY_KEY, {
511
+ registerContentTemplate: internal.registerContentTemplate
512
+ });
513
+ return () => slots.default?.();
514
+ }
515
+ });
516
+ export {
517
+ LayerBind,
518
+ LayerTemplate,
519
+ createLayer,
520
+ defineLayer
521
+ };
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "vue-layerx",
3
+ "version": "0.0.1",
4
+ "description": "Vue 3 hook factory for declarative + imperative layered UI (dialogs, drawers, etc.)",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsup",
21
+ "dev": "tsup --watch",
22
+ "test": "vitest run",
23
+ "test:watch": "vitest",
24
+ "test:coverage": "vitest run --coverage",
25
+ "playground": "pnpm build && pnpm --filter playground dev",
26
+ "docs:dev": "vitepress dev docs",
27
+ "docs:build": "vitepress build docs",
28
+ "docs:preview": "vitepress preview docs"
29
+ },
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/xuyimingwork/vue-layerx.git"
33
+ },
34
+ "homepage": "https://github.com/xuyimingwork/vue-layerx#readme",
35
+ "bugs": {
36
+ "url": "https://github.com/xuyimingwork/vue-layerx/issues"
37
+ },
38
+ "keywords": [
39
+ "vue",
40
+ "vue3",
41
+ "dialog",
42
+ "modal",
43
+ "layer",
44
+ "composable"
45
+ ],
46
+ "license": "MIT",
47
+ "peerDependencies": {
48
+ "vue": "^3.5.0"
49
+ },
50
+ "devDependencies": {
51
+ "@vitest/coverage-v8": "^3.2.4",
52
+ "@vue/test-utils": "^2.4.6",
53
+ "element-plus": "^2.11.4",
54
+ "jsdom": "^26.1.0",
55
+ "tsup": "^8.5.0",
56
+ "typescript": "^5.8.3",
57
+ "vitepress": "^1.6.3",
58
+ "vitest": "^3.2.4",
59
+ "vue": "^3.5.16"
60
+ }
61
+ }