@lerx/promise-modal 0.12.2 → 0.12.3

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/README.md CHANGED
@@ -793,11 +793,13 @@ Opens a prompt modal to receive input from the user.
793
793
  - props: `{ value: T; onChange: (value: T) => void }`
794
794
  - `defaultValue?`: Default value (T)
795
795
  - `disabled?`: Function to determine if confirm button should be disabled
796
- - `(value: T) => boolean`
797
- - `returnOnCancel?`: Whether to return default value on cancel (boolean)
796
+ - `(value: T | undefined) => boolean`
797
+ - `returnOnCancel?`: If true, cancel resolves with the current input value instead of null (boolean)
798
798
  - `footer?`: Footer settings (similar to confirm)
799
799
 
800
- **Returns:** `Promise<T>` - Resolves to the input value
800
+ **Returns:** `Promise<T | null>` - Resolves to the input value on confirm, or null on cancel
801
+
802
+ > **Upgrade note:** `prompt()` now types cancellation explicitly — the return is `Promise<T | null>` and the `disabled` callback receives `T | undefined`. Consumers that previously annotated these as non-null may hit new TypeScript errors on upgrade; the runtime behaviour (cancel resolves `null`) is unchanged.
801
803
 
802
804
  ```typescript
803
805
  // Example
@@ -15,8 +15,17 @@ export declare class ModalManager {
15
15
  static get anchored(): boolean;
16
16
  private static __prerenderList__;
17
17
  static get prerender(): Modal[];
18
+ private static __defaultOpenHandler__;
18
19
  private static __openHandler__;
20
+ /**
21
+ * Registers the React-side open handler, then flushes the prerender queue
22
+ * through it so modals opened before mount keep their promise wiring alive.
23
+ */
19
24
  static set openHandler(handler: Fn<[Modal], ModalNode>);
25
+ /** Attaches a flush-time dispatcher to a queued modal; no-op if not queued. */
26
+ static bindPrerender(modal: Modal, dispatch: Fn): void;
27
+ /** Removes a queued modal (pre-mount abort). Returns whether it was queued. */
28
+ static cancelPrerender(modal: Modal): boolean;
20
29
  private static __refreshHandler__?;
21
30
  static set refreshHandler(handler: Fn<[], void>);
22
31
  static refresh(): void;
@@ -24,5 +33,5 @@ export declare class ModalManager {
24
33
  static applyStyleSheet(): void;
25
34
  static getHashedClassNames(styleId: string): string;
26
35
  static reset(): void;
27
- static open(modal: Modal): ModalNode;
36
+ static open(modal: Modal): ModalNode | undefined;
28
37
  }
@@ -0,0 +1,2 @@
1
+ export { ModalManager } from './ModalManager.js';
2
+ export { DEFAULT_ANIMATION_DURATION, DEFAULT_BACKDROP_COLOR, DEFAULT_Z_INDEX, } from './constant.js';
@@ -96,33 +96,6 @@ import type { BootstrapProviderProps } from './type.js';
96
96
  * ```
97
97
  *
98
98
  * @example
99
- * Multiple modal systems:
100
- * ```tsx
101
- * function MultiModalApp() {
102
- * // System modals (errors, confirmations)
103
- * const { portal: systemPortal } = useBootstrap({
104
- * ForegroundComponent: SystemModal,
105
- * options: { backdrop: 'rgba(255, 0, 0, 0.8)' },
106
- * });
107
- *
108
- * // Feature modals (forms, wizards)
109
- * const { portal: featurePortal } = useBootstrap({
110
- * ForegroundComponent: FeatureModal,
111
- * options: { backdrop: 'rgba(0, 0, 255, 0.8)' },
112
- * });
113
- *
114
- * return (
115
- * <>
116
- * <SystemSection />
117
- * <FeatureSection />
118
- * {systemPortal}
119
- * {featurePortal}
120
- * </>
121
- * );
122
- * }
123
- * ```
124
- *
125
- * @example
126
99
  * With custom hooks for specific modal types:
127
100
  * ```tsx
128
101
  * function useConfirmDialog() {
@@ -178,7 +151,8 @@ import type { BootstrapProviderProps } from './type.js';
178
151
  * @remarks
179
152
  * - Use `mode: 'manual'` when you need to control the initialization timing
180
153
  * - The portal element must be rendered in your component tree
181
- * - Each hook instance creates an independent modal system
154
+ * - The modal system is a singleton: mount exactly one bootstrap
155
+ * (ModalProvider or useBootstrap) at a time
182
156
  * - Context changes will affect all modals created after the change
183
157
  */
184
158
  export declare const useBootstrap: ({ usePathname: useExternalPathname, ForegroundComponent, BackgroundComponent, TitleComponent, SubtitleComponent, ContentComponent, FooterComponent, options, context, mode, }?: BootstrapProviderProps & {
@@ -2,15 +2,15 @@
2
2
  "schemaVersion": 1,
3
3
  "package": {
4
4
  "name": "@lerx/promise-modal",
5
- "version": "0.12.2"
5
+ "version": "0.12.3"
6
6
  },
7
- "generatedAt": "2026-04-24T16:37:00.386Z",
7
+ "generatedAt": "2026-07-05T18:39:01.047Z",
8
8
  "algorithm": "sha256",
9
9
  "assetRoot": "docs/claude",
10
10
  "files": {
11
11
  "skills/promise-modal-skill/SKILL.md": "b424c979b9e969e11ba8fa0a56afc3cbf1793c822f06509e6b730068f384679e",
12
12
  "skills/promise-modal-skill/knowledge/advanced-patterns.md": "2e2c29449a46b1336727f8a1d5f1eede4e99639675dbe819edd2bf4243acfaab",
13
- "skills/promise-modal-skill/knowledge/api-reference.md": "1df36fa5b832d2349c05dacaec913876f7c30fad6478fcae64c8a4bd65bc023a",
13
+ "skills/promise-modal-skill/knowledge/api-reference.md": "b54ae4dcd0791ebfce8110a69af615e712bfdcbb5be5b57f725cd4bfb12ab50f",
14
14
  "skills/promise-modal-skill/knowledge/hooks-reference.md": "b0137caf257ad9712cfa7ab44c908041bbbc61bf355fa0180925a35479e47516",
15
15
  "skills/promise-modal-skill/knowledge/type-definitions.md": "fde17b7f99d3e65b7e6c16abf4689af0a9e41a8a06dfeb9482b94a7611841d50"
16
16
  },
@@ -8,7 +8,8 @@ import type { AlertProps } from './type.js';
8
8
  * @returns Object containing modalNode and promiseHandler
9
9
  *
10
10
  * @remarks
11
- * - modalNode: The created modal node instance
11
+ * - modalNode: live getter for the modal node; undefined while the modal is
12
+ * queued before the ModalProvider mounts, then set once the queue flushes
12
13
  * - promiseHandler: Promise that resolves when the modal is closed
13
14
  *
14
15
  * @example
@@ -22,6 +23,7 @@ import type { AlertProps } from './type.js';
22
23
  * ```
23
24
  */
24
25
  export declare const alertHandler: <BackgroundValue = any>(args: AlertProps<BackgroundValue>) => {
25
- readonly modalNode: AlertNode<BackgroundValue>;
26
- readonly promiseHandler: Promise<void>;
26
+ readonly modalNode: AlertNode<BackgroundValue> | undefined;
27
+ promiseHandler: Promise<void>;
28
+ cancel: import("../../@aileron/declare").Fn<[], void>;
27
29
  };
@@ -8,10 +8,11 @@ import type { ConfirmProps } from './type.js';
8
8
  * @returns Object containing modalNode and promiseHandler
9
9
  *
10
10
  * @remarks
11
- * - modalNode: The created modal node instance
11
+ * - modalNode: live getter for the modal node; undefined while the modal is
12
+ * queued before the ModalProvider mounts, then set once the queue flushes
12
13
  * - promiseHandler: Promise that resolves to true if confirmed, false if cancelled
13
14
  * - Returns true only when explicitly confirmed via the confirm action
14
- * - Returns false for cancel action or backdrop click (if enabled)
15
+ * - Returns false for cancel action, backdrop click (if enabled), or abort
15
16
  *
16
17
  * @example
17
18
  * ```tsx
@@ -27,6 +28,7 @@ import type { ConfirmProps } from './type.js';
27
28
  * ```
28
29
  */
29
30
  export declare const confirmHandler: <BackgroundValue = any>(args: ConfirmProps<BackgroundValue>) => {
30
- readonly modalNode: ConfirmNode<BackgroundValue>;
31
- readonly promiseHandler: Promise<boolean>;
31
+ readonly modalNode: ConfirmNode<BackgroundValue> | undefined;
32
+ promiseHandler: Promise<boolean>;
33
+ cancel: import("../../@aileron/declare").Fn<[], void>;
32
34
  };
@@ -0,0 +1,29 @@
1
+ import type { Fn } from '../../@aileron/declare/index.js';
2
+ import type { ModalNode } from '../../core/node/index.js';
3
+ import type { Modal } from '../../types/index.js';
4
+ interface DispatchModalOptions<Result> {
5
+ /** Abort signal that cancels the modal (before or after mount). */
6
+ signal?: AbortSignal;
7
+ /** Maps the node's raw resolve payload to the public promise result. */
8
+ mapResult: Fn<[result: unknown], Result>;
9
+ /** Raw payload used when the modal is aborted before React ever mounted. */
10
+ cancelResult: Fn<[], unknown>;
11
+ }
12
+ /**
13
+ * Opens a modal through ModalManager and wires its promise settlement.
14
+ *
15
+ * The resolver travels inside the modal data (`handleResolve`), so the wiring
16
+ * survives the prerender queue: modals opened before the provider mounts are
17
+ * flushed later and still settle the very same promise.
18
+ *
19
+ * @returns modalNode - live getter: undefined while the modal is queued before
20
+ * mount, then the node created by the flush (late reads see the real node)
21
+ * @returns promiseHandler - settles on user interaction or abort; rejects only
22
+ * when the open handler itself throws
23
+ */
24
+ export declare const dispatchModal: <Node extends ModalNode, Result>(modal: Modal, { signal, mapResult, cancelResult }: DispatchModalOptions<Result>) => {
25
+ readonly modalNode: Node | undefined;
26
+ promiseHandler: Promise<Result>;
27
+ cancel: Fn<[], void>;
28
+ };
29
+ export {};
@@ -2,4 +2,4 @@ export { alertHandler } from './alert.js';
2
2
  export { confirmHandler } from './confirm.js';
3
3
  export { promptHandler } from './prompt.js';
4
4
  export { alert, confirm, prompt } from './static.js';
5
- export type { AlertProps, ConfirmProps, PromptProps } from './type.js';
5
+ export type { AlertProps, ConfirmProps, OverridableHandleProps, PromptProps, } from './type.js';
@@ -9,12 +9,14 @@ import type { PromptProps } from './type.js';
9
9
  * @returns Object containing modalNode and promiseHandler
10
10
  *
11
11
  * @remarks
12
- * - modalNode: The created modal node instance
13
- * - promiseHandler: Promise that resolves with user input value
12
+ * - modalNode: live getter for the modal node; undefined while the modal is
13
+ * queued before the ModalProvider mounts, then set once the queue flushes
14
+ * - promiseHandler: Promise that resolves with the user input value
14
15
  * - Input component receives value, onChange, onConfirm, onCancel, and context props
15
16
  * - Use disabled function to control confirm button's enabled state
16
- * - If returnOnCancel is false (default), promise rejects on cancel
17
- * - If returnOnCancel is true, returns defaultValue on cancel
17
+ * - If returnOnCancel is false (default), the promise resolves with null on cancel
18
+ * - If returnOnCancel is true, resolves with the input value at cancel time
19
+ * (initially defaultValue)
18
20
  *
19
21
  * @example
20
22
  * ```tsx
@@ -31,15 +33,12 @@ import type { PromptProps } from './type.js';
31
33
  * defaultValue: '',
32
34
  * });
33
35
  *
34
- * try {
35
- * const name = await promiseHandler;
36
- * console.log('User entered:', name);
37
- * } catch {
38
- * console.log('User cancelled');
39
- * }
36
+ * const name = await promiseHandler;
37
+ * if (name === null) console.log('User cancelled');
40
38
  * ```
41
39
  */
42
40
  export declare const promptHandler: <InputValue, BackgroundValue = any>(args: PromptProps<InputValue, BackgroundValue>) => {
43
- readonly modalNode: PromptNode<InputValue, BackgroundValue>;
44
- readonly promiseHandler: Promise<InputValue>;
41
+ readonly modalNode: PromptNode<InputValue, BackgroundValue> | undefined;
42
+ promiseHandler: Promise<InputValue | null>;
43
+ cancel: import("../../@aileron/declare").Fn<[], void>;
45
44
  };
@@ -66,8 +66,9 @@ export declare const confirm: <BackgroundValue = any>(args: ConfirmProps<Backgro
66
66
  * @typeParam InputValue - Type of the value collected from user input
67
67
  * @typeParam BackgroundValue - Type of background data passed to BackgroundComponent
68
68
  * @param args - Prompt modal configuration options
69
- * @returns Promise that resolves with user input value
70
- * @throws Rejects when cancelled (unless returnOnCancel is true)
69
+ * @returns Promise that resolves with the user input value on confirm, or null
70
+ * on cancel. With returnOnCancel: true, cancel resolves with the input value
71
+ * at cancel time (initially defaultValue) instead of null.
71
72
  *
72
73
  * @example
73
74
  * ```tsx
@@ -84,6 +85,9 @@ export declare const confirm: <BackgroundValue = any>(args: ConfirmProps<Backgro
84
85
  * ),
85
86
  * defaultValue: 'John Doe',
86
87
  * });
88
+ * if (name === null) {
89
+ * console.log('User cancelled');
90
+ * }
87
91
  * ```
88
92
  *
89
93
  * @example
@@ -102,4 +106,4 @@ export declare const confirm: <BackgroundValue = any>(args: ConfirmProps<Backgro
102
106
  * });
103
107
  * ```
104
108
  */
105
- export declare const prompt: <InputValue, BackgroundValue = any>(args: PromptProps<InputValue, BackgroundValue>) => Promise<InputValue>;
109
+ export declare const prompt: <InputValue, BackgroundValue = any>(args: PromptProps<InputValue, BackgroundValue>) => Promise<InputValue | null>;
@@ -44,9 +44,13 @@ export interface PromptProps<InputValue, BackgroundValue = any> extends BaseHand
44
44
  defaultValue?: InputValue;
45
45
  /** Input component that receives value, onChange, and other props */
46
46
  Input: Fn<[props: PromptInputProps<InputValue>], ReactNode>;
47
- /** Validation function. Returns true to disable the confirm button */
48
- disabled?: Fn<[value: InputValue], boolean>;
49
- /** If true, returns defaultValue instead of rejecting on cancel */
47
+ /**
48
+ * Validation function. Returns true to disable the confirm button.
49
+ * Called with the current value, which stays undefined until the first
50
+ * input when no defaultValue is provided.
51
+ */
52
+ disabled?: Fn<[value: InputValue | undefined], boolean>;
53
+ /** If true, cancel resolves with the current input value instead of null */
50
54
  returnOnCancel?: boolean;
51
55
  /** Footer configuration. Can be a render function, options object, or false */
52
56
  footer?: PromptFooterRender<InputValue> | FooterOptions | false;
@@ -8,7 +8,7 @@ export declare class PromptNode<T = any, B = any> extends AbstractNode<T, B> {
8
8
  readonly content?: ReactNode | ComponentType<PromptContentProps>;
9
9
  readonly defaultValue: T | undefined;
10
10
  readonly Input: Fn<[props: PromptInputProps<T>], ReactNode>;
11
- readonly disabled?: Fn<[value: T], boolean>;
11
+ readonly disabled?: Fn<[value: T | undefined], boolean>;
12
12
  readonly returnOnCancel?: boolean;
13
13
  readonly footer?: PromptFooterRender<T> | FooterOptions | false;
14
14
  private __value__;
@@ -0,0 +1,2 @@
1
+ import type { ModalNode } from '../core/index.js';
2
+ export declare const hideModal: (modalNode: ModalNode, refresh?: boolean) => NodeJS.Timeout | undefined;
@@ -1,7 +1,6 @@
1
- import { type AlertProps, type ConfirmProps, type PromptProps } from '../core/index.js';
2
- import type { OverridableHandleProps } from '../core/handle/type.js';
1
+ import { type AlertProps, type ConfirmProps, type OverridableHandleProps, type PromptProps } from '../core/index.js';
3
2
  export declare const useModal: (configuration?: OverridableHandleProps) => {
4
3
  readonly alert: <Background = any>(args: AlertProps<Background>) => Promise<void>;
5
4
  readonly confirm: <Background = any>(args: ConfirmProps<Background>) => Promise<boolean>;
6
- readonly prompt: <Value, Background = any>(args: PromptProps<Value, Background>) => Promise<Value>;
5
+ readonly prompt: <Value, Background = any>(args: PromptProps<Value, Background>) => Promise<Value | null>;
7
6
  };