@tooee/overlays 0.1.20 → 0.1.21
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 +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/overlay-context.d.ts +8 -0
- package/dist/overlay-context.d.ts.map +1 -1
- package/dist/overlay-context.js +4 -0
- package/dist/overlay-context.js.map +1 -1
- package/dist/overlay-store.d.ts +72 -0
- package/dist/overlay-store.d.ts.map +1 -0
- package/dist/overlay-store.js +84 -0
- package/dist/overlay-store.js.map +1 -0
- package/dist/overlay-store.typecheck.d.ts +2 -0
- package/dist/overlay-store.typecheck.d.ts.map +1 -0
- package/dist/overlay-store.typecheck.js +29 -0
- package/dist/overlay-store.typecheck.js.map +1 -0
- package/package.json +4 -1
- package/src/index.ts +15 -0
- package/src/overlay-context.tsx +12 -0
- package/src/overlay-store.ts +158 -0
- package/src/overlay-store.typecheck.ts +49 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
export { OverlayContext, OverlayControllerContext, OverlayStateContext, useOverlay, useOverlayState, useCurrentOverlay, useHasOverlay, } from "./overlay-context.js";
|
|
1
|
+
export { OverlayContext, OverlayControllerContext, OverlayStateContext, useOverlay, useOverlayState, useCurrentOverlay, useHasOverlay, useHasModalOverlay, } from "./overlay-context.js";
|
|
2
2
|
export type { OverlayContextValue, OverlayId, OverlayCloseReason, OverlayOpenOptions, OverlayRole, OverlayRenderArgs, OverlayRenderer, OverlayHandle, OverlayController, OverlayState, } from "./overlay-context.js";
|
|
3
|
+
export { createOverlayStore, selectHasOverlay, selectIsOpen, selectStack, selectStackIds, selectTop, } from "./overlay-store.js";
|
|
4
|
+
export type { OverlayClosedEmit, OverlayRecord, OverlayStore, OverlayStoreContext, } from "./overlay-store.js";
|
|
3
5
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,wBAAwB,EACxB,mBAAmB,EACnB,UAAU,EACV,eAAe,EACf,iBAAiB,EACjB,aAAa,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,wBAAwB,EACxB,mBAAmB,EACnB,UAAU,EACV,eAAe,EACf,iBAAiB,EACjB,aAAa,EACb,kBAAkB,GACnB,MAAM,sBAAsB,CAAA;AAC7B,YAAY,EACV,mBAAmB,EACnB,SAAS,EACT,kBAAkB,EAClB,kBAAkB,EAClB,WAAW,EACX,iBAAiB,EACjB,eAAe,EACf,aAAa,EACb,iBAAiB,EACjB,YAAY,GACb,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACX,cAAc,EACd,SAAS,GACV,MAAM,oBAAoB,CAAA;AAC3B,YAAY,EACV,iBAAiB,EACjB,aAAa,EACb,YAAY,EACZ,mBAAmB,GACpB,MAAM,oBAAoB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
export { OverlayContext, OverlayControllerContext, OverlayStateContext, useOverlay, useOverlayState, useCurrentOverlay, useHasOverlay, } from "./overlay-context.js";
|
|
1
|
+
export { OverlayContext, OverlayControllerContext, OverlayStateContext, useOverlay, useOverlayState, useCurrentOverlay, useHasOverlay, useHasModalOverlay, } from "./overlay-context.js";
|
|
2
|
+
export { createOverlayStore, selectHasOverlay, selectIsOpen, selectStack, selectStackIds, selectTop, } from "./overlay-store.js";
|
|
2
3
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,wBAAwB,EACxB,mBAAmB,EACnB,UAAU,EACV,eAAe,EACf,iBAAiB,EACjB,aAAa,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,EACd,wBAAwB,EACxB,mBAAmB,EACnB,UAAU,EACV,eAAe,EACf,iBAAiB,EACjB,aAAa,EACb,kBAAkB,GACnB,MAAM,sBAAsB,CAAA;AAa7B,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACX,cAAc,EACd,SAAS,GACV,MAAM,oBAAoB,CAAA"}
|
|
@@ -52,6 +52,13 @@ export interface OverlayController {
|
|
|
52
52
|
export interface OverlayState {
|
|
53
53
|
current: ReactNode | null;
|
|
54
54
|
hasOverlay: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* True when any overlay other than a passive owned surface is open. Passive
|
|
57
|
+
* surfaces (e.g. the which-key hint) render for visuals only and never own
|
|
58
|
+
* input, so they don't count. Use this for guards that should stand down
|
|
59
|
+
* while a modal overlay is up but keep working under passive hints.
|
|
60
|
+
*/
|
|
61
|
+
hasModalOverlay: boolean;
|
|
55
62
|
stack: OverlayId[];
|
|
56
63
|
}
|
|
57
64
|
export interface OverlayContextValue {
|
|
@@ -66,5 +73,6 @@ export declare const OverlayContext: import("react").Context<OverlayContextValue
|
|
|
66
73
|
export declare function useOverlay(): OverlayController;
|
|
67
74
|
export declare function useOverlayState(): OverlayState;
|
|
68
75
|
export declare function useHasOverlay(): boolean;
|
|
76
|
+
export declare function useHasModalOverlay(): boolean;
|
|
69
77
|
export declare function useCurrentOverlay(): ReactNode | null;
|
|
70
78
|
//# sourceMappingURL=overlay-context.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"overlay-context.d.ts","sourceRoot":"","sources":["../src/overlay-context.tsx"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"overlay-context.d.ts","sourceRoot":"","sources":["../src/overlay-context.tsx"],"names":[],"mappings":"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"}
|
package/dist/overlay-context.js
CHANGED
|
@@ -12,6 +12,7 @@ const defaultController = {
|
|
|
12
12
|
const defaultState = {
|
|
13
13
|
current: null,
|
|
14
14
|
hasOverlay: false,
|
|
15
|
+
hasModalOverlay: false,
|
|
15
16
|
stack: [],
|
|
16
17
|
};
|
|
17
18
|
export const OverlayControllerContext = createContext(defaultController);
|
|
@@ -33,6 +34,9 @@ export function useOverlayState() {
|
|
|
33
34
|
export function useHasOverlay() {
|
|
34
35
|
return useContext(OverlayStateContext).hasOverlay;
|
|
35
36
|
}
|
|
37
|
+
export function useHasModalOverlay() {
|
|
38
|
+
return useContext(OverlayStateContext).hasModalOverlay;
|
|
39
|
+
}
|
|
36
40
|
export function useCurrentOverlay() {
|
|
37
41
|
return useContext(OverlayStateContext).current;
|
|
38
42
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"overlay-context.js","sourceRoot":"","sources":["../src/overlay-context.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"overlay-context.js","sourceRoot":"","sources":["../src/overlay-context.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,OAAO,CAAA;AAyFjD,WAAW;AAEX,MAAM,iBAAiB,GAAsB;IAC3C,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,GAAE,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,GAAE,CAAC,EAAE,CAAC;IACzD,MAAM,EAAE,GAAG,EAAE,GAAE,CAAC;IAChB,IAAI,EAAE,GAAG,EAAE,GAAE,CAAC;IACd,IAAI,EAAE,GAAG,EAAE,GAAE,CAAC;IACd,QAAQ,EAAE,GAAG,EAAE,GAAE,CAAC;IAClB,MAAM,EAAE,GAAG,EAAE,CAAC,KAAK;IACnB,KAAK,EAAE,IAAI;CACZ,CAAA;AAED,MAAM,YAAY,GAAiB;IACjC,OAAO,EAAE,IAAI;IACb,UAAU,EAAE,KAAK;IACjB,eAAe,EAAE,KAAK;IACtB,KAAK,EAAE,EAAE;CACV,CAAA;AAED,MAAM,CAAC,MAAM,wBAAwB,GAAG,aAAa,CAAoB,iBAAiB,CAAC,CAAA;AAC3F,MAAM,CAAC,MAAM,mBAAmB,GAAG,aAAa,CAAe,YAAY,CAAC,CAAA;AAE5E,mFAAmF;AACnF,MAAM,CAAC,MAAM,cAAc,GAAG,aAAa,CAAsB;IAC/D,IAAI,EAAE,GAAG,EAAE,GAAE,CAAC;IACd,IAAI,EAAE,GAAG,EAAE,GAAE,CAAC;IACd,OAAO,EAAE,IAAI;IACb,UAAU,EAAE,KAAK;CAClB,CAAC,CAAA;AAEF,QAAQ;AAER,MAAM,UAAU,UAAU;IACxB,OAAO,UAAU,CAAC,wBAAwB,CAAC,CAAA;AAC7C,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,OAAO,UAAU,CAAC,mBAAmB,CAAC,CAAA;AACxC,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,OAAO,UAAU,CAAC,mBAAmB,CAAC,CAAC,UAAU,CAAA;AACnD,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,OAAO,UAAU,CAAC,mBAAmB,CAAC,CAAC,eAAe,CAAA;AACxD,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,OAAO,UAAU,CAAC,mBAAmB,CAAC,CAAC,OAAO,CAAA;AAChD,CAAC"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { OverlayCloseReason, OverlayId, OverlayOpenOptions, OverlayRenderer } from "./overlay-context.js";
|
|
2
|
+
/**
|
|
3
|
+
* An overlay stack entry as tracked by the store. `prevMode` is the host mode
|
|
4
|
+
* captured at open time (mode itself stays outside this package — see the
|
|
5
|
+
* shell's OverlayProvider, which owns all mode side effects).
|
|
6
|
+
*/
|
|
7
|
+
export interface OverlayRecord<TPayload = unknown> {
|
|
8
|
+
id: OverlayId;
|
|
9
|
+
render: OverlayRenderer<TPayload>;
|
|
10
|
+
payload: TPayload;
|
|
11
|
+
options: OverlayOpenOptions;
|
|
12
|
+
prevMode: string;
|
|
13
|
+
}
|
|
14
|
+
export interface OverlayStoreContext {
|
|
15
|
+
stack: readonly OverlayRecord[];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Emitted when a record leaves the stack (close, closeTop, or same-id
|
|
19
|
+
* replacement). Lifecycle side effects (onClose callbacks, mode restoration,
|
|
20
|
+
* sequence-reset bridges) are performed by the adapter subscribed via
|
|
21
|
+
* `store.on("closed", ...)`; transitions stay pure.
|
|
22
|
+
*/
|
|
23
|
+
export interface OverlayClosedEmit {
|
|
24
|
+
record: OverlayRecord;
|
|
25
|
+
reason: OverlayCloseReason;
|
|
26
|
+
/**
|
|
27
|
+
* Legacy mode restoration decision, computed in the transition (it needs
|
|
28
|
+
* stack state): `record.prevMode` when the record is non-ownCommands,
|
|
29
|
+
* `restoreMode !== false`, AND it was the topmost non-ownCommands entry
|
|
30
|
+
* (closing a buried legacy overlay must not clobber the mode set by the one
|
|
31
|
+
* above it — R-04); otherwise null. Same-id replacement displacement never
|
|
32
|
+
* restores (the successor takes over).
|
|
33
|
+
*/
|
|
34
|
+
restoreModeTo: string | null;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Event payload map, passed explicitly as a `createStore` generic
|
|
38
|
+
* (@xstate/store v4 removed the `emits` config; event/emitted types come from
|
|
39
|
+
* generics or `schemas`). Both maps here are deliberately CLOSED — no index
|
|
40
|
+
* signatures — so unknown `store.trigger.*` / `store.send` / `store.on` /
|
|
41
|
+
* `enqueue.emit.*` event names are compile errors (pinned by
|
|
42
|
+
* `overlay-store.typecheck.ts`). The v4 `schemas.emitted` alternative was
|
|
43
|
+
* evaluated and rejected: it gives the same strictness while adding runtime
|
|
44
|
+
* schema objects this store does not need.
|
|
45
|
+
*/
|
|
46
|
+
export type OverlayStoreEvents = {
|
|
47
|
+
opened: {
|
|
48
|
+
record: OverlayRecord;
|
|
49
|
+
};
|
|
50
|
+
updated: {
|
|
51
|
+
id: OverlayId;
|
|
52
|
+
next: unknown | ((prev: unknown) => unknown);
|
|
53
|
+
};
|
|
54
|
+
closed: {
|
|
55
|
+
id: OverlayId;
|
|
56
|
+
reason: OverlayCloseReason;
|
|
57
|
+
};
|
|
58
|
+
closedTop: {
|
|
59
|
+
reason: OverlayCloseReason;
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
export declare function createOverlayStore(): import("@xstate/store").Store<OverlayStoreContext, OverlayStoreEvents, OverlayClosedEmit & {
|
|
63
|
+
type: "closed";
|
|
64
|
+
}>;
|
|
65
|
+
export type OverlayStore = ReturnType<typeof createOverlayStore>;
|
|
66
|
+
export declare function selectStack(ctx: OverlayStoreContext): readonly OverlayRecord[];
|
|
67
|
+
export declare function selectTop(ctx: OverlayStoreContext): OverlayRecord | null;
|
|
68
|
+
export declare function selectHasOverlay(ctx: OverlayStoreContext): boolean;
|
|
69
|
+
export declare function selectIsOpen(ctx: OverlayStoreContext, id: OverlayId): boolean;
|
|
70
|
+
/** Fresh array; prefer selectStack identity + memo in render paths. */
|
|
71
|
+
export declare function selectStackIds(ctx: OverlayStoreContext): readonly OverlayId[];
|
|
72
|
+
//# sourceMappingURL=overlay-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overlay-store.d.ts","sourceRoot":"","sources":["../src/overlay-store.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,kBAAkB,EAClB,SAAS,EACT,kBAAkB,EAClB,eAAe,EAChB,MAAM,sBAAsB,CAAA;AAE7B;;;;GAIG;AACH,MAAM,WAAW,aAAa,CAAC,QAAQ,GAAG,OAAO;IAC/C,EAAE,EAAE,SAAS,CAAA;IACb,MAAM,EAAE,eAAe,CAAC,QAAQ,CAAC,CAAA;IACjC,OAAO,EAAE,QAAQ,CAAA;IACjB,OAAO,EAAE,kBAAkB,CAAA;IAC3B,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,SAAS,aAAa,EAAE,CAAA;CAChC;AAED;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,aAAa,CAAA;IACrB,MAAM,EAAE,kBAAkB,CAAA;IAC1B;;;;;;;OAOG;IACH,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;CAC7B;AAgBD;;;;;;;;;GASG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE;QAAE,MAAM,EAAE,aAAa,CAAA;KAAE,CAAA;IACjC,OAAO,EAAE;QAAE,EAAE,EAAE,SAAS,CAAC;QAAC,IAAI,EAAE,OAAO,GAAG,CAAC,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC,CAAA;KAAE,CAAA;IACxE,MAAM,EAAE;QAAE,EAAE,EAAE,SAAS,CAAC;QAAC,MAAM,EAAE,kBAAkB,CAAA;KAAE,CAAA;IACrD,SAAS,EAAE;QAAE,MAAM,EAAE,kBAAkB,CAAA;KAAE,CAAA;CAC1C,CAAA;AAOD,wBAAgB,kBAAkB;;GAmDjC;AAED,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAA;AAIhE,wBAAgB,WAAW,CAAC,GAAG,EAAE,mBAAmB,GAAG,SAAS,aAAa,EAAE,CAE9E;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,mBAAmB,GAAG,aAAa,GAAG,IAAI,CAExE;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,mBAAmB,GAAG,OAAO,CAElE;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,mBAAmB,EAAE,EAAE,EAAE,SAAS,GAAG,OAAO,CAE7E;AAED,uEAAuE;AACvE,wBAAgB,cAAc,CAAC,GAAG,EAAE,mBAAmB,GAAG,SAAS,SAAS,EAAE,CAE7E"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { createStore } from "@xstate/store";
|
|
2
|
+
/**
|
|
3
|
+
* Owned command surfaces keep their mode local; the host mode was never
|
|
4
|
+
* mutated on open, so there is nothing to restore. For legacy entries, only
|
|
5
|
+
* the topmost non-ownCommands overlay owns the host mode.
|
|
6
|
+
*/
|
|
7
|
+
function restoreModeDecision(stack, record) {
|
|
8
|
+
if (record.options.ownCommands || record.options.restoreMode === false)
|
|
9
|
+
return null;
|
|
10
|
+
const topLegacy = stack.findLast((entry) => !entry.options.ownCommands);
|
|
11
|
+
return topLegacy === record ? record.prevMode : null;
|
|
12
|
+
}
|
|
13
|
+
export function createOverlayStore() {
|
|
14
|
+
return createStore({
|
|
15
|
+
context: { stack: [] },
|
|
16
|
+
on: {
|
|
17
|
+
opened: (ctx, event, enqueue) => {
|
|
18
|
+
// Replacing an existing same-id entry closes it: consumers release
|
|
19
|
+
// open-state (e.g. a picker's isOpen) via the contract's "replaced"
|
|
20
|
+
// reason instead of the entry being silently filtered away.
|
|
21
|
+
const displaced = ctx.stack.find((entry) => entry.id === event.record.id);
|
|
22
|
+
if (displaced) {
|
|
23
|
+
enqueue.emit.closed({ record: displaced, reason: "replaced", restoreModeTo: null });
|
|
24
|
+
}
|
|
25
|
+
const filtered = displaced
|
|
26
|
+
? ctx.stack.filter((entry) => entry.id !== event.record.id)
|
|
27
|
+
: ctx.stack;
|
|
28
|
+
return { stack: [...filtered, event.record] };
|
|
29
|
+
},
|
|
30
|
+
updated: (ctx, event) => {
|
|
31
|
+
const idx = ctx.stack.findIndex((entry) => entry.id === event.id);
|
|
32
|
+
if (idx === -1)
|
|
33
|
+
return ctx;
|
|
34
|
+
const record = ctx.stack[idx];
|
|
35
|
+
const payload = typeof event.next === "function"
|
|
36
|
+
? event.next(record.payload)
|
|
37
|
+
: event.next;
|
|
38
|
+
const stack = [...ctx.stack];
|
|
39
|
+
stack[idx] = { ...record, payload };
|
|
40
|
+
return { stack };
|
|
41
|
+
},
|
|
42
|
+
closed: (ctx, event, enqueue) => {
|
|
43
|
+
const record = ctx.stack.find((entry) => entry.id === event.id);
|
|
44
|
+
if (!record)
|
|
45
|
+
return ctx;
|
|
46
|
+
enqueue.emit.closed({
|
|
47
|
+
record,
|
|
48
|
+
reason: event.reason,
|
|
49
|
+
restoreModeTo: restoreModeDecision(ctx.stack, record),
|
|
50
|
+
});
|
|
51
|
+
return { stack: ctx.stack.filter((entry) => entry !== record) };
|
|
52
|
+
},
|
|
53
|
+
closedTop: (ctx, event, enqueue) => {
|
|
54
|
+
if (ctx.stack.length === 0)
|
|
55
|
+
return ctx;
|
|
56
|
+
const record = ctx.stack[ctx.stack.length - 1];
|
|
57
|
+
enqueue.emit.closed({
|
|
58
|
+
record,
|
|
59
|
+
reason: event.reason,
|
|
60
|
+
restoreModeTo: restoreModeDecision(ctx.stack, record),
|
|
61
|
+
});
|
|
62
|
+
return { stack: ctx.stack.slice(0, -1) };
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
// --- Selectors ---------------------------------------------------------------
|
|
68
|
+
export function selectStack(ctx) {
|
|
69
|
+
return ctx.stack;
|
|
70
|
+
}
|
|
71
|
+
export function selectTop(ctx) {
|
|
72
|
+
return ctx.stack.length > 0 ? ctx.stack[ctx.stack.length - 1] : null;
|
|
73
|
+
}
|
|
74
|
+
export function selectHasOverlay(ctx) {
|
|
75
|
+
return ctx.stack.length > 0;
|
|
76
|
+
}
|
|
77
|
+
export function selectIsOpen(ctx, id) {
|
|
78
|
+
return ctx.stack.some((entry) => entry.id === id);
|
|
79
|
+
}
|
|
80
|
+
/** Fresh array; prefer selectStack identity + memo in render paths. */
|
|
81
|
+
export function selectStackIds(ctx) {
|
|
82
|
+
return ctx.stack.map((entry) => entry.id);
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=overlay-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overlay-store.js","sourceRoot":"","sources":["../src/overlay-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overlay-store.typecheck.d.ts","sourceRoot":"","sources":["../src/overlay-store.typecheck.ts"],"names":[],"mappings":"AAoBA,wBAAgB,sBAAsB,IAAI,IAAI,CA4B7C"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createOverlayStore } from "./overlay-store.js";
|
|
2
|
+
// Never called; exists only to be typechecked.
|
|
3
|
+
export function overlayStoreTypeChecks() {
|
|
4
|
+
const store = createOverlayStore();
|
|
5
|
+
// --- Valid usage must compile -------------------------------------------
|
|
6
|
+
store.trigger.opened({ record });
|
|
7
|
+
store.trigger.updated({ id: "x", next: null });
|
|
8
|
+
store.trigger.closed({ id: "x", reason });
|
|
9
|
+
store.trigger.closedTop({ reason });
|
|
10
|
+
store.send({ type: "closedTop", reason });
|
|
11
|
+
store.on("closed", (emitted) => {
|
|
12
|
+
const _reason = emitted.reason;
|
|
13
|
+
const _restoreModeTo = emitted.restoreModeTo;
|
|
14
|
+
const _record = emitted.record;
|
|
15
|
+
});
|
|
16
|
+
// --- Unknown event names must NOT compile --------------------------------
|
|
17
|
+
// @ts-expect-error unknown trigger event name
|
|
18
|
+
store.trigger.bogusEvent({});
|
|
19
|
+
// @ts-expect-error unknown send event type
|
|
20
|
+
store.send({ type: "bogusEvent" });
|
|
21
|
+
// @ts-expect-error unknown emitted event name
|
|
22
|
+
store.on("bogusEmit", () => { });
|
|
23
|
+
// --- Wrong payloads must NOT compile --------------------------------------
|
|
24
|
+
// @ts-expect-error reason must be an OverlayCloseReason
|
|
25
|
+
store.trigger.closedTop({ reason: 42 });
|
|
26
|
+
// @ts-expect-error record is required
|
|
27
|
+
store.trigger.opened({});
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=overlay-store.typecheck.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overlay-store.typecheck.js","sourceRoot":"","sources":["../src/overlay-store.typecheck.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,kBAAkB,EAAsB,MAAM,oBAAoB,CAAA;AAK3E,+CAA+C;AAC/C,MAAM,UAAU,sBAAsB;IACpC,MAAM,KAAK,GAAG,kBAAkB,EAAE,CAAA;IAElC,2EAA2E;IAC3E,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC,CAAA;IAChC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;IAC9C,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAA;IACzC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC,CAAA;IACnC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC,CAAA;IACzC,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,OAAO,GAAuB,OAAO,CAAC,MAAM,CAAA;QAClD,MAAM,cAAc,GAAkB,OAAO,CAAC,aAAa,CAAA;QAC3D,MAAM,OAAO,GAAkB,OAAO,CAAC,MAAM,CAAA;IAC/C,CAAC,CAAC,CAAA;IAEF,4EAA4E;IAC5E,8CAA8C;IAC9C,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC,CAAA;IAC5B,2CAA2C;IAC3C,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAA;IAClC,8CAA8C;IAC9C,KAAK,CAAC,EAAE,CAAC,WAAW,EAAE,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;IAE/B,6EAA6E;IAC7E,wDAAwD;IACxD,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAA;IACvC,sCAAsC;IACtC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;AAC1B,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tooee/overlays",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.21",
|
|
4
4
|
"description": "Overlay stack management for Tooee",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -33,6 +33,9 @@
|
|
|
33
33
|
"scripts": {
|
|
34
34
|
"typecheck": "tsc --noEmit"
|
|
35
35
|
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@xstate/store": "^4"
|
|
38
|
+
},
|
|
36
39
|
"devDependencies": {
|
|
37
40
|
"@types/bun": "^1.3.10",
|
|
38
41
|
"@types/react": "^19.2.14",
|
package/src/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ export {
|
|
|
6
6
|
useOverlayState,
|
|
7
7
|
useCurrentOverlay,
|
|
8
8
|
useHasOverlay,
|
|
9
|
+
useHasModalOverlay,
|
|
9
10
|
} from "./overlay-context.js"
|
|
10
11
|
export type {
|
|
11
12
|
OverlayContextValue,
|
|
@@ -19,3 +20,17 @@ export type {
|
|
|
19
20
|
OverlayController,
|
|
20
21
|
OverlayState,
|
|
21
22
|
} from "./overlay-context.js"
|
|
23
|
+
export {
|
|
24
|
+
createOverlayStore,
|
|
25
|
+
selectHasOverlay,
|
|
26
|
+
selectIsOpen,
|
|
27
|
+
selectStack,
|
|
28
|
+
selectStackIds,
|
|
29
|
+
selectTop,
|
|
30
|
+
} from "./overlay-store.js"
|
|
31
|
+
export type {
|
|
32
|
+
OverlayClosedEmit,
|
|
33
|
+
OverlayRecord,
|
|
34
|
+
OverlayStore,
|
|
35
|
+
OverlayStoreContext,
|
|
36
|
+
} from "./overlay-store.js"
|
package/src/overlay-context.tsx
CHANGED
|
@@ -69,6 +69,13 @@ export interface OverlayController {
|
|
|
69
69
|
export interface OverlayState {
|
|
70
70
|
current: ReactNode | null
|
|
71
71
|
hasOverlay: boolean
|
|
72
|
+
/**
|
|
73
|
+
* True when any overlay other than a passive owned surface is open. Passive
|
|
74
|
+
* surfaces (e.g. the which-key hint) render for visuals only and never own
|
|
75
|
+
* input, so they don't count. Use this for guards that should stand down
|
|
76
|
+
* while a modal overlay is up but keep working under passive hints.
|
|
77
|
+
*/
|
|
78
|
+
hasModalOverlay: boolean
|
|
72
79
|
stack: OverlayId[]
|
|
73
80
|
}
|
|
74
81
|
|
|
@@ -95,6 +102,7 @@ const defaultController: OverlayController = {
|
|
|
95
102
|
const defaultState: OverlayState = {
|
|
96
103
|
current: null,
|
|
97
104
|
hasOverlay: false,
|
|
105
|
+
hasModalOverlay: false,
|
|
98
106
|
stack: [],
|
|
99
107
|
}
|
|
100
108
|
|
|
@@ -123,6 +131,10 @@ export function useHasOverlay(): boolean {
|
|
|
123
131
|
return useContext(OverlayStateContext).hasOverlay
|
|
124
132
|
}
|
|
125
133
|
|
|
134
|
+
export function useHasModalOverlay(): boolean {
|
|
135
|
+
return useContext(OverlayStateContext).hasModalOverlay
|
|
136
|
+
}
|
|
137
|
+
|
|
126
138
|
export function useCurrentOverlay(): ReactNode | null {
|
|
127
139
|
return useContext(OverlayStateContext).current
|
|
128
140
|
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { createStore } from "@xstate/store"
|
|
2
|
+
import type {
|
|
3
|
+
OverlayCloseReason,
|
|
4
|
+
OverlayId,
|
|
5
|
+
OverlayOpenOptions,
|
|
6
|
+
OverlayRenderer,
|
|
7
|
+
} from "./overlay-context.js"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* An overlay stack entry as tracked by the store. `prevMode` is the host mode
|
|
11
|
+
* captured at open time (mode itself stays outside this package — see the
|
|
12
|
+
* shell's OverlayProvider, which owns all mode side effects).
|
|
13
|
+
*/
|
|
14
|
+
export interface OverlayRecord<TPayload = unknown> {
|
|
15
|
+
id: OverlayId
|
|
16
|
+
render: OverlayRenderer<TPayload>
|
|
17
|
+
payload: TPayload
|
|
18
|
+
options: OverlayOpenOptions
|
|
19
|
+
prevMode: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface OverlayStoreContext {
|
|
23
|
+
stack: readonly OverlayRecord[]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Emitted when a record leaves the stack (close, closeTop, or same-id
|
|
28
|
+
* replacement). Lifecycle side effects (onClose callbacks, mode restoration,
|
|
29
|
+
* sequence-reset bridges) are performed by the adapter subscribed via
|
|
30
|
+
* `store.on("closed", ...)`; transitions stay pure.
|
|
31
|
+
*/
|
|
32
|
+
export interface OverlayClosedEmit {
|
|
33
|
+
record: OverlayRecord
|
|
34
|
+
reason: OverlayCloseReason
|
|
35
|
+
/**
|
|
36
|
+
* Legacy mode restoration decision, computed in the transition (it needs
|
|
37
|
+
* stack state): `record.prevMode` when the record is non-ownCommands,
|
|
38
|
+
* `restoreMode !== false`, AND it was the topmost non-ownCommands entry
|
|
39
|
+
* (closing a buried legacy overlay must not clobber the mode set by the one
|
|
40
|
+
* above it — R-04); otherwise null. Same-id replacement displacement never
|
|
41
|
+
* restores (the successor takes over).
|
|
42
|
+
*/
|
|
43
|
+
restoreModeTo: string | null
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Owned command surfaces keep their mode local; the host mode was never
|
|
48
|
+
* mutated on open, so there is nothing to restore. For legacy entries, only
|
|
49
|
+
* the topmost non-ownCommands overlay owns the host mode.
|
|
50
|
+
*/
|
|
51
|
+
function restoreModeDecision(
|
|
52
|
+
stack: readonly OverlayRecord[],
|
|
53
|
+
record: OverlayRecord,
|
|
54
|
+
): string | null {
|
|
55
|
+
if (record.options.ownCommands || record.options.restoreMode === false) return null
|
|
56
|
+
const topLegacy = stack.findLast((entry) => !entry.options.ownCommands)
|
|
57
|
+
return topLegacy === record ? record.prevMode : null
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Event payload map, passed explicitly as a `createStore` generic
|
|
62
|
+
* (@xstate/store v4 removed the `emits` config; event/emitted types come from
|
|
63
|
+
* generics or `schemas`). Both maps here are deliberately CLOSED — no index
|
|
64
|
+
* signatures — so unknown `store.trigger.*` / `store.send` / `store.on` /
|
|
65
|
+
* `enqueue.emit.*` event names are compile errors (pinned by
|
|
66
|
+
* `overlay-store.typecheck.ts`). The v4 `schemas.emitted` alternative was
|
|
67
|
+
* evaluated and rejected: it gives the same strictness while adding runtime
|
|
68
|
+
* schema objects this store does not need.
|
|
69
|
+
*/
|
|
70
|
+
export type OverlayStoreEvents = {
|
|
71
|
+
opened: { record: OverlayRecord }
|
|
72
|
+
updated: { id: OverlayId; next: unknown | ((prev: unknown) => unknown) }
|
|
73
|
+
closed: { id: OverlayId; reason: OverlayCloseReason }
|
|
74
|
+
closedTop: { reason: OverlayCloseReason }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Emitted payload map (see OverlayStoreEvents docs — deliberately closed). */
|
|
78
|
+
type OverlayStoreEmitted = {
|
|
79
|
+
closed: OverlayClosedEmit
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function createOverlayStore() {
|
|
83
|
+
return createStore<OverlayStoreContext, OverlayStoreEvents, OverlayStoreEmitted>({
|
|
84
|
+
context: { stack: [] },
|
|
85
|
+
on: {
|
|
86
|
+
opened: (ctx, event, enqueue) => {
|
|
87
|
+
// Replacing an existing same-id entry closes it: consumers release
|
|
88
|
+
// open-state (e.g. a picker's isOpen) via the contract's "replaced"
|
|
89
|
+
// reason instead of the entry being silently filtered away.
|
|
90
|
+
const displaced = ctx.stack.find((entry) => entry.id === event.record.id)
|
|
91
|
+
if (displaced) {
|
|
92
|
+
enqueue.emit.closed({ record: displaced, reason: "replaced", restoreModeTo: null })
|
|
93
|
+
}
|
|
94
|
+
const filtered = displaced
|
|
95
|
+
? ctx.stack.filter((entry) => entry.id !== event.record.id)
|
|
96
|
+
: ctx.stack
|
|
97
|
+
return { stack: [...filtered, event.record] }
|
|
98
|
+
},
|
|
99
|
+
updated: (ctx, event) => {
|
|
100
|
+
const idx = ctx.stack.findIndex((entry) => entry.id === event.id)
|
|
101
|
+
if (idx === -1) return ctx
|
|
102
|
+
const record = ctx.stack[idx]!
|
|
103
|
+
const payload =
|
|
104
|
+
typeof event.next === "function"
|
|
105
|
+
? (event.next as (prev: unknown) => unknown)(record.payload)
|
|
106
|
+
: event.next
|
|
107
|
+
const stack = [...ctx.stack]
|
|
108
|
+
stack[idx] = { ...record, payload }
|
|
109
|
+
return { stack }
|
|
110
|
+
},
|
|
111
|
+
closed: (ctx, event, enqueue) => {
|
|
112
|
+
const record = ctx.stack.find((entry) => entry.id === event.id)
|
|
113
|
+
if (!record) return ctx
|
|
114
|
+
enqueue.emit.closed({
|
|
115
|
+
record,
|
|
116
|
+
reason: event.reason,
|
|
117
|
+
restoreModeTo: restoreModeDecision(ctx.stack, record),
|
|
118
|
+
})
|
|
119
|
+
return { stack: ctx.stack.filter((entry) => entry !== record) }
|
|
120
|
+
},
|
|
121
|
+
closedTop: (ctx, event, enqueue) => {
|
|
122
|
+
if (ctx.stack.length === 0) return ctx
|
|
123
|
+
const record = ctx.stack[ctx.stack.length - 1]!
|
|
124
|
+
enqueue.emit.closed({
|
|
125
|
+
record,
|
|
126
|
+
reason: event.reason,
|
|
127
|
+
restoreModeTo: restoreModeDecision(ctx.stack, record),
|
|
128
|
+
})
|
|
129
|
+
return { stack: ctx.stack.slice(0, -1) }
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export type OverlayStore = ReturnType<typeof createOverlayStore>
|
|
136
|
+
|
|
137
|
+
// --- Selectors ---------------------------------------------------------------
|
|
138
|
+
|
|
139
|
+
export function selectStack(ctx: OverlayStoreContext): readonly OverlayRecord[] {
|
|
140
|
+
return ctx.stack
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function selectTop(ctx: OverlayStoreContext): OverlayRecord | null {
|
|
144
|
+
return ctx.stack.length > 0 ? ctx.stack[ctx.stack.length - 1]! : null
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function selectHasOverlay(ctx: OverlayStoreContext): boolean {
|
|
148
|
+
return ctx.stack.length > 0
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function selectIsOpen(ctx: OverlayStoreContext, id: OverlayId): boolean {
|
|
152
|
+
return ctx.stack.some((entry) => entry.id === id)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Fresh array; prefer selectStack identity + memo in render paths. */
|
|
156
|
+
export function selectStackIds(ctx: OverlayStoreContext): readonly OverlayId[] {
|
|
157
|
+
return ctx.stack.map((entry) => entry.id)
|
|
158
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compile-time regression tests for the overlay store's event typing. This
|
|
3
|
+
* module has no runtime behaviour and is never imported; it exists so that
|
|
4
|
+
* `tsc -b` (which only checks `src/`) fails if the store's typing is ever
|
|
5
|
+
* loosened.
|
|
6
|
+
*
|
|
7
|
+
* The store's event/emitted payload maps (`OverlayStoreEvents`, the emitted
|
|
8
|
+
* map inside `overlay-store.ts`) are deliberately CLOSED — no index
|
|
9
|
+
* signatures. That makes unknown `store.trigger.*`, `store.send`,
|
|
10
|
+
* `store.on(...)`, and `enqueue.emit.*` event names compile errors. If one of
|
|
11
|
+
* the `@ts-expect-error` lines below starts compiling, the maps have been
|
|
12
|
+
* widened (e.g. an index signature was added); fix the maps, not this file.
|
|
13
|
+
*/
|
|
14
|
+
import type { OverlayCloseReason } from "./overlay-context.js"
|
|
15
|
+
import { createOverlayStore, type OverlayRecord } from "./overlay-store.js"
|
|
16
|
+
|
|
17
|
+
declare const record: OverlayRecord
|
|
18
|
+
declare const reason: OverlayCloseReason
|
|
19
|
+
|
|
20
|
+
// Never called; exists only to be typechecked.
|
|
21
|
+
export function overlayStoreTypeChecks(): void {
|
|
22
|
+
const store = createOverlayStore()
|
|
23
|
+
|
|
24
|
+
// --- Valid usage must compile -------------------------------------------
|
|
25
|
+
store.trigger.opened({ record })
|
|
26
|
+
store.trigger.updated({ id: "x", next: null })
|
|
27
|
+
store.trigger.closed({ id: "x", reason })
|
|
28
|
+
store.trigger.closedTop({ reason })
|
|
29
|
+
store.send({ type: "closedTop", reason })
|
|
30
|
+
store.on("closed", (emitted) => {
|
|
31
|
+
const _reason: OverlayCloseReason = emitted.reason
|
|
32
|
+
const _restoreModeTo: string | null = emitted.restoreModeTo
|
|
33
|
+
const _record: OverlayRecord = emitted.record
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
// --- Unknown event names must NOT compile --------------------------------
|
|
37
|
+
// @ts-expect-error unknown trigger event name
|
|
38
|
+
store.trigger.bogusEvent({})
|
|
39
|
+
// @ts-expect-error unknown send event type
|
|
40
|
+
store.send({ type: "bogusEvent" })
|
|
41
|
+
// @ts-expect-error unknown emitted event name
|
|
42
|
+
store.on("bogusEmit", () => {})
|
|
43
|
+
|
|
44
|
+
// --- Wrong payloads must NOT compile --------------------------------------
|
|
45
|
+
// @ts-expect-error reason must be an OverlayCloseReason
|
|
46
|
+
store.trigger.closedTop({ reason: 42 })
|
|
47
|
+
// @ts-expect-error record is required
|
|
48
|
+
store.trigger.opened({})
|
|
49
|
+
}
|