@wippy-fe/vue-host 0.0.34 → 0.0.36
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.d.ts +80 -0
- package/dist/index.js +93 -0
- package/dist/index.js.map +1 -0
- package/package.json +19 -6
- package/src/index.ts +0 -7
- package/src/useWippyLayout.test.ts +0 -399
- package/src/useWippyLayout.ts +0 -181
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { LayoutSnapshot, SizeValue, PanelTarget } from '@wippy-fe/proxy';
|
|
2
|
+
import { Ref, ComputedRef, MaybeRefOrGetter } from 'vue';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Vue 3 reactive bindings for `host.layout.*`. Use from any component
|
|
6
|
+
* inside a Wippy iframe (managed-layout or compat). In compat mode
|
|
7
|
+
* `snapshot.value` stays `null` and mutations are silent no-ops.
|
|
8
|
+
*
|
|
9
|
+
* Boot-time seed comes from `host.layout.snapshot` (sync cache,
|
|
10
|
+
* populated from `AppContext.layout` in the child's `__WIPPY_INITIAL_CONFIG__`).
|
|
11
|
+
* Subsequent updates arrive via the `@layout-change` proxy topic —
|
|
12
|
+
* `events.ts` is the single writer that updates the cache and then
|
|
13
|
+
* fires the event, so reads here are always fresh.
|
|
14
|
+
*
|
|
15
|
+
* ```vue
|
|
16
|
+
* <script setup lang="ts">
|
|
17
|
+
* import { useWippyLayout } from '@wippy-fe/vue-host'
|
|
18
|
+
*
|
|
19
|
+
* const layout = useWippyLayout()
|
|
20
|
+
* // layout.snapshot — Ref<LayoutSnapshot | null>
|
|
21
|
+
* // layout.activeBreakpoint — ComputedRef<string>
|
|
22
|
+
* // layout.panels — ComputedRef<Record<string, unknown>>
|
|
23
|
+
* // layout.isManaged — ComputedRef<boolean>
|
|
24
|
+
*
|
|
25
|
+
* layout.resizePanel('files', '400px')
|
|
26
|
+
* layout.collapsePanel('sidebar')
|
|
27
|
+
* </script>
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
interface UseWippyLayout {
|
|
31
|
+
/** Live layout snapshot — null outside a managed-layout host. */
|
|
32
|
+
snapshot: Ref<LayoutSnapshot | null>;
|
|
33
|
+
/** Currently-active breakpoint name. Empty string when no snapshot. */
|
|
34
|
+
activeBreakpoint: ComputedRef<string>;
|
|
35
|
+
/** Live per-panel declaration map. Includes `route`, `props`, etc. */
|
|
36
|
+
panels: ComputedRef<Record<string, unknown>>;
|
|
37
|
+
/** True when a managed-layout host is driving this iframe. */
|
|
38
|
+
isManaged: ComputedRef<boolean>;
|
|
39
|
+
resizePanel: (panelId: string, size: SizeValue) => void;
|
|
40
|
+
collapsePanel: (panelId: string) => void;
|
|
41
|
+
expandPanel: (panelId: string) => void;
|
|
42
|
+
movePanel: (panelId: string, target: PanelTarget) => void;
|
|
43
|
+
removePanel: (panelId: string) => void;
|
|
44
|
+
closeModal: (modalId: string) => void;
|
|
45
|
+
removeFloating: (floatingId: string) => void;
|
|
46
|
+
}
|
|
47
|
+
declare function useWippyLayout(): UseWippyLayout;
|
|
48
|
+
/**
|
|
49
|
+
* Reactive ref to a single panel's live def. Returns `null` when the
|
|
50
|
+
* panel doesn't exist in the current snapshot (removed or not declared).
|
|
51
|
+
*
|
|
52
|
+
* `panelId` accepts a static string, a ref, or a getter — so a consumer
|
|
53
|
+
* passing `() => props.id` or a `Ref<string>` will see the panel def
|
|
54
|
+
* change reactively when the id input changes.
|
|
55
|
+
*
|
|
56
|
+
* Common use: read `panel.value?.route` inside an editor component to
|
|
57
|
+
* drive its internal router from the authoritative layout state.
|
|
58
|
+
*/
|
|
59
|
+
declare function useWippyPanel(panelId: MaybeRefOrGetter<string>): ComputedRef<Record<string, unknown> | null>;
|
|
60
|
+
/**
|
|
61
|
+
* Reactive active-breakpoint ref. Updates off both `@layout-change`
|
|
62
|
+
* and `@layout-breakpoint`; the latter fires immediately on each
|
|
63
|
+
* transition (no 50ms debounce), so UI that reacts to breakpoint
|
|
64
|
+
* transitions is responsive.
|
|
65
|
+
*/
|
|
66
|
+
declare function useWippyBreakpoint(): Ref<string>;
|
|
67
|
+
/**
|
|
68
|
+
* Reactive ref to the main panel's current route. Derived by walking
|
|
69
|
+
* the snapshot's active-breakpoint tree to find the slot with
|
|
70
|
+
* `main: true` and reading `panels[id].route`. Returns empty string
|
|
71
|
+
* when there's no main panel or no route set.
|
|
72
|
+
*
|
|
73
|
+
* Note: the active breakpoint may not have a main panel (e.g. an `sm`
|
|
74
|
+
* layout that drops the main entirely for a mobile-only view). In that
|
|
75
|
+
* case this ref returns ''. Bind to `useWippyPanel(<id>).value?.route`
|
|
76
|
+
* for a specific panel's route across breakpoints.
|
|
77
|
+
*/
|
|
78
|
+
declare function useWippyMainRoute(): ComputedRef<string>;
|
|
79
|
+
|
|
80
|
+
export { type UseWippyLayout, useWippyBreakpoint, useWippyLayout, useWippyMainRoute, useWippyPanel };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// src/useWippyLayout.ts
|
|
2
|
+
import { host, on } from "@wippy-fe/proxy";
|
|
3
|
+
import { computed, shallowRef, toValue } from "vue";
|
|
4
|
+
var sharedSnapshot = null;
|
|
5
|
+
var sharedBreakpoint = null;
|
|
6
|
+
function getSharedSnapshot() {
|
|
7
|
+
if (sharedSnapshot)
|
|
8
|
+
return sharedSnapshot;
|
|
9
|
+
const ref = shallowRef(host.layout.snapshot);
|
|
10
|
+
on("@layout-change", (next) => {
|
|
11
|
+
ref.value = next;
|
|
12
|
+
});
|
|
13
|
+
sharedSnapshot = ref;
|
|
14
|
+
return ref;
|
|
15
|
+
}
|
|
16
|
+
function getSharedBreakpoint() {
|
|
17
|
+
if (sharedBreakpoint)
|
|
18
|
+
return sharedBreakpoint;
|
|
19
|
+
const ref = shallowRef(host.layout.snapshot?.activeBreakpoint ?? "");
|
|
20
|
+
on("@layout-change", (snap) => {
|
|
21
|
+
if (snap?.activeBreakpoint)
|
|
22
|
+
ref.value = snap.activeBreakpoint;
|
|
23
|
+
});
|
|
24
|
+
on("@layout-breakpoint", ({ name }) => {
|
|
25
|
+
ref.value = name;
|
|
26
|
+
});
|
|
27
|
+
sharedBreakpoint = ref;
|
|
28
|
+
return ref;
|
|
29
|
+
}
|
|
30
|
+
function useWippyLayout() {
|
|
31
|
+
const snapshot = getSharedSnapshot();
|
|
32
|
+
return {
|
|
33
|
+
snapshot,
|
|
34
|
+
activeBreakpoint: computed(() => snapshot.value?.activeBreakpoint ?? ""),
|
|
35
|
+
panels: computed(() => snapshot.value?.panels ?? {}),
|
|
36
|
+
isManaged: computed(() => snapshot.value !== null),
|
|
37
|
+
resizePanel: (id, size) => host.layout.resizePanel(id, size),
|
|
38
|
+
collapsePanel: (id) => host.layout.collapsePanel(id),
|
|
39
|
+
expandPanel: (id) => host.layout.expandPanel(id),
|
|
40
|
+
movePanel: (id, t) => host.layout.movePanel(id, t),
|
|
41
|
+
removePanel: (id) => host.layout.removePanel(id),
|
|
42
|
+
closeModal: (id) => host.layout.closeModal(id),
|
|
43
|
+
removeFloating: (id) => host.layout.removeFloating(id)
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function useWippyPanel(panelId) {
|
|
47
|
+
const snapshot = getSharedSnapshot();
|
|
48
|
+
return computed(() => {
|
|
49
|
+
const id = toValue(panelId);
|
|
50
|
+
const panels = snapshot.value?.panels;
|
|
51
|
+
return panels?.[id] ?? null;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
function useWippyBreakpoint() {
|
|
55
|
+
return getSharedBreakpoint();
|
|
56
|
+
}
|
|
57
|
+
function useWippyMainRoute() {
|
|
58
|
+
const snapshot = getSharedSnapshot();
|
|
59
|
+
return computed(() => {
|
|
60
|
+
const snap = snapshot.value;
|
|
61
|
+
if (!snap)
|
|
62
|
+
return "";
|
|
63
|
+
const layouts = snap.layouts;
|
|
64
|
+
const tree = layouts[snap.activeBreakpoint ?? "default"] ?? layouts.default;
|
|
65
|
+
const mainId = findMainPanel(tree);
|
|
66
|
+
if (!mainId)
|
|
67
|
+
return "";
|
|
68
|
+
const panelDef = snap.panels[mainId];
|
|
69
|
+
return panelDef?.route ?? "";
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function findMainPanel(node) {
|
|
73
|
+
if (!node || typeof node !== "object")
|
|
74
|
+
return null;
|
|
75
|
+
const n = node;
|
|
76
|
+
if (typeof n.panel === "string" && n.main === true)
|
|
77
|
+
return n.panel;
|
|
78
|
+
if (Array.isArray(n.children)) {
|
|
79
|
+
for (const child of n.children) {
|
|
80
|
+
const found = findMainPanel(child);
|
|
81
|
+
if (found !== null)
|
|
82
|
+
return found;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
export {
|
|
88
|
+
useWippyBreakpoint,
|
|
89
|
+
useWippyLayout,
|
|
90
|
+
useWippyMainRoute,
|
|
91
|
+
useWippyPanel
|
|
92
|
+
};
|
|
93
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/useWippyLayout.ts"],"sourcesContent":["import type { LayoutSnapshot, PanelTarget, SizeValue } from '@wippy-fe/proxy'\nimport type { ComputedRef, MaybeRefOrGetter, Ref, ShallowRef } from 'vue'\nimport { host, on } from '@wippy-fe/proxy'\nimport { computed, shallowRef, toValue } from 'vue'\n\n/**\n * Vue 3 reactive bindings for `host.layout.*`. Use from any component\n * inside a Wippy iframe (managed-layout or compat). In compat mode\n * `snapshot.value` stays `null` and mutations are silent no-ops.\n *\n * Boot-time seed comes from `host.layout.snapshot` (sync cache,\n * populated from `AppContext.layout` in the child's `__WIPPY_INITIAL_CONFIG__`).\n * Subsequent updates arrive via the `@layout-change` proxy topic —\n * `events.ts` is the single writer that updates the cache and then\n * fires the event, so reads here are always fresh.\n *\n * ```vue\n * <script setup lang=\"ts\">\n * import { useWippyLayout } from '@wippy-fe/vue-host'\n *\n * const layout = useWippyLayout()\n * // layout.snapshot — Ref<LayoutSnapshot | null>\n * // layout.activeBreakpoint — ComputedRef<string>\n * // layout.panels — ComputedRef<Record<string, unknown>>\n * // layout.isManaged — ComputedRef<boolean>\n *\n * layout.resizePanel('files', '400px')\n * layout.collapsePanel('sidebar')\n * </script>\n * ```\n */\nexport interface UseWippyLayout {\n /** Live layout snapshot — null outside a managed-layout host. */\n snapshot: Ref<LayoutSnapshot | null>\n /** Currently-active breakpoint name. Empty string when no snapshot. */\n activeBreakpoint: ComputedRef<string>\n /** Live per-panel declaration map. Includes `route`, `props`, etc. */\n panels: ComputedRef<Record<string, unknown>>\n /** True when a managed-layout host is driving this iframe. */\n isManaged: ComputedRef<boolean>\n\n resizePanel: (panelId: string, size: SizeValue) => void\n collapsePanel: (panelId: string) => void\n expandPanel: (panelId: string) => void\n movePanel: (panelId: string, target: PanelTarget) => void\n removePanel: (panelId: string) => void\n closeModal: (modalId: string) => void\n removeFloating: (floatingId: string) => void\n}\n\n/**\n * Module-scoped shared snapshot ref + subscription. Every hook in this\n * package reads from the same `ShallowRef`, so a component using\n * `useWippyLayout()` + `useWippyPanel('a')` + `useWippyBreakpoint()`\n * pays for ONE `@layout-change` subscription, not three.\n *\n * The subscription is installed lazily on first call and lives for the\n * iframe's lifetime — no per-component cleanup needed (one closure on\n * a process-wide bus is cheap; in compat-mode hosts this never fires).\n */\nlet sharedSnapshot: ShallowRef<LayoutSnapshot | null> | null = null\nlet sharedBreakpoint: ShallowRef<string> | null = null\n\nfunction getSharedSnapshot(): ShallowRef<LayoutSnapshot | null> {\n if (sharedSnapshot)\n return sharedSnapshot\n const ref = shallowRef<LayoutSnapshot | null>(host.layout.snapshot)\n on('@layout-change', (next) => {\n ref.value = next\n })\n sharedSnapshot = ref\n return ref\n}\n\nfunction getSharedBreakpoint(): ShallowRef<string> {\n if (sharedBreakpoint)\n return sharedBreakpoint\n const ref = shallowRef<string>(host.layout.snapshot?.activeBreakpoint ?? '')\n on('@layout-change', (snap) => {\n if (snap?.activeBreakpoint)\n ref.value = snap.activeBreakpoint\n })\n on('@layout-breakpoint', ({ name }) => {\n ref.value = name\n })\n sharedBreakpoint = ref\n return ref\n}\n\nexport function useWippyLayout(): UseWippyLayout {\n const snapshot = getSharedSnapshot()\n\n return {\n snapshot,\n activeBreakpoint: computed(() => snapshot.value?.activeBreakpoint ?? ''),\n panels: computed(() => (snapshot.value?.panels ?? {}) as Record<string, unknown>),\n isManaged: computed(() => snapshot.value !== null),\n resizePanel: (id, size) => host.layout.resizePanel(id, size),\n collapsePanel: id => host.layout.collapsePanel(id),\n expandPanel: id => host.layout.expandPanel(id),\n movePanel: (id, t) => host.layout.movePanel(id, t),\n removePanel: id => host.layout.removePanel(id),\n closeModal: id => host.layout.closeModal(id),\n removeFloating: id => host.layout.removeFloating(id),\n }\n}\n\n/**\n * Reactive ref to a single panel's live def. Returns `null` when the\n * panel doesn't exist in the current snapshot (removed or not declared).\n *\n * `panelId` accepts a static string, a ref, or a getter — so a consumer\n * passing `() => props.id` or a `Ref<string>` will see the panel def\n * change reactively when the id input changes.\n *\n * Common use: read `panel.value?.route` inside an editor component to\n * drive its internal router from the authoritative layout state.\n */\nexport function useWippyPanel(\n panelId: MaybeRefOrGetter<string>,\n): ComputedRef<Record<string, unknown> | null> {\n const snapshot = getSharedSnapshot()\n return computed(() => {\n const id = toValue(panelId)\n const panels = snapshot.value?.panels as Record<string, unknown> | undefined\n return (panels?.[id] as Record<string, unknown> | undefined) ?? null\n })\n}\n\n/**\n * Reactive active-breakpoint ref. Updates off both `@layout-change`\n * and `@layout-breakpoint`; the latter fires immediately on each\n * transition (no 50ms debounce), so UI that reacts to breakpoint\n * transitions is responsive.\n */\nexport function useWippyBreakpoint(): Ref<string> {\n return getSharedBreakpoint()\n}\n\n/**\n * Reactive ref to the main panel's current route. Derived by walking\n * the snapshot's active-breakpoint tree to find the slot with\n * `main: true` and reading `panels[id].route`. Returns empty string\n * when there's no main panel or no route set.\n *\n * Note: the active breakpoint may not have a main panel (e.g. an `sm`\n * layout that drops the main entirely for a mobile-only view). In that\n * case this ref returns ''. Bind to `useWippyPanel(<id>).value?.route`\n * for a specific panel's route across breakpoints.\n */\nexport function useWippyMainRoute(): ComputedRef<string> {\n const snapshot = getSharedSnapshot()\n return computed(() => {\n const snap = snapshot.value\n if (!snap)\n return ''\n const layouts = snap.layouts as Record<string, unknown>\n const tree = layouts[snap.activeBreakpoint ?? 'default'] ?? layouts.default\n const mainId = findMainPanel(tree)\n if (!mainId)\n return ''\n const panelDef = (snap.panels as Record<string, { route?: string }>)[mainId]\n return panelDef?.route ?? ''\n })\n}\n\nfunction findMainPanel(node: unknown): string | null {\n if (!node || typeof node !== 'object')\n return null\n const n = node as { panel?: string, main?: boolean, children?: unknown[] }\n if (typeof n.panel === 'string' && n.main === true)\n return n.panel\n if (Array.isArray(n.children)) {\n for (const child of n.children) {\n const found = findMainPanel(child)\n if (found !== null)\n return found\n }\n }\n return null\n}\n"],"mappings":";AAEA,SAAS,MAAM,UAAU;AACzB,SAAS,UAAU,YAAY,eAAe;AAyD9C,IAAI,iBAA2D;AAC/D,IAAI,mBAA8C;AAElD,SAAS,oBAAuD;AAC9D,MAAI;AACF,WAAO;AACT,QAAM,MAAM,WAAkC,KAAK,OAAO,QAAQ;AAClE,KAAG,kBAAkB,CAAC,SAAS;AAC7B,QAAI,QAAQ;AAAA,EACd,CAAC;AACD,mBAAiB;AACjB,SAAO;AACT;AAEA,SAAS,sBAA0C;AACjD,MAAI;AACF,WAAO;AACT,QAAM,MAAM,WAAmB,KAAK,OAAO,UAAU,oBAAoB,EAAE;AAC3E,KAAG,kBAAkB,CAAC,SAAS;AAC7B,QAAI,MAAM;AACR,UAAI,QAAQ,KAAK;AAAA,EACrB,CAAC;AACD,KAAG,sBAAsB,CAAC,EAAE,KAAK,MAAM;AACrC,QAAI,QAAQ;AAAA,EACd,CAAC;AACD,qBAAmB;AACnB,SAAO;AACT;AAEO,SAAS,iBAAiC;AAC/C,QAAM,WAAW,kBAAkB;AAEnC,SAAO;AAAA,IACL;AAAA,IACA,kBAAkB,SAAS,MAAM,SAAS,OAAO,oBAAoB,EAAE;AAAA,IACvE,QAAQ,SAAS,MAAO,SAAS,OAAO,UAAU,CAAC,CAA6B;AAAA,IAChF,WAAW,SAAS,MAAM,SAAS,UAAU,IAAI;AAAA,IACjD,aAAa,CAAC,IAAI,SAAS,KAAK,OAAO,YAAY,IAAI,IAAI;AAAA,IAC3D,eAAe,QAAM,KAAK,OAAO,cAAc,EAAE;AAAA,IACjD,aAAa,QAAM,KAAK,OAAO,YAAY,EAAE;AAAA,IAC7C,WAAW,CAAC,IAAI,MAAM,KAAK,OAAO,UAAU,IAAI,CAAC;AAAA,IACjD,aAAa,QAAM,KAAK,OAAO,YAAY,EAAE;AAAA,IAC7C,YAAY,QAAM,KAAK,OAAO,WAAW,EAAE;AAAA,IAC3C,gBAAgB,QAAM,KAAK,OAAO,eAAe,EAAE;AAAA,EACrD;AACF;AAaO,SAAS,cACd,SAC6C;AAC7C,QAAM,WAAW,kBAAkB;AACnC,SAAO,SAAS,MAAM;AACpB,UAAM,KAAK,QAAQ,OAAO;AAC1B,UAAM,SAAS,SAAS,OAAO;AAC/B,WAAQ,SAAS,EAAE,KAA6C;AAAA,EAClE,CAAC;AACH;AAQO,SAAS,qBAAkC;AAChD,SAAO,oBAAoB;AAC7B;AAaO,SAAS,oBAAyC;AACvD,QAAM,WAAW,kBAAkB;AACnC,SAAO,SAAS,MAAM;AACpB,UAAM,OAAO,SAAS;AACtB,QAAI,CAAC;AACH,aAAO;AACT,UAAM,UAAU,KAAK;AACrB,UAAM,OAAO,QAAQ,KAAK,oBAAoB,SAAS,KAAK,QAAQ;AACpE,UAAM,SAAS,cAAc,IAAI;AACjC,QAAI,CAAC;AACH,aAAO;AACT,UAAM,WAAY,KAAK,OAA8C,MAAM;AAC3E,WAAO,UAAU,SAAS;AAAA,EAC5B,CAAC;AACH;AAEA,SAAS,cAAc,MAA8B;AACnD,MAAI,CAAC,QAAQ,OAAO,SAAS;AAC3B,WAAO;AACT,QAAM,IAAI;AACV,MAAI,OAAO,EAAE,UAAU,YAAY,EAAE,SAAS;AAC5C,WAAO,EAAE;AACX,MAAI,MAAM,QAAQ,EAAE,QAAQ,GAAG;AAC7B,eAAW,SAAS,EAAE,UAAU;AAC9B,YAAM,QAAQ,cAAc,KAAK;AACjC,UAAI,UAAU;AACZ,eAAO;AAAA,IACX;AAAA,EACF;AACA,SAAO;AACT;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,18 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wippy-fe/vue-host",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "Vue 3 reactive bindings for the Wippy host.* proxy API. Hooks for layout state, panel live state, breakpoints, and per-panel routes
|
|
3
|
+
"version": "0.0.36",
|
|
4
|
+
"description": "Vue 3 reactive bindings for the Wippy host.* proxy API. Hooks for layout state, panel live state, breakpoints, and per-panel routes - inside a Wippy iframe.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"
|
|
8
|
-
"
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"module": "dist/index.js",
|
|
10
|
+
"types": "dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./package.json": "./package.json"
|
|
17
|
+
},
|
|
9
18
|
"files": [
|
|
10
|
-
"src/",
|
|
11
19
|
"package.json",
|
|
20
|
+
"dist/",
|
|
12
21
|
"README.md"
|
|
13
22
|
],
|
|
14
23
|
"peerDependencies": {
|
|
15
24
|
"vue": "^3.5.0",
|
|
16
|
-
"@wippy-fe/proxy": "0.0.
|
|
25
|
+
"@wippy-fe/proxy": "^0.0.36"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsup",
|
|
29
|
+
"clean": "rimraf dist"
|
|
17
30
|
}
|
|
18
31
|
}
|
package/src/index.ts
DELETED
|
@@ -1,399 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @vitest-environment jsdom
|
|
3
|
-
*/
|
|
4
|
-
import type { LayoutSnapshot } from '@wippy-fe/proxy'
|
|
5
|
-
import { mount } from '@vue/test-utils'
|
|
6
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
7
|
-
import { defineComponent, h, isRef, ref } from 'vue'
|
|
8
|
-
|
|
9
|
-
// Mock @wippy-fe/proxy with a controllable host + on(). The shared
|
|
10
|
-
// state in useWippyLayout reads `host.layout.snapshot` ONCE at first
|
|
11
|
-
// hook call (per iframe lifetime — and per Vitest module), so we
|
|
12
|
-
// pre-seed the snapshot, then drive updates by firing registered
|
|
13
|
-
// `@layout-change` listeners.
|
|
14
|
-
|
|
15
|
-
const mockState = {
|
|
16
|
-
snapshot: null as LayoutSnapshot | null,
|
|
17
|
-
listeners: new Map<string, Set<(payload: any) => void>>(),
|
|
18
|
-
hostLayoutMethods: {
|
|
19
|
-
resizePanel: vi.fn(),
|
|
20
|
-
collapsePanel: vi.fn(),
|
|
21
|
-
expandPanel: vi.fn(),
|
|
22
|
-
movePanel: vi.fn(),
|
|
23
|
-
removePanel: vi.fn(),
|
|
24
|
-
closeModal: vi.fn(),
|
|
25
|
-
removeFloating: vi.fn(),
|
|
26
|
-
},
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function fire(topic: string, payload: any) {
|
|
30
|
-
const set = mockState.listeners.get(topic)
|
|
31
|
-
if (set) {
|
|
32
|
-
for (const cb of set) cb(payload)
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function listenerCount(topic: string): number {
|
|
37
|
-
return mockState.listeners.get(topic)?.size ?? 0
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
vi.mock('@wippy-fe/proxy', () => ({
|
|
41
|
-
host: {
|
|
42
|
-
layout: {
|
|
43
|
-
get snapshot() {
|
|
44
|
-
return mockState.snapshot
|
|
45
|
-
},
|
|
46
|
-
...mockState.hostLayoutMethods,
|
|
47
|
-
},
|
|
48
|
-
},
|
|
49
|
-
on: (topic: string, cb: (payload: any) => void) => {
|
|
50
|
-
let set = mockState.listeners.get(topic)
|
|
51
|
-
if (!set) {
|
|
52
|
-
set = new Set()
|
|
53
|
-
mockState.listeners.set(topic, set)
|
|
54
|
-
}
|
|
55
|
-
set.add(cb)
|
|
56
|
-
return () => {
|
|
57
|
-
set!.delete(cb)
|
|
58
|
-
}
|
|
59
|
-
},
|
|
60
|
-
}))
|
|
61
|
-
|
|
62
|
-
// Import AFTER the mock — the hooks resolve `@wippy-fe/proxy` at module-load.
|
|
63
|
-
const {
|
|
64
|
-
useWippyLayout,
|
|
65
|
-
useWippyPanel,
|
|
66
|
-
useWippyBreakpoint,
|
|
67
|
-
useWippyMainRoute,
|
|
68
|
-
} = await import('./useWippyLayout')
|
|
69
|
-
|
|
70
|
-
function makeSnapshot(over: Partial<LayoutSnapshot> = {}): LayoutSnapshot {
|
|
71
|
-
return {
|
|
72
|
-
layouts: { default: { direction: 'horizontal', children: [] } },
|
|
73
|
-
panels: {},
|
|
74
|
-
activeBreakpoint: 'default',
|
|
75
|
-
...over,
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
beforeEach(() => {
|
|
80
|
-
// Reset all mock counters; the shared snapshot ref persists across
|
|
81
|
-
// tests by design (it's installed once on first hook call).
|
|
82
|
-
for (const fn of Object.values(mockState.hostLayoutMethods)) fn.mockReset()
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
afterEach(() => {
|
|
86
|
-
// Reset mock state for next test
|
|
87
|
-
mockState.snapshot = null
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
describe('useWippyLayout', () => {
|
|
91
|
-
it('seeds snapshot.value from host.layout.snapshot at first call', () => {
|
|
92
|
-
const initial = makeSnapshot({ activeBreakpoint: 'md' })
|
|
93
|
-
mockState.snapshot = initial
|
|
94
|
-
|
|
95
|
-
let captured: any
|
|
96
|
-
const Comp = defineComponent({
|
|
97
|
-
setup() {
|
|
98
|
-
const layout = useWippyLayout()
|
|
99
|
-
captured = layout
|
|
100
|
-
return () => h('div')
|
|
101
|
-
},
|
|
102
|
-
})
|
|
103
|
-
const w = mount(Comp)
|
|
104
|
-
|
|
105
|
-
// First call seeds. Snapshot ref tracks shared state from this
|
|
106
|
-
// point — even if mockState.snapshot changes, the ref doesn't
|
|
107
|
-
// until @layout-change fires.
|
|
108
|
-
expect(captured.snapshot.value).toBeDefined()
|
|
109
|
-
w.unmount()
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
it('updates snapshot when @layout-change fires', () => {
|
|
113
|
-
let captured: any
|
|
114
|
-
const Comp = defineComponent({
|
|
115
|
-
setup() {
|
|
116
|
-
captured = useWippyLayout()
|
|
117
|
-
return () => h('div')
|
|
118
|
-
},
|
|
119
|
-
})
|
|
120
|
-
const w = mount(Comp)
|
|
121
|
-
|
|
122
|
-
const next = makeSnapshot({ activeBreakpoint: 'sm' })
|
|
123
|
-
fire('@layout-change', next)
|
|
124
|
-
expect(captured.snapshot.value).toBe(next)
|
|
125
|
-
expect(captured.activeBreakpoint.value).toBe('sm')
|
|
126
|
-
w.unmount()
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
it('isManaged tracks whether snapshot is null', () => {
|
|
130
|
-
let captured: any
|
|
131
|
-
const Comp = defineComponent({
|
|
132
|
-
setup() {
|
|
133
|
-
captured = useWippyLayout()
|
|
134
|
-
return () => h('div')
|
|
135
|
-
},
|
|
136
|
-
})
|
|
137
|
-
const w = mount(Comp)
|
|
138
|
-
|
|
139
|
-
fire('@layout-change', null)
|
|
140
|
-
expect(captured.isManaged.value).toBe(false)
|
|
141
|
-
fire('@layout-change', makeSnapshot())
|
|
142
|
-
expect(captured.isManaged.value).toBe(true)
|
|
143
|
-
w.unmount()
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
it('mutation methods call host.layout.* through', () => {
|
|
147
|
-
let captured: any
|
|
148
|
-
const Comp = defineComponent({
|
|
149
|
-
setup() {
|
|
150
|
-
captured = useWippyLayout()
|
|
151
|
-
return () => h('div')
|
|
152
|
-
},
|
|
153
|
-
})
|
|
154
|
-
const w = mount(Comp)
|
|
155
|
-
|
|
156
|
-
captured.resizePanel('files', '300px')
|
|
157
|
-
captured.collapsePanel('nav')
|
|
158
|
-
captured.expandPanel('nav')
|
|
159
|
-
captured.movePanel('a', { position: 'right' })
|
|
160
|
-
captured.removePanel('x')
|
|
161
|
-
captured.closeModal('m')
|
|
162
|
-
captured.removeFloating('f')
|
|
163
|
-
|
|
164
|
-
expect(mockState.hostLayoutMethods.resizePanel).toHaveBeenCalledWith('files', '300px')
|
|
165
|
-
expect(mockState.hostLayoutMethods.collapsePanel).toHaveBeenCalledWith('nav')
|
|
166
|
-
expect(mockState.hostLayoutMethods.expandPanel).toHaveBeenCalledWith('nav')
|
|
167
|
-
expect(mockState.hostLayoutMethods.movePanel).toHaveBeenCalledWith('a', { position: 'right' })
|
|
168
|
-
expect(mockState.hostLayoutMethods.removePanel).toHaveBeenCalledWith('x')
|
|
169
|
-
expect(mockState.hostLayoutMethods.closeModal).toHaveBeenCalledWith('m')
|
|
170
|
-
expect(mockState.hostLayoutMethods.removeFloating).toHaveBeenCalledWith('f')
|
|
171
|
-
w.unmount()
|
|
172
|
-
})
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
describe('useWippyPanel', () => {
|
|
176
|
-
it('returns panel def from snapshot.panels', () => {
|
|
177
|
-
let panelRef: any
|
|
178
|
-
const Comp = defineComponent({
|
|
179
|
-
setup() {
|
|
180
|
-
panelRef = useWippyPanel('editor')
|
|
181
|
-
return () => h('div')
|
|
182
|
-
},
|
|
183
|
-
})
|
|
184
|
-
const w = mount(Comp)
|
|
185
|
-
|
|
186
|
-
fire('@layout-change', makeSnapshot({
|
|
187
|
-
panels: { editor: { kind: 'page', id: 'editor', route: '/foo' } },
|
|
188
|
-
}))
|
|
189
|
-
expect(panelRef.value).toMatchObject({ kind: 'page', id: 'editor', route: '/foo' })
|
|
190
|
-
w.unmount()
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
it('returns null when panel id is not in snapshot', () => {
|
|
194
|
-
let panelRef: any
|
|
195
|
-
const Comp = defineComponent({
|
|
196
|
-
setup() {
|
|
197
|
-
panelRef = useWippyPanel('ghost')
|
|
198
|
-
return () => h('div')
|
|
199
|
-
},
|
|
200
|
-
})
|
|
201
|
-
const w = mount(Comp)
|
|
202
|
-
|
|
203
|
-
fire('@layout-change', makeSnapshot({ panels: {} }))
|
|
204
|
-
expect(panelRef.value).toBeNull()
|
|
205
|
-
w.unmount()
|
|
206
|
-
})
|
|
207
|
-
|
|
208
|
-
it('reacts to a Ref<string> id input', () => {
|
|
209
|
-
const id = ref('a')
|
|
210
|
-
let panelRef: any
|
|
211
|
-
const Comp = defineComponent({
|
|
212
|
-
setup() {
|
|
213
|
-
panelRef = useWippyPanel(id)
|
|
214
|
-
return () => h('div')
|
|
215
|
-
},
|
|
216
|
-
})
|
|
217
|
-
const w = mount(Comp)
|
|
218
|
-
|
|
219
|
-
fire('@layout-change', makeSnapshot({
|
|
220
|
-
panels: {
|
|
221
|
-
a: { kind: 'page', id: 'a', route: '/aa' },
|
|
222
|
-
b: { kind: 'page', id: 'b', route: '/bb' },
|
|
223
|
-
},
|
|
224
|
-
}))
|
|
225
|
-
expect(panelRef.value).toMatchObject({ id: 'a' })
|
|
226
|
-
id.value = 'b'
|
|
227
|
-
expect(panelRef.value).toMatchObject({ id: 'b' })
|
|
228
|
-
w.unmount()
|
|
229
|
-
})
|
|
230
|
-
|
|
231
|
-
it('reacts to a getter id input', () => {
|
|
232
|
-
const id = ref('a')
|
|
233
|
-
let panelRef: any
|
|
234
|
-
const Comp = defineComponent({
|
|
235
|
-
setup() {
|
|
236
|
-
panelRef = useWippyPanel(() => id.value)
|
|
237
|
-
return () => h('div')
|
|
238
|
-
},
|
|
239
|
-
})
|
|
240
|
-
const w = mount(Comp)
|
|
241
|
-
|
|
242
|
-
fire('@layout-change', makeSnapshot({
|
|
243
|
-
panels: {
|
|
244
|
-
a: { kind: 'page', id: 'a' },
|
|
245
|
-
b: { kind: 'page', id: 'b' },
|
|
246
|
-
},
|
|
247
|
-
}))
|
|
248
|
-
expect(panelRef.value?.id).toBe('a')
|
|
249
|
-
id.value = 'b'
|
|
250
|
-
expect(panelRef.value?.id).toBe('b')
|
|
251
|
-
w.unmount()
|
|
252
|
-
})
|
|
253
|
-
})
|
|
254
|
-
|
|
255
|
-
describe('useWippyBreakpoint', () => {
|
|
256
|
-
it('returns a ref', () => {
|
|
257
|
-
let bp: any
|
|
258
|
-
const Comp = defineComponent({
|
|
259
|
-
setup() {
|
|
260
|
-
bp = useWippyBreakpoint()
|
|
261
|
-
return () => h('div')
|
|
262
|
-
},
|
|
263
|
-
})
|
|
264
|
-
const w = mount(Comp)
|
|
265
|
-
expect(isRef(bp)).toBe(true)
|
|
266
|
-
w.unmount()
|
|
267
|
-
})
|
|
268
|
-
|
|
269
|
-
it('updates from @layout-breakpoint event', () => {
|
|
270
|
-
let bp: any
|
|
271
|
-
const Comp = defineComponent({
|
|
272
|
-
setup() {
|
|
273
|
-
bp = useWippyBreakpoint()
|
|
274
|
-
return () => h('div')
|
|
275
|
-
},
|
|
276
|
-
})
|
|
277
|
-
const w = mount(Comp)
|
|
278
|
-
fire('@layout-breakpoint', { name: 'lg', width: 1200 })
|
|
279
|
-
expect(bp.value).toBe('lg')
|
|
280
|
-
w.unmount()
|
|
281
|
-
})
|
|
282
|
-
|
|
283
|
-
it('also updates from @layout-change activeBreakpoint', () => {
|
|
284
|
-
let bp: any
|
|
285
|
-
const Comp = defineComponent({
|
|
286
|
-
setup() {
|
|
287
|
-
bp = useWippyBreakpoint()
|
|
288
|
-
return () => h('div')
|
|
289
|
-
},
|
|
290
|
-
})
|
|
291
|
-
const w = mount(Comp)
|
|
292
|
-
fire('@layout-change', makeSnapshot({ activeBreakpoint: 'sm' }))
|
|
293
|
-
expect(bp.value).toBe('sm')
|
|
294
|
-
w.unmount()
|
|
295
|
-
})
|
|
296
|
-
})
|
|
297
|
-
|
|
298
|
-
describe('useWippyMainRoute', () => {
|
|
299
|
-
it('walks active-breakpoint tree to find main panel', () => {
|
|
300
|
-
let route: any
|
|
301
|
-
const Comp = defineComponent({
|
|
302
|
-
setup() {
|
|
303
|
-
route = useWippyMainRoute()
|
|
304
|
-
return () => h('div')
|
|
305
|
-
},
|
|
306
|
-
})
|
|
307
|
-
const w = mount(Comp)
|
|
308
|
-
|
|
309
|
-
fire('@layout-change', makeSnapshot({
|
|
310
|
-
activeBreakpoint: 'default',
|
|
311
|
-
layouts: {
|
|
312
|
-
default: {
|
|
313
|
-
direction: 'horizontal',
|
|
314
|
-
children: [
|
|
315
|
-
{ panel: 'sidebar', size: '200px' },
|
|
316
|
-
{ panel: 'editor', size: '1fr', main: true },
|
|
317
|
-
] as any,
|
|
318
|
-
},
|
|
319
|
-
},
|
|
320
|
-
panels: {
|
|
321
|
-
sidebar: { kind: 'component', tagName: 'x' },
|
|
322
|
-
editor: { kind: 'page', id: 'e1', route: '/edit' },
|
|
323
|
-
},
|
|
324
|
-
}))
|
|
325
|
-
expect(route.value).toBe('/edit')
|
|
326
|
-
w.unmount()
|
|
327
|
-
})
|
|
328
|
-
|
|
329
|
-
it('returns empty string when no main panel in active layout', () => {
|
|
330
|
-
let route: any
|
|
331
|
-
const Comp = defineComponent({
|
|
332
|
-
setup() {
|
|
333
|
-
route = useWippyMainRoute()
|
|
334
|
-
return () => h('div')
|
|
335
|
-
},
|
|
336
|
-
})
|
|
337
|
-
const w = mount(Comp)
|
|
338
|
-
fire('@layout-change', makeSnapshot({
|
|
339
|
-
layouts: {
|
|
340
|
-
default: { direction: 'horizontal', children: [{ panel: 'a', size: '1fr' }] },
|
|
341
|
-
},
|
|
342
|
-
panels: { a: { kind: 'page', id: 'a' } },
|
|
343
|
-
}))
|
|
344
|
-
expect(route.value).toBe('')
|
|
345
|
-
w.unmount()
|
|
346
|
-
})
|
|
347
|
-
|
|
348
|
-
it('finds main inside a nested tree', () => {
|
|
349
|
-
let route: any
|
|
350
|
-
const Comp = defineComponent({
|
|
351
|
-
setup() {
|
|
352
|
-
route = useWippyMainRoute()
|
|
353
|
-
return () => h('div')
|
|
354
|
-
},
|
|
355
|
-
})
|
|
356
|
-
const w = mount(Comp)
|
|
357
|
-
fire('@layout-change', makeSnapshot({
|
|
358
|
-
layouts: {
|
|
359
|
-
default: {
|
|
360
|
-
direction: 'horizontal',
|
|
361
|
-
children: [
|
|
362
|
-
{
|
|
363
|
-
direction: 'vertical',
|
|
364
|
-
children: [{ panel: 'editor', size: '1fr', main: true }],
|
|
365
|
-
} as any,
|
|
366
|
-
],
|
|
367
|
-
},
|
|
368
|
-
},
|
|
369
|
-
panels: { editor: { kind: 'page', id: 'e', route: '/deep' } },
|
|
370
|
-
}))
|
|
371
|
-
expect(route.value).toBe('/deep')
|
|
372
|
-
w.unmount()
|
|
373
|
-
})
|
|
374
|
-
})
|
|
375
|
-
|
|
376
|
-
describe('subscription sharing', () => {
|
|
377
|
-
it('multiple hooks across multiple components share ONE @layout-change listener', () => {
|
|
378
|
-
const before = listenerCount('@layout-change')
|
|
379
|
-
|
|
380
|
-
const Comp = defineComponent({
|
|
381
|
-
setup() {
|
|
382
|
-
useWippyLayout()
|
|
383
|
-
useWippyPanel('a')
|
|
384
|
-
useWippyMainRoute()
|
|
385
|
-
return () => h('div')
|
|
386
|
-
},
|
|
387
|
-
})
|
|
388
|
-
const w1 = mount(Comp)
|
|
389
|
-
const w2 = mount(Comp)
|
|
390
|
-
|
|
391
|
-
// Module-scope shared snapshot — same listener regardless of how
|
|
392
|
-
// many hooks / components consume it.
|
|
393
|
-
expect(listenerCount('@layout-change') - before).toBeLessThanOrEqual(2)
|
|
394
|
-
// (At most: snapshot listener + breakpoint listener, both module-scoped.)
|
|
395
|
-
|
|
396
|
-
w1.unmount()
|
|
397
|
-
w2.unmount()
|
|
398
|
-
})
|
|
399
|
-
})
|
package/src/useWippyLayout.ts
DELETED
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
import type { LayoutSnapshot, PanelTarget, SizeValue } from '@wippy-fe/proxy'
|
|
2
|
-
import type { ComputedRef, MaybeRefOrGetter, Ref, ShallowRef } from 'vue'
|
|
3
|
-
import { host, on } from '@wippy-fe/proxy'
|
|
4
|
-
import { computed, shallowRef, toValue } from 'vue'
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Vue 3 reactive bindings for `host.layout.*`. Use from any component
|
|
8
|
-
* inside a Wippy iframe (managed-layout or compat). In compat mode
|
|
9
|
-
* `snapshot.value` stays `null` and mutations are silent no-ops.
|
|
10
|
-
*
|
|
11
|
-
* Boot-time seed comes from `host.layout.snapshot` (sync cache,
|
|
12
|
-
* populated from `AppContext.layout` in the child's `__WIPPY_INITIAL_CONFIG__`).
|
|
13
|
-
* Subsequent updates arrive via the `@layout-change` proxy topic —
|
|
14
|
-
* `events.ts` is the single writer that updates the cache and then
|
|
15
|
-
* fires the event, so reads here are always fresh.
|
|
16
|
-
*
|
|
17
|
-
* ```vue
|
|
18
|
-
* <script setup lang="ts">
|
|
19
|
-
* import { useWippyLayout } from '@wippy-fe/vue-host'
|
|
20
|
-
*
|
|
21
|
-
* const layout = useWippyLayout()
|
|
22
|
-
* // layout.snapshot — Ref<LayoutSnapshot | null>
|
|
23
|
-
* // layout.activeBreakpoint — ComputedRef<string>
|
|
24
|
-
* // layout.panels — ComputedRef<Record<string, unknown>>
|
|
25
|
-
* // layout.isManaged — ComputedRef<boolean>
|
|
26
|
-
*
|
|
27
|
-
* layout.resizePanel('files', '400px')
|
|
28
|
-
* layout.collapsePanel('sidebar')
|
|
29
|
-
* </script>
|
|
30
|
-
* ```
|
|
31
|
-
*/
|
|
32
|
-
export interface UseWippyLayout {
|
|
33
|
-
/** Live layout snapshot — null outside a managed-layout host. */
|
|
34
|
-
snapshot: Ref<LayoutSnapshot | null>
|
|
35
|
-
/** Currently-active breakpoint name. Empty string when no snapshot. */
|
|
36
|
-
activeBreakpoint: ComputedRef<string>
|
|
37
|
-
/** Live per-panel declaration map. Includes `route`, `props`, etc. */
|
|
38
|
-
panels: ComputedRef<Record<string, unknown>>
|
|
39
|
-
/** True when a managed-layout host is driving this iframe. */
|
|
40
|
-
isManaged: ComputedRef<boolean>
|
|
41
|
-
|
|
42
|
-
resizePanel: (panelId: string, size: SizeValue) => void
|
|
43
|
-
collapsePanel: (panelId: string) => void
|
|
44
|
-
expandPanel: (panelId: string) => void
|
|
45
|
-
movePanel: (panelId: string, target: PanelTarget) => void
|
|
46
|
-
removePanel: (panelId: string) => void
|
|
47
|
-
closeModal: (modalId: string) => void
|
|
48
|
-
removeFloating: (floatingId: string) => void
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Module-scoped shared snapshot ref + subscription. Every hook in this
|
|
53
|
-
* package reads from the same `ShallowRef`, so a component using
|
|
54
|
-
* `useWippyLayout()` + `useWippyPanel('a')` + `useWippyBreakpoint()`
|
|
55
|
-
* pays for ONE `@layout-change` subscription, not three.
|
|
56
|
-
*
|
|
57
|
-
* The subscription is installed lazily on first call and lives for the
|
|
58
|
-
* iframe's lifetime — no per-component cleanup needed (one closure on
|
|
59
|
-
* a process-wide bus is cheap; in compat-mode hosts this never fires).
|
|
60
|
-
*/
|
|
61
|
-
let sharedSnapshot: ShallowRef<LayoutSnapshot | null> | null = null
|
|
62
|
-
let sharedBreakpoint: ShallowRef<string> | null = null
|
|
63
|
-
|
|
64
|
-
function getSharedSnapshot(): ShallowRef<LayoutSnapshot | null> {
|
|
65
|
-
if (sharedSnapshot)
|
|
66
|
-
return sharedSnapshot
|
|
67
|
-
const ref = shallowRef<LayoutSnapshot | null>(host.layout.snapshot)
|
|
68
|
-
on('@layout-change', (next) => {
|
|
69
|
-
ref.value = next
|
|
70
|
-
})
|
|
71
|
-
sharedSnapshot = ref
|
|
72
|
-
return ref
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function getSharedBreakpoint(): ShallowRef<string> {
|
|
76
|
-
if (sharedBreakpoint)
|
|
77
|
-
return sharedBreakpoint
|
|
78
|
-
const ref = shallowRef<string>(host.layout.snapshot?.activeBreakpoint ?? '')
|
|
79
|
-
on('@layout-change', (snap) => {
|
|
80
|
-
if (snap?.activeBreakpoint)
|
|
81
|
-
ref.value = snap.activeBreakpoint
|
|
82
|
-
})
|
|
83
|
-
on('@layout-breakpoint', ({ name }) => {
|
|
84
|
-
ref.value = name
|
|
85
|
-
})
|
|
86
|
-
sharedBreakpoint = ref
|
|
87
|
-
return ref
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export function useWippyLayout(): UseWippyLayout {
|
|
91
|
-
const snapshot = getSharedSnapshot()
|
|
92
|
-
|
|
93
|
-
return {
|
|
94
|
-
snapshot,
|
|
95
|
-
activeBreakpoint: computed(() => snapshot.value?.activeBreakpoint ?? ''),
|
|
96
|
-
panels: computed(() => (snapshot.value?.panels ?? {}) as Record<string, unknown>),
|
|
97
|
-
isManaged: computed(() => snapshot.value !== null),
|
|
98
|
-
resizePanel: (id, size) => host.layout.resizePanel(id, size),
|
|
99
|
-
collapsePanel: id => host.layout.collapsePanel(id),
|
|
100
|
-
expandPanel: id => host.layout.expandPanel(id),
|
|
101
|
-
movePanel: (id, t) => host.layout.movePanel(id, t),
|
|
102
|
-
removePanel: id => host.layout.removePanel(id),
|
|
103
|
-
closeModal: id => host.layout.closeModal(id),
|
|
104
|
-
removeFloating: id => host.layout.removeFloating(id),
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Reactive ref to a single panel's live def. Returns `null` when the
|
|
110
|
-
* panel doesn't exist in the current snapshot (removed or not declared).
|
|
111
|
-
*
|
|
112
|
-
* `panelId` accepts a static string, a ref, or a getter — so a consumer
|
|
113
|
-
* passing `() => props.id` or a `Ref<string>` will see the panel def
|
|
114
|
-
* change reactively when the id input changes.
|
|
115
|
-
*
|
|
116
|
-
* Common use: read `panel.value?.route` inside an editor component to
|
|
117
|
-
* drive its internal router from the authoritative layout state.
|
|
118
|
-
*/
|
|
119
|
-
export function useWippyPanel(
|
|
120
|
-
panelId: MaybeRefOrGetter<string>,
|
|
121
|
-
): ComputedRef<Record<string, unknown> | null> {
|
|
122
|
-
const snapshot = getSharedSnapshot()
|
|
123
|
-
return computed(() => {
|
|
124
|
-
const id = toValue(panelId)
|
|
125
|
-
const panels = snapshot.value?.panels as Record<string, unknown> | undefined
|
|
126
|
-
return (panels?.[id] as Record<string, unknown> | undefined) ?? null
|
|
127
|
-
})
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Reactive active-breakpoint ref. Updates off both `@layout-change`
|
|
132
|
-
* and `@layout-breakpoint`; the latter fires immediately on each
|
|
133
|
-
* transition (no 50ms debounce), so UI that reacts to breakpoint
|
|
134
|
-
* transitions is responsive.
|
|
135
|
-
*/
|
|
136
|
-
export function useWippyBreakpoint(): Ref<string> {
|
|
137
|
-
return getSharedBreakpoint()
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Reactive ref to the main panel's current route. Derived by walking
|
|
142
|
-
* the snapshot's active-breakpoint tree to find the slot with
|
|
143
|
-
* `main: true` and reading `panels[id].route`. Returns empty string
|
|
144
|
-
* when there's no main panel or no route set.
|
|
145
|
-
*
|
|
146
|
-
* Note: the active breakpoint may not have a main panel (e.g. an `sm`
|
|
147
|
-
* layout that drops the main entirely for a mobile-only view). In that
|
|
148
|
-
* case this ref returns ''. Bind to `useWippyPanel(<id>).value?.route`
|
|
149
|
-
* for a specific panel's route across breakpoints.
|
|
150
|
-
*/
|
|
151
|
-
export function useWippyMainRoute(): ComputedRef<string> {
|
|
152
|
-
const snapshot = getSharedSnapshot()
|
|
153
|
-
return computed(() => {
|
|
154
|
-
const snap = snapshot.value
|
|
155
|
-
if (!snap)
|
|
156
|
-
return ''
|
|
157
|
-
const layouts = snap.layouts as Record<string, unknown>
|
|
158
|
-
const tree = layouts[snap.activeBreakpoint ?? 'default'] ?? layouts.default
|
|
159
|
-
const mainId = findMainPanel(tree)
|
|
160
|
-
if (!mainId)
|
|
161
|
-
return ''
|
|
162
|
-
const panelDef = (snap.panels as Record<string, { route?: string }>)[mainId]
|
|
163
|
-
return panelDef?.route ?? ''
|
|
164
|
-
})
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function findMainPanel(node: unknown): string | null {
|
|
168
|
-
if (!node || typeof node !== 'object')
|
|
169
|
-
return null
|
|
170
|
-
const n = node as { panel?: string, main?: boolean, children?: unknown[] }
|
|
171
|
-
if (typeof n.panel === 'string' && n.main === true)
|
|
172
|
-
return n.panel
|
|
173
|
-
if (Array.isArray(n.children)) {
|
|
174
|
-
for (const child of n.children) {
|
|
175
|
-
const found = findMainPanel(child)
|
|
176
|
-
if (found !== null)
|
|
177
|
-
return found
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
return null
|
|
181
|
-
}
|