@rokku-x/react-hook-dialog 1.0.4 → 1.2.0

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
@@ -1,38 +1,46 @@
1
1
  # react-hook-dialog
2
2
 
3
- [![CI](https://github.com/rokku-x/react-hook-dialog/actions/workflows/ci.yml/badge.svg)](https://github.com/rokku-x/react-hook-dialog/actions/workflows/ci.yml)
3
+ [![CI](https://github.com/rokku-x/react-hook-dialog/actions/workflows/ci.yml/badge.svg)](https://github.com/rokku-x/react-hook-dialog/actions/workflows/ci.yml) [![npm version](https://img.shields.io/npm/v/@rokku-x/react-hook-dialog.svg)](https://www.npmjs.com/package/@rokku-x/react-hook-dialog) [![license](https://img.shields.io/npm/l/@rokku-x/react-hook-dialog.svg)](https://www.npmjs.com/package/@rokku-x/react-hook-dialog) [![downloads](https://img.shields.io/npm/dm/@rokku-x/react-hook-dialog.svg)](https://www.npmjs.com/package/@rokku-x/react-hook-dialog) ![TS](https://img.shields.io/badge/TS-%E2%9C%93-blue)
4
4
 
5
- A powerful and flexible React dialog hook library for confirmation dialogs, alerts, and modals. Built on top of `@rokku-x/react-hook-modal` with a focus on dialog-specific features like action buttons, variants, and customizable styling.
5
+ A lightweight, powerful, and flexible React dialog hook library for confirmation dialogs, alerts, and modals. Built on top of `@rokku-x/react-hook-modal` with a focus on dialog-specific features like action buttons, variants, and customizable styling.
6
6
 
7
7
  ## Features
8
8
 
9
+ - ♿ **Accessibility Focused** - Keyboard navigation and ARIA support
10
+ - 🔄 **Asynchronous** - Async/await friendly dialog results
9
11
  - 🎯 **Hook-based API** - Simple and intuitive `useHookDialog()` hook
10
12
  - 🎨 **Rich Variants** - 7 button variants (primary, secondary, danger, success, warning, info, neutral)
11
- - 🧩 **Modular Components** - Composed from reusable Backdrop and DialogWindow components
12
13
  - 📝 **Dialog Actions** - Flexible action button system with left/right positioning
13
14
  - 💅 **Full Customization** - Injectable className and styles at every level
14
15
  - ⌨️ **Rich Configuration** - Default configs with per-call overrides
15
- - 🎁 **Zero Dependencies** - Only requires React, Zustand, and @rokku-x/react-hook-modal
16
16
  - 📱 **TypeScript Support** - Full type safety out of the box
17
- - **Backdrop Control** - Configurable backdrop click behavior
17
+ - 🧑‍🤝‍🧑 **Multiple Dialogs** - Support for multiple simultaneous dialogs
18
+ - 🛠️ **Programmatic Control** - Force actions and cancellations via dialog context
19
+ - 🖼️ **Rich Content Support** - Accepts React nodes for titles and content
20
+ - 📦 **Lightweight** - Minimal bundle size for fast load times
18
21
 
19
22
  ## Installation
20
23
 
21
24
  ```bash
22
25
  npm install @rokku-x/react-hook-dialog
23
- ```
24
-
25
- or with yarn:
26
-
27
- ```bash
26
+ # or
27
+ bun add @rokku-x/react-hook-dialog
28
+ # or
28
29
  yarn add @rokku-x/react-hook-dialog
30
+ # or
31
+ pnpm add @rokku-x/react-hook-dialog
29
32
  ```
30
33
 
31
34
  ## Quick Start
32
35
 
33
- ### 1. Setup BaseModalRenderer
36
+ ### 1. Setup BaseModalRenderer or BaseDialogRenderer
37
+
38
+ You can either use the upstream `BaseModalRenderer` directly (from `@rokku-x/react-hook-modal`) or use the convenience wrapper `BaseDialogRenderer` provided by this package.
39
+
40
+ - `BaseModalRenderer` (upstream): mount this at the root to render modal instances.
41
+ - `BaseDialogRenderer` (this package): a thin wrapper around `BaseModalRenderer` that lets you pass a `defaultConfig` prop to set default dialog options for all dialogs created by `useHookDialog()`.
34
42
 
35
- First, add the `BaseModalRenderer` at the root of your application (from `@rokku-x/react-hook-modal`):
43
+ Use `BaseModalRenderer` directly:
36
44
 
37
45
  ```tsx
38
46
  import { BaseModalRenderer } from '@rokku-x/react-hook-modal';
@@ -47,6 +55,23 @@ function App() {
47
55
  }
48
56
  ```
49
57
 
58
+ Or use the wrapper `BaseDialogRenderer` from this package to set defaults:
59
+
60
+ ```tsx
61
+ import { BaseDialogRenderer } from '@rokku-x/react-hook-dialog';
62
+
63
+ function App() {
64
+ return (
65
+ <>
66
+ <YourComponents />
67
+ <BaseDialogRenderer defaultConfig={{ showCloseButton: false, backdropCancel: false }} />
68
+ </>
69
+ );
70
+ }
71
+ ```
72
+
73
+ Either component is fine — `BaseDialogRenderer` simply sets the dialog defaults for you so consumers of `useHookDialog()` don't have to provide them on each call.
74
+
50
75
  ### 2. Use useHookDialog Hook
51
76
 
52
77
  ```tsx
@@ -91,13 +116,62 @@ useHookDialog(defaultConfig?: UseHookDialogConfig)
91
116
  #### Returns
92
117
 
93
118
  ```typescript
94
- [requestDialog]
119
+ [requestDialog, getContext]
95
120
  ```
96
121
 
97
122
  | Return Value | Type | Description |
98
123
  |---|---|---|
99
- | `requestDialog` | `(config: ConfirmConfig) => Promise<ValidValue>` | Function to open a dialog and get user response |
124
+ | `requestDialog` | `(config: ConfirmConfig) => RequestDialogReturnType<ValidValue>` | Open a dialog and receive a result. The return value is an **augmented Promise** (see `RequestDialogReturnType<T>`). |
125
+ | `getContext` | `(id: string) => DialogInstanceContext` | Retrieve the runtime context for an open dialog by its `id` (useful if you only have the `id`). |
126
+
127
+ ```typescript
128
+ // Augmented promise returned by `requestDialog`
129
+ type RequestDialogReturnType<T> = Promise<T> & { id: string; context: DialogInstanceContext };
130
+ ```
131
+
132
+ > Important: awaiting the returned Promise resolves with the dialog result (`T`). The `id` and `context` properties are available immediately after calling `requestDialog(...)`, allowing programmatic control while the dialog is open.
133
+
134
+ ### Dialog Context & Force Functions ✅
135
+
136
+ The augmented Promise exposes two helpers:
137
+
138
+ - `id: string` — unique identifier for the dialog instance
139
+ - `context: DialogInstanceContext` — runtime control helpers
140
+
141
+ `DialogInstanceContext` methods:
142
+
143
+ | Method | Signature | Description |
144
+ |---|---|---|
145
+ | `forceCancel` | `forceCancel(forceReject?: boolean = true): void` | Close the dialog as a cancellation. **Default:** `forceReject = true` (the promise will be rejected by default). If set to `false`, the dialog follows the dialog's `rejectOnCancel` setting. |
146
+ | `forceAction` | `forceAction(action: ModalAction): void` | Programmatically trigger the specified action (resolves/rejects according to the action and dialog config). |
147
+ | `forceDefault` | `forceDefault(): void` | Trigger the dialog's default action (first action marked with `isFocused`). Throws if no default is defined. |
148
+
149
+ Quick example:
150
+
151
+ ```tsx
152
+ const [requestDialog, getContext] = useHookDialog();
153
+
154
+ const actions: ModalAction[][] = [[ { title: 'Cancel', isCancel: true }, { title: 'OK', value: true, isFocused: true } ]];
155
+
156
+ const p = requestDialog({
157
+ title: 'Confirm',
158
+ content: 'Proceed?',
159
+ actions
160
+ });
100
161
 
162
+ // id available immediately
163
+ console.log('dialog id:', p.id);
164
+
165
+ // cancel programmatically (rejects by default)
166
+ p.context.forceCancel();
167
+
168
+ // trigger a specific action by referencing the action in the array
169
+ getContext(p.id).forceAction(actions[0][1]); // triggers `okAction`
170
+ // or
171
+ p.context.forceAction(actions[0][0]); // triggers `cancelAction`
172
+ ```
173
+
174
+ > Tip: use `forceAction(...)` when you want to trigger a specific action object. Use `forceDefault()` to trigger the focused/default action (if defined). The returned Promise still resolves with the action's `value` (or rejects when cancelled).
101
175
  #### Default Config Options
102
176
 
103
177
  | Property | Type | Default | Description |
@@ -213,6 +287,18 @@ Customize inline styles for all elements:
213
287
 
214
288
  ## Components
215
289
 
290
+ ### BaseDialogRenderer 🔧
291
+
292
+ A convenience wrapper around the upstream `BaseModalRenderer` that accepts a `defaultConfig` prop allowing you to specify default `UseHookDialogConfig` for all dialogs created by `useHookDialog()`.
293
+
294
+ ```tsx
295
+ import { BaseDialogRenderer } from '@rokku-x/react-hook-dialog';
296
+
297
+ <BaseDialogRenderer defaultConfig={{ showCloseButton: false, backdropCancel: false }} />
298
+ ```
299
+
300
+ > Note: `BaseModalRenderer` from `@rokku-x/react-hook-modal` can still be used directly if you prefer — this wrapper only adds `defaultConfig` convenience.
301
+
216
302
  ### Backdrop
217
303
 
218
304
  Overlay component that wraps dialog windows.
@@ -726,10 +812,10 @@ Custom styles for each variant type.
726
812
 
727
813
  ## Bundle Size
728
814
 
729
- - ESM: ~4.06 kB gzipped (13.48 kB raw)
730
- - CJS: ~3.48 kB gzipped (9.21 kB raw)
815
+ - ESM : ~6.69 kB gzipped (13.10 kB raw)
816
+ - CJS : ~7.35 kB gzipped (14.14 kB raw)
731
817
 
732
- Measured with Vite build for v0.0.1.
818
+ Measured with Vite build for the current branch.
733
819
 
734
820
  ## License
735
821
 
package/dist/README.md CHANGED
@@ -1,38 +1,46 @@
1
1
  # react-hook-dialog
2
2
 
3
- [![CI](https://github.com/rokku-x/react-hook-dialog/actions/workflows/ci.yml/badge.svg)](https://github.com/rokku-x/react-hook-dialog/actions/workflows/ci.yml)
3
+ [![CI](https://github.com/rokku-x/react-hook-dialog/actions/workflows/ci.yml/badge.svg)](https://github.com/rokku-x/react-hook-dialog/actions/workflows/ci.yml) [![npm version](https://img.shields.io/npm/v/@rokku-x/react-hook-dialog.svg)](https://www.npmjs.com/package/@rokku-x/react-hook-dialog) [![license](https://img.shields.io/npm/l/@rokku-x/react-hook-dialog.svg)](https://www.npmjs.com/package/@rokku-x/react-hook-dialog) [![downloads](https://img.shields.io/npm/dm/@rokku-x/react-hook-dialog.svg)](https://www.npmjs.com/package/@rokku-x/react-hook-dialog) ![TS](https://img.shields.io/badge/TS-%E2%9C%93-blue)
4
4
 
5
- A powerful and flexible React dialog hook library for confirmation dialogs, alerts, and modals. Built on top of `@rokku-x/react-hook-modal` with a focus on dialog-specific features like action buttons, variants, and customizable styling.
5
+ A lightweight, powerful, and flexible React dialog hook library for confirmation dialogs, alerts, and modals. Built on top of `@rokku-x/react-hook-modal` with a focus on dialog-specific features like action buttons, variants, and customizable styling.
6
6
 
7
7
  ## Features
8
8
 
9
+ - ♿ **Accessibility Focused** - Keyboard navigation and ARIA support
10
+ - 🔄 **Asynchronous** - Async/await friendly dialog results
9
11
  - 🎯 **Hook-based API** - Simple and intuitive `useHookDialog()` hook
10
12
  - 🎨 **Rich Variants** - 7 button variants (primary, secondary, danger, success, warning, info, neutral)
11
- - 🧩 **Modular Components** - Composed from reusable Backdrop and DialogWindow components
12
13
  - 📝 **Dialog Actions** - Flexible action button system with left/right positioning
13
14
  - 💅 **Full Customization** - Injectable className and styles at every level
14
15
  - ⌨️ **Rich Configuration** - Default configs with per-call overrides
15
- - 🎁 **Zero Dependencies** - Only requires React, Zustand, and @rokku-x/react-hook-modal
16
16
  - 📱 **TypeScript Support** - Full type safety out of the box
17
- - **Backdrop Control** - Configurable backdrop click behavior
17
+ - 🧑‍🤝‍🧑 **Multiple Dialogs** - Support for multiple simultaneous dialogs
18
+ - 🛠️ **Programmatic Control** - Force actions and cancellations via dialog context
19
+ - 🖼️ **Rich Content Support** - Accepts React nodes for titles and content
20
+ - 📦 **Lightweight** - Minimal bundle size for fast load times
18
21
 
19
22
  ## Installation
20
23
 
21
24
  ```bash
22
25
  npm install @rokku-x/react-hook-dialog
23
- ```
24
-
25
- or with yarn:
26
-
27
- ```bash
26
+ # or
27
+ bun add @rokku-x/react-hook-dialog
28
+ # or
28
29
  yarn add @rokku-x/react-hook-dialog
30
+ # or
31
+ pnpm add @rokku-x/react-hook-dialog
29
32
  ```
30
33
 
31
34
  ## Quick Start
32
35
 
33
- ### 1. Setup BaseModalRenderer
36
+ ### 1. Setup BaseModalRenderer or BaseDialogRenderer
37
+
38
+ You can either use the upstream `BaseModalRenderer` directly (from `@rokku-x/react-hook-modal`) or use the convenience wrapper `BaseDialogRenderer` provided by this package.
39
+
40
+ - `BaseModalRenderer` (upstream): mount this at the root to render modal instances.
41
+ - `BaseDialogRenderer` (this package): a thin wrapper around `BaseModalRenderer` that lets you pass a `defaultConfig` prop to set default dialog options for all dialogs created by `useHookDialog()`.
34
42
 
35
- First, add the `BaseModalRenderer` at the root of your application (from `@rokku-x/react-hook-modal`):
43
+ Use `BaseModalRenderer` directly:
36
44
 
37
45
  ```tsx
38
46
  import { BaseModalRenderer } from '@rokku-x/react-hook-modal';
@@ -47,6 +55,23 @@ function App() {
47
55
  }
48
56
  ```
49
57
 
58
+ Or use the wrapper `BaseDialogRenderer` from this package to set defaults:
59
+
60
+ ```tsx
61
+ import { BaseDialogRenderer } from '@rokku-x/react-hook-dialog';
62
+
63
+ function App() {
64
+ return (
65
+ <>
66
+ <YourComponents />
67
+ <BaseDialogRenderer defaultConfig={{ showCloseButton: false, backdropCancel: false }} />
68
+ </>
69
+ );
70
+ }
71
+ ```
72
+
73
+ Either component is fine — `BaseDialogRenderer` simply sets the dialog defaults for you so consumers of `useHookDialog()` don't have to provide them on each call.
74
+
50
75
  ### 2. Use useHookDialog Hook
51
76
 
52
77
  ```tsx
@@ -91,13 +116,62 @@ useHookDialog(defaultConfig?: UseHookDialogConfig)
91
116
  #### Returns
92
117
 
93
118
  ```typescript
94
- [requestDialog]
119
+ [requestDialog, getContext]
95
120
  ```
96
121
 
97
122
  | Return Value | Type | Description |
98
123
  |---|---|---|
99
- | `requestDialog` | `(config: ConfirmConfig) => Promise<ValidValue>` | Function to open a dialog and get user response |
124
+ | `requestDialog` | `(config: ConfirmConfig) => RequestDialogReturnType<ValidValue>` | Open a dialog and receive a result. The return value is an **augmented Promise** (see `RequestDialogReturnType<T>`). |
125
+ | `getContext` | `(id: string) => DialogInstanceContext` | Retrieve the runtime context for an open dialog by its `id` (useful if you only have the `id`). |
126
+
127
+ ```typescript
128
+ // Augmented promise returned by `requestDialog`
129
+ type RequestDialogReturnType<T> = Promise<T> & { id: string; context: DialogInstanceContext };
130
+ ```
131
+
132
+ > Important: awaiting the returned Promise resolves with the dialog result (`T`). The `id` and `context` properties are available immediately after calling `requestDialog(...)`, allowing programmatic control while the dialog is open.
133
+
134
+ ### Dialog Context & Force Functions ✅
135
+
136
+ The augmented Promise exposes two helpers:
137
+
138
+ - `id: string` — unique identifier for the dialog instance
139
+ - `context: DialogInstanceContext` — runtime control helpers
140
+
141
+ `DialogInstanceContext` methods:
142
+
143
+ | Method | Signature | Description |
144
+ |---|---|---|
145
+ | `forceCancel` | `forceCancel(forceReject?: boolean = true): void` | Close the dialog as a cancellation. **Default:** `forceReject = true` (the promise will be rejected by default). If set to `false`, the dialog follows the dialog's `rejectOnCancel` setting. |
146
+ | `forceAction` | `forceAction(action: ModalAction): void` | Programmatically trigger the specified action (resolves/rejects according to the action and dialog config). |
147
+ | `forceDefault` | `forceDefault(): void` | Trigger the dialog's default action (first action marked with `isFocused`). Throws if no default is defined. |
148
+
149
+ Quick example:
150
+
151
+ ```tsx
152
+ const [requestDialog, getContext] = useHookDialog();
153
+
154
+ const actions: ModalAction[][] = [[ { title: 'Cancel', isCancel: true }, { title: 'OK', value: true, isFocused: true } ]];
155
+
156
+ const p = requestDialog({
157
+ title: 'Confirm',
158
+ content: 'Proceed?',
159
+ actions
160
+ });
100
161
 
162
+ // id available immediately
163
+ console.log('dialog id:', p.id);
164
+
165
+ // cancel programmatically (rejects by default)
166
+ p.context.forceCancel();
167
+
168
+ // trigger a specific action by referencing the action in the array
169
+ getContext(p.id).forceAction(actions[0][1]); // triggers `okAction`
170
+ // or
171
+ p.context.forceAction(actions[0][0]); // triggers `cancelAction`
172
+ ```
173
+
174
+ > Tip: use `forceAction(...)` when you want to trigger a specific action object. Use `forceDefault()` to trigger the focused/default action (if defined). The returned Promise still resolves with the action's `value` (or rejects when cancelled).
101
175
  #### Default Config Options
102
176
 
103
177
  | Property | Type | Default | Description |
@@ -213,6 +287,18 @@ Customize inline styles for all elements:
213
287
 
214
288
  ## Components
215
289
 
290
+ ### BaseDialogRenderer 🔧
291
+
292
+ A convenience wrapper around the upstream `BaseModalRenderer` that accepts a `defaultConfig` prop allowing you to specify default `UseHookDialogConfig` for all dialogs created by `useHookDialog()`.
293
+
294
+ ```tsx
295
+ import { BaseDialogRenderer } from '@rokku-x/react-hook-dialog';
296
+
297
+ <BaseDialogRenderer defaultConfig={{ showCloseButton: false, backdropCancel: false }} />
298
+ ```
299
+
300
+ > Note: `BaseModalRenderer` from `@rokku-x/react-hook-modal` can still be used directly if you prefer — this wrapper only adds `defaultConfig` convenience.
301
+
216
302
  ### Backdrop
217
303
 
218
304
  Overlay component that wraps dialog windows.
@@ -726,10 +812,10 @@ Custom styles for each variant type.
726
812
 
727
813
  ## Bundle Size
728
814
 
729
- - ESM: ~4.06 kB gzipped (13.48 kB raw)
730
- - CJS: ~3.48 kB gzipped (9.21 kB raw)
815
+ - ESM : ~6.69 kB gzipped (13.10 kB raw)
816
+ - CJS : ~7.35 kB gzipped (14.14 kB raw)
731
817
 
732
- Measured with Vite build for v0.0.1.
818
+ Measured with Vite build for the current branch.
733
819
 
734
820
  ## License
735
821
 
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("react/jsx-runtime"),r=require("../store/dialog.cjs.js"),t=require("@rokku-x/react-hook-modal"),o=require("react");exports.default=function({defaultConfig:u,...s}){const{setDefaultConfig:a}=r.default();return o.useEffect(()=>{a(u||{})},[u]),e.jsx(t.BaseModalRenderer,{...s})};
@@ -0,0 +1,13 @@
1
+ import { UseHookDialogConfig } from '../types';
2
+ import { ModalWindowProps } from '@rokku-x/react-hook-modal';
3
+ /**
4
+ * Wrapper component around the BaseModalRenderer to set default dialog configuration.
5
+ * @param props - Component props
6
+ * @param props.defaultConfig - Default configuration applied to all dialogs rendered by this component
7
+ * @param rest - Other props passed to BaseModalRenderer
8
+ *
9
+ * @internal
10
+ */
11
+ export default function BaseDialogRenderer({ defaultConfig, ...rest }: ModalWindowProps & {
12
+ defaultConfig?: UseHookDialogConfig;
13
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1 @@
1
+ import{jsx as o}from"react/jsx-runtime";import r from"../store/dialog.esm.js";import{BaseModalRenderer as t}from"@rokku-x/react-hook-modal";import{useEffect as e}from"react";function m({defaultConfig:m,...f}){const{setDefaultConfig:a}=r();return e(()=>{a(m||{})},[m]),/* @__PURE__ */o(t,{...f})}export{m as default};
@@ -1 +1 @@
1
- "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("react/jsx-runtime"),t=require("../constants/theme.cjs.js"),o=require("../utils/utils.cjs.js"),n=require("@rokku-x/react-hook-modal"),i=require("react"),a=n.ModalWindow;exports.default=function({modalWindowId:s,handleAction:l,handleClose:r,config:c}){const{actions:u=[],title:d,backdropCancel:f,showCloseButton:m,classNames:p={},styles:h={},variantStyles:g={}}=c;let{content:y}=c;const b=(u.length?u:[[{title:"OK",variant:"primary"}]]).filter(e=>e&&e.length),x={...t.baseVariantStyles,...g},k=i.useRef(null),v=i.useRef(null),j=i.useRef(null);return i.useEffect(()=>{const e=k.current;if(!e)return;const t=document.activeElement;if(v.current)v.current.focus();else{const t=e.querySelector("button, [href], input, select, textarea, [tabindex]:not([tabindex='-1'])");t?t.focus():(e.setAttribute("tabindex","-1"),e.focus())}const o=t=>{if("Escape"===t.key)t.stopPropagation(),r(s);else if("Tab"===t.key){const o=Array.from(e.querySelectorAll("button, [href], input, select, textarea, [tabindex]:not([tabindex='-1'])")).filter(Boolean);if(0===o.length)return void t.preventDefault();const n=o[0],i=o[o.length-1];t.shiftKey?document.activeElement===n&&(t.preventDefault(),i.focus()):document.activeElement===i&&(t.preventDefault(),n.focus())}};return e.addEventListener("keydown",o),()=>{e.removeEventListener("keydown",o),t&&t.focus&&t.focus()}},[r,s]),o.IsForm(y)&&(y=i.cloneElement(y,{ref:j})),e.jsx(n.ModalBackdrop,{onClick:()=>f&&r(s),className:p.backdrop||"",style:h.backdrop,children:e.jsxs(a,{ref:k,role:"dialog","aria-modal":"true","aria-labelledby":d?`${s}-title`:void 0,className:p.dialog||"",style:h.dialog,children:[m&&e.jsx("button",{type:"button",className:`hook-dialog-close-button ${p.closeButton||""}`,"aria-label":"Close",onClick:()=>r(s),style:{position:"absolute",top:0,right:0,transform:"translate(75%, -75%)",width:32,height:32,background:"none",border:"none",fontSize:21,cursor:"pointer",lineHeight:1,color:"#555",...h.closeButton},children:"×"}),d&&e.jsx("h3",{id:`${s}-title`,className:`hook-dialog-title ${p.title||""}`,style:{margin:"0 0 15px",fontSize:20,...h.title},children:d}),y&&e.jsx("div",{className:`hook-dialog-content ${p.content||""}`,style:{marginBottom:15,color:"#555",...h.content},children:y}),e.jsx("div",{className:`hook-dialog-actions ${p.actions||""}`,style:{display:"flex",flexDirection:"column",gap:10,paddingTop:15,...h.actions},children:b.map((t,n)=>{const i=t.filter(e=>e.isOnLeft),a=t.filter(e=>!e.isOnLeft),r=(t,n)=>{const i=x[t.variant||"secondary"]||x.secondary;return e.jsx("button",{ref:e=>{t.isFocused&&e&&(v.current=e)},"data-action-focused":t.isFocused?"true":void 0,className:`hook-dialog-action-button hook-dialog-action-${t.variant||"secondary"} ${p.actionButton||""} ${t.className||""}`,onClick:e=>{try{t.onClick?.(e,t)}catch{}return t.isSubmit&&c.isReturnSubmit&&j.current?l(s,t,o.FormDataToObject(new FormData(j.current))):(t.isSubmit&&j.current?.requestSubmit(),t.noActionReturn?e.stopPropagation():void l(s,t))},style:{border:"none",borderRadius:15,padding:"10px 18px",fontSize:14,fontWeight:800,cursor:"pointer",...i,...h.actionButton,...t.style||{}},children:t.title},`${t.title}-${n}`)};return e.jsxs("div",{className:`hook-dialog-actions-row ${p.actionsRow||""}`,style:{display:"flex",gap:8,justifyContent:"space-between",...h.actionsRow},children:[e.jsx("div",{className:"hook-dialog-actions-left",style:{display:"flex",gap:8},children:i.map((e,t)=>r(e,t))}),e.jsx("div",{className:"hook-dialog-actions-right",style:{display:"flex",gap:8},children:a.map((e,t)=>r(e,t))})]},n)})})]})})};
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("react/jsx-runtime"),t=require("../constants/theme.cjs.js"),o=require("../utils/utils.cjs.js"),n=require("@rokku-x/react-hook-modal"),s=require("react");;/* empty css */const a=n.ModalWindow;exports.default=function({modalWindowId:i,handleAction:l,handleClose:r,config:c}){const{actions:u=[],title:d,backdropCancel:f,showCloseButton:m,classNames:h={},styles:b={},variantStyles:y={}}=c;let{content:k}=c;const v=(u.length?u:[[{title:"OK",variant:"primary"}]]).filter(e=>e&&e.length),g={...t.baseVariantStyles,...y},p=s.useRef(null),x=s.useRef(null),j=s.useRef(null);return s.useEffect(()=>{const e=p.current;if(!e)return;const t=document.activeElement;if(x.current)x.current.focus();else{const t=e.querySelector("button, [href], input, select, textarea, [tabindex]:not([tabindex='-1'])");t?t.focus():(e.setAttribute("tabindex","-1"),e.focus())}const o=t=>{if("Escape"===t.key)t.stopPropagation(),r(i);else if("Tab"===t.key){const o=Array.from(e.querySelectorAll("button, [href], input, select, textarea, [tabindex]:not([tabindex='-1'])")).filter(Boolean);if(0===o.length)return void t.preventDefault();const n=o[0],s=o[o.length-1];t.shiftKey?document.activeElement===n&&(t.preventDefault(),s.focus()):document.activeElement===s&&(t.preventDefault(),n.focus())}};return e.addEventListener("keydown",o),()=>{e.removeEventListener("keydown",o),t&&t.focus&&t.focus()}},[r,i]),o.IsForm(k)&&(k=s.cloneElement(k,{ref:j})),e.jsx(n.ModalBackdrop,{onClick:()=>f&&r(i),className:h.backdrop||"",style:b.backdrop,children:e.jsxs(a,{ref:p,role:"dialog","aria-modal":"true","aria-labelledby":d?`${i}-title`:void 0,className:h.dialog||"",style:b.dialog,children:[m&&e.jsx("button",{type:"button",className:`hook-dialog-close-button ${h.closeButton||""}`,"aria-label":"Close",onClick:()=>r(i),style:b.closeButton,children:"×"}),d&&e.jsx("h3",{id:`${i}-title`,className:`hook-dialog-title ${h.title||""}`,style:b.title,children:d}),k&&e.jsx("div",{className:`hook-dialog-content ${h.content||""}`,style:b.content,children:k}),e.jsx("div",{className:`hook-dialog-actions ${h.actions||""}`,style:b.actions,children:v.map((t,n)=>{const s=t.filter(e=>e.isOnLeft),a=t.filter(e=>!e.isOnLeft),r=(t,n)=>{const s=g[t.variant||"secondary"]||g.secondary;return e.jsx("button",{ref:e=>{t.isFocused&&e&&(x.current=e)},"data-action-focused":t.isFocused?"true":void 0,className:`hook-dialog-action-button hook-dialog-action-${t.variant||"secondary"} ${h.actionButton||""} ${t.className||""}`,onClick:e=>{try{t.onClick?.(e,t)}catch{}return t.isSubmit&&c.isReturnSubmit&&j.current?l(i,t,o.FormDataToObject(new FormData(j.current))):(t.isSubmit&&j.current?.requestSubmit(),t.noActionReturn?e.stopPropagation():void l(i,t))},style:{...s,...b.actionButton,...t.style||{}},children:t.title},`${t.title}-${n}`)};return e.jsxs("div",{className:`hook-dialog-actions-row ${h.actionsRow||""}`,style:b.actionsRow,children:[e.jsx("div",{className:"hook-dialog-actions-left",children:s.map((e,t)=>r(e,t))}),e.jsx("div",{className:"hook-dialog-actions-right",children:a.map((e,t)=>r(e,t))})]},n)})})]})})};
@@ -1,6 +1,6 @@
1
- import{jsx as t,jsxs as e}from"react/jsx-runtime";import{baseVariantStyles as o}from"../constants/theme.esm.js";import{IsForm as n,FormDataToObject as i}from"../utils/utils.esm.js";import{ModalBackdrop as a,ModalWindow as l}from"@rokku-x/react-hook-modal";import r,{useRef as s,useEffect as c}from"react";const d=l;function u({modalWindowId:l,handleAction:u,handleClose:m,config:f}){const{actions:p=[],title:h,backdropCancel:g,showCloseButton:y,classNames:b={},styles:k={},variantStyles:v={}}=f;let{content:x}=f;const N=(p.length?p:[[{title:"OK",variant:"primary"}]]).filter(t=>t&&t.length),$={...o,...v},w=s(null),S=s(null),C=s(null);return c(()=>{const t=w.current;if(!t)return;const e=document.activeElement;if(S.current)S.current.focus();else{const e=t.querySelector("button, [href], input, select, textarea, [tabindex]:not([tabindex='-1'])");e?e.focus():(t.setAttribute("tabindex","-1"),t.focus())}const o=e=>{if("Escape"===e.key)e.stopPropagation(),m(l);else if("Tab"===e.key){const o=Array.from(t.querySelectorAll("button, [href], input, select, textarea, [tabindex]:not([tabindex='-1'])")).filter(Boolean);if(0===o.length)return void e.preventDefault();const n=o[0],i=o[o.length-1];e.shiftKey?document.activeElement===n&&(e.preventDefault(),i.focus()):document.activeElement===i&&(e.preventDefault(),n.focus())}};return t.addEventListener("keydown",o),()=>{t.removeEventListener("keydown",o),e&&e.focus&&e.focus()}},[m,l]),n(x)&&(x=r.cloneElement(x,{ref:C})),/* @__PURE__ */t(a,{onClick:()=>g&&m(l),className:b.backdrop||"",style:k.backdrop,children:/* @__PURE__ */e(d,{ref:w,role:"dialog","aria-modal":"true","aria-labelledby":h?`${l}-title`:void 0,className:b.dialog||"",style:k.dialog,children:[y&&/* @__PURE__ */t("button",{type:"button",className:`hook-dialog-close-button ${b.closeButton||""}`,"aria-label":"Close",onClick:()=>m(l),style:{position:"absolute",top:0,right:0,transform:"translate(75%, -75%)",width:32,height:32,background:"none",border:"none",fontSize:21,cursor:"pointer",lineHeight:1,color:"#555",...k.closeButton},children:"×"}),h&&/* @__PURE__ */t("h3",{id:`${l}-title`,className:`hook-dialog-title ${b.title||""}`,style:{margin:"0 0 15px",fontSize:20,...k.title},children:h}),x&&/* @__PURE__ */t("div",{className:`hook-dialog-content ${b.content||""}`,style:{marginBottom:15,color:"#555",...k.content},children:x}),
2
- /* @__PURE__ */t("div",{className:`hook-dialog-actions ${b.actions||""}`,style:{display:"flex",flexDirection:"column",gap:10,paddingTop:15,...k.actions},children:N.map((o,n)=>{const a=o.filter(t=>t.isOnLeft),r=o.filter(t=>!t.isOnLeft),s=(e,o)=>{const n=$[e.variant||"secondary"]||$.secondary;/* @__PURE__ */
3
- return t("button",{ref:t=>{e.isFocused&&t&&(S.current=t)},"data-action-focused":e.isFocused?"true":void 0,className:`hook-dialog-action-button hook-dialog-action-${e.variant||"secondary"} ${b.actionButton||""} ${e.className||""}`,onClick:t=>{try{e.onClick?.(t,e)}catch{}return e.isSubmit&&f.isReturnSubmit&&C.current?u(l,e,i(new FormData(C.current))):(e.isSubmit&&C.current?.requestSubmit(),e.noActionReturn?t.stopPropagation():void u(l,e))},style:{border:"none",borderRadius:15,padding:"10px 18px",fontSize:14,fontWeight:800,cursor:"pointer",...n,...k.actionButton,...e.style||{}},children:e.title},`${e.title}-${o}`)};/* @__PURE__ */
4
- return e("div",{className:`hook-dialog-actions-row ${b.actionsRow||""}`,style:{display:"flex",gap:8,justifyContent:"space-between",...k.actionsRow},children:[
5
- /* @__PURE__ */t("div",{className:"hook-dialog-actions-left",style:{display:"flex",gap:8},children:a.map((t,e)=>s(t,e))}),
6
- /* @__PURE__ */t("div",{className:"hook-dialog-actions-right",style:{display:"flex",gap:8},children:r.map((t,e)=>s(t,e))})]},n)})})]})})}export{u as default};
1
+ import{jsx as t,jsxs as e}from"react/jsx-runtime";import{baseVariantStyles as o}from"../constants/theme.esm.js";import{IsForm as n,FormDataToObject as a}from"../utils/utils.esm.js";import{ModalBackdrop as i,ModalWindow as l}from"@rokku-x/react-hook-modal";import s,{useRef as r,useEffect as c}from"react";/* empty css */const d=l;function u({modalWindowId:l,handleAction:u,handleClose:m,config:f}){const{actions:h=[],title:y,backdropCancel:p,showCloseButton:b,classNames:k={},styles:v={},variantStyles:g={}}=f;let{content:N}=f;const $=(h.length?h:[[{title:"OK",variant:"primary"}]]).filter(t=>t&&t.length),w={...o,...g},x=r(null),C=r(null),E=r(null);return c(()=>{const t=x.current;if(!t)return;const e=document.activeElement;if(C.current)C.current.focus();else{const e=t.querySelector("button, [href], input, select, textarea, [tabindex]:not([tabindex='-1'])");e?e.focus():(t.setAttribute("tabindex","-1"),t.focus())}const o=e=>{if("Escape"===e.key)e.stopPropagation(),m(l);else if("Tab"===e.key){const o=Array.from(t.querySelectorAll("button, [href], input, select, textarea, [tabindex]:not([tabindex='-1'])")).filter(Boolean);if(0===o.length)return void e.preventDefault();const n=o[0],a=o[o.length-1];e.shiftKey?document.activeElement===n&&(e.preventDefault(),a.focus()):document.activeElement===a&&(e.preventDefault(),n.focus())}};return t.addEventListener("keydown",o),()=>{t.removeEventListener("keydown",o),e&&e.focus&&e.focus()}},[m,l]),n(N)&&(N=s.cloneElement(N,{ref:E})),/* @__PURE__ */t(i,{onClick:()=>p&&m(l),className:k.backdrop||"",style:v.backdrop,children:/* @__PURE__ */e(d,{ref:x,role:"dialog","aria-modal":"true","aria-labelledby":y?`${l}-title`:void 0,className:k.dialog||"",style:v.dialog,children:[b&&/* @__PURE__ */t("button",{type:"button",className:`hook-dialog-close-button ${k.closeButton||""}`,"aria-label":"Close",onClick:()=>m(l),style:v.closeButton,children:"×"}),y&&/* @__PURE__ */t("h3",{id:`${l}-title`,className:`hook-dialog-title ${k.title||""}`,style:v.title,children:y}),N&&/* @__PURE__ */t("div",{className:`hook-dialog-content ${k.content||""}`,style:v.content,children:N}),
2
+ /* @__PURE__ */t("div",{className:`hook-dialog-actions ${k.actions||""}`,style:v.actions,children:$.map((o,n)=>{const i=o.filter(t=>t.isOnLeft),s=o.filter(t=>!t.isOnLeft),r=(e,o)=>{const n=w[e.variant||"secondary"]||w.secondary;/* @__PURE__ */
3
+ return t("button",{ref:t=>{e.isFocused&&t&&(C.current=t)},"data-action-focused":e.isFocused?"true":void 0,className:`hook-dialog-action-button hook-dialog-action-${e.variant||"secondary"} ${k.actionButton||""} ${e.className||""}`,onClick:t=>{try{e.onClick?.(t,e)}catch{}return e.isSubmit&&f.isReturnSubmit&&E.current?u(l,e,a(new FormData(E.current))):(e.isSubmit&&E.current?.requestSubmit(),e.noActionReturn?t.stopPropagation():void u(l,e))},style:{...n,...v.actionButton,...e.style||{}},children:e.title},`${e.title}-${o}`)};/* @__PURE__ */
4
+ return e("div",{className:`hook-dialog-actions-row ${k.actionsRow||""}`,style:v.actionsRow,children:[
5
+ /* @__PURE__ */t("div",{className:"hook-dialog-actions-left",children:i.map((t,e)=>r(t,e))}),
6
+ /* @__PURE__ */t("div",{className:"hook-dialog-actions-right",children:s.map((t,e)=>r(t,e))})]},n)})})]})})}export{u as default};
@@ -1 +1 @@
1
- "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("react"),a=require("@rokku-x/react-hook-modal"),s=require("../components/ModalWindow.cjs.js"),t=require("../store/dialogStore.cjs.js");exports.default=function(o){const{instances:n,addInstance:l,removeInstance:c,getInstance:r}=t.useDialogStore(),i=a.useBaseModal(),u=e.useCallback(e=>{const a=r(e);a&&(i.popModal(e),!1!==a.config.rejectOnCancel?a.reject(a.config.defaultCancelValue):a.resolve(a.config.defaultCancelValue),c(e))},[r,c,i]),d=e.useCallback((e,a)=>{const s=r(e);s&&(i.popModal(e),a.isCancel&&!1!==s.config.rejectOnCancel?s.reject(a.value):s.resolve(a.value),c(e))},[r,c,i]);return[function(a){return new Promise((t,n)=>{const c=Math.random().toString(36).substring(2,6),r={...o,...a,classNames:{...o?.classNames,...a.classNames},styles:{...o?.styles,...a.styles},variantStyles:{...o?.variantStyles,...a.variantStyles}},f={id:c,config:r,resolve:t,reject:n};l(f),f.id=i.pushModal(c,e.createElement(s.default,{config:r,modalWindowId:c,handleClose:u,handleAction:d}))})}]};
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("react"),s=require("@rokku-x/react-hook-modal"),t=require("../components/ModalWindow.cjs.js"),a=require("../store/dialog.cjs.js");exports.default=function(o){const n=s.useBaseModal(),{addInstance:r,handleAction:l,handleClose:i,rendererDefaultConfig:d}=a.default();return[function(s){return new Promise((a,c)=>{const u=Math.random().toString(36).substring(2,6),m={...d,...o,...s,classNames:{...d?.classNames,...o?.classNames,...s.classNames},styles:{...d?.styles,...o?.styles,...s.styles},variantStyles:{...d?.variantStyles,...o?.variantStyles,...s.variantStyles}},f={id:u,config:m,resolve:a,reject:c};r(f),f.id=n.pushModal(u,e.createElement(t.default,{config:m,modalWindowId:u,handleClose:i,handleAction:l}))})}]};
@@ -1 +1 @@
1
- import e,{useCallback as o}from"react";import{useBaseModal as t}from"@rokku-x/react-hook-modal";import n from"../components/ModalWindow.esm.js";import{useDialogStore as a}from"../store/dialogStore.esm.js";function s(s){const{instances:l,addInstance:r,removeInstance:c,getInstance:i}=a(),d=t(),m=o(e=>{const o=i(e);o&&(d.popModal(e),!1!==o.config.rejectOnCancel?o.reject(o.config.defaultCancelValue):o.resolve(o.config.defaultCancelValue),c(e))},[i,c,d]),f=o((e,o)=>{const t=i(e);t&&(d.popModal(e),o.isCancel&&!1!==t.config.rejectOnCancel?t.reject(o.value):t.resolve(o.value),c(e))},[i,c,d]);return[function(o){return new Promise((t,a)=>{const l=Math.random().toString(36).substring(2,6),c={...s,...o,classNames:{...s?.classNames,...o.classNames},styles:{...s?.styles,...o.styles},variantStyles:{...s?.variantStyles,...o.variantStyles}},i={id:l,config:c,resolve:t,reject:a};r(i),i.id=d.pushModal(l,e.createElement(n,{config:c,modalWindowId:l,handleClose:m,handleAction:f}))})}]}export{s as default};
1
+ import e from"react";import{useBaseModal as s}from"@rokku-x/react-hook-modal";import t from"../components/ModalWindow.esm.js";import o from"../store/dialog.esm.js";function a(a){const n=s(),{addInstance:r,handleAction:l,handleClose:i,rendererDefaultConfig:m}=o();return[function(s){return new Promise((o,d)=>{const c=Math.random().toString(36).substring(2,6),f={...m,...a,...s,classNames:{...m?.classNames,...a?.classNames,...s.classNames},styles:{...m?.styles,...a?.styles,...s.styles},variantStyles:{...m?.variantStyles,...a?.variantStyles,...s.variantStyles}},u={id:c,config:f,resolve:o,reject:d};r(u),u.id=n.pushModal(c,e.createElement(t,{config:f,modalWindowId:c,handleClose:i,handleAction:l}))})}]}export{a as default};
package/dist/index.cjs.js CHANGED
@@ -1 +1,3 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("@rokku-x/react-hook-modal"),o=require("./hooks/useHookDialog.cjs.js");Object.defineProperty(exports,"BaseModalRenderer",{enumerable:!0,get:()=>e.BaseModalRenderer}),exports.useHookDialog=o.default;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("@rokku-x/react-hook-modal"),o=require("./components/BaseDialogRenderer.cjs.js"),r=require("./hooks/useHookDialog.cjs.js");Object.defineProperty(exports,"BaseModalRenderer",{enumerable:!0,get:()=>e.BaseModalRenderer}),exports.BaseDialogRenderer=o.default,exports.useHookDialog=r.default;
2
+
3
+ if (typeof document !== 'undefined') { const style = document.createElement('style'); style.textContent = ".hook-dialog-close-button{position:absolute;top:0;right:0;transform:translate(75%,-75%);width:32px;height:32px;background:none;border:none;font-size:21px;cursor:pointer;line-height:1;color:#555}.hook-dialog-title{margin:0 0 15px;font-size:20px}.hook-dialog-content{margin-bottom:15px;color:#555}.hook-dialog-actions{display:flex;flex-direction:column;gap:10px;padding-top:15px}.hook-dialog-actions-row{display:flex;gap:8px;justify-content:space-between}.hook-dialog-actions-left,.hook-dialog-actions-right{display:flex;gap:8px}.hook-dialog-action-button{border:none;border-radius:15px;padding:10px 18px;font-size:14px;font-weight:800;cursor:pointer}\n"; document.head.appendChild(style); }
package/dist/index.d.ts CHANGED
@@ -36,12 +36,16 @@
36
36
  * ```
37
37
  */
38
38
  /**
39
- * Re-export of BaseModalRenderer from @rokku-x/react-hook-modal.
40
- * Must be mounted at the root of your application for dialogs to render.
39
+ * Re-exports of renderer components.
40
+ * - `BaseModalRenderer` is the upstream renderer from `@rokku-x/react-hook-modal`.
41
+ * - `BaseDialogRenderer` is a small wrapper provided by this package that allows setting a `defaultConfig` prop.
42
+ * Both should be mounted at the root of your application for dialogs to render.
41
43
  */
42
44
  export { BaseModalRenderer } from '@rokku-x/react-hook-modal';
45
+ export { default as BaseDialogRenderer } from './components/BaseDialogRenderer';
43
46
  /**
44
47
  * Main hook for displaying confirmation dialogs and alerts.
45
48
  * @see {@link useHookDialog}
46
49
  */
47
50
  export { default as useHookDialog } from './hooks/useHookDialog';
51
+ export type { UseHookDialogConfig, ConfirmConfig, ModalAction, RequestDialogReturnType, DialogInstanceContext, RequestDialog, UseHookDialogReturnType } from './types';
package/dist/index.esm.js CHANGED
@@ -1 +1,3 @@
1
- import{BaseModalRenderer as o}from"@rokku-x/react-hook-modal";import{default as a}from"./hooks/useHookDialog.esm.js";export{o as BaseModalRenderer,a as useHookDialog};
1
+ import{BaseModalRenderer as o}from"@rokku-x/react-hook-modal";import{default as e}from"./components/BaseDialogRenderer.esm.js";import{default as a}from"./hooks/useHookDialog.esm.js";export{e as BaseDialogRenderer,o as BaseModalRenderer,a as useHookDialog};
2
+
3
+ if (typeof document !== 'undefined') { const style = document.createElement('style'); style.textContent = ".hook-dialog-close-button{position:absolute;top:0;right:0;transform:translate(75%,-75%);width:32px;height:32px;background:none;border:none;font-size:21px;cursor:pointer;line-height:1;color:#555}.hook-dialog-title{margin:0 0 15px;font-size:20px}.hook-dialog-content{margin-bottom:15px;color:#555}.hook-dialog-actions{display:flex;flex-direction:column;gap:10px;padding-top:15px}.hook-dialog-actions-row{display:flex;gap:8px;justify-content:space-between}.hook-dialog-actions-left,.hook-dialog-actions-right{display:flex;gap:8px}.hook-dialog-action-button{border:none;border-radius:15px;padding:10px 18px;font-size:14px;font-weight:800;cursor:pointer}\n"; document.head.appendChild(style); }
@@ -0,0 +1 @@
1
+ .hook-dialog-close-button{position:absolute;top:0;right:0;transform:translate(75%,-75%);width:32px;height:32px;background:none;border:none;font-size:21px;cursor:pointer;line-height:1;color:#555}.hook-dialog-title{margin:0 0 15px;font-size:20px}.hook-dialog-content{margin-bottom:15px;color:#555}.hook-dialog-actions{display:flex;flex-direction:column;gap:10px;padding-top:15px}.hook-dialog-actions-row{display:flex;gap:8px;justify-content:space-between}.hook-dialog-actions-left,.hook-dialog-actions-right{display:flex;gap:8px}.hook-dialog-action-button{border:none;border-radius:15px;padding:10px 18px;font-size:14px;font-weight:800;cursor:pointer}
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const e=require("../components/ModalWindow.cjs.js"),n=require("@rokku-x/react-hook-modal"),t=require("react"),o=require("zustand").create((o,a)=>({instances:[],rendererDefaultConfig:{},setDefaultConfig:e=>{o(()=>({rendererDefaultConfig:e}))},addInstance:c=>{o(e=>({instances:[...e.instances,c]})),c.id=n.storeBaseModal.getState().actions.pushModal(c.id,t.createElement(e.default,{config:c.config,modalWindowId:c.id,handleClose:a().handleClose,handleAction:a().handleAction}))},removeInstance:e=>o(n=>({instances:n.instances.filter(n=>n.id!==e)})),handleClose:(e,t=!1)=>{const o=a().getInstance(e);o&&(n.storeBaseModal.getState().actions.popModal(e),!1!==o.config.rejectOnCancel||t?o.reject(o.config.defaultCancelValue):o.resolve(o.config.defaultCancelValue),a().removeInstance(e))},handleAction:(e,t)=>{const o=a().getInstance(e);o&&(n.storeBaseModal.getState().actions.popModal(e),t.isCancel&&!1!==o.config.rejectOnCancel?o.reject(t.value):o.resolve(t.value),a().removeInstance(e))},getContext:e=>{const n=a().getInstance(e);if(!n)throw new Error(`Dialog instance with id "${e}" not found.`);return{id:n.id,config:n.config,forceCancel:(n=!0)=>a().handleClose(e,n),forceAction:n=>{a().handleAction(e,n)},forceDefault:()=>{const t=n.config.actions?.flat().find(e=>e.isFocused);if(!t)throw new Error(`No default action (isFocused) defined for dialog instance with id "${e}".`);a().handleAction(e,t)}}},getInstance:e=>a().instances.find(n=>n.id===e)}));exports.default=o;
@@ -0,0 +1,42 @@
1
+ import { ConfirmInstance, ModalAction, UseHookDialogConfig, ValidValue } from '../types';
2
+ /**
3
+ * Zustand store interface for managing dialog instances.
4
+ * Maintains a centralized list of active dialog instances.
5
+ * @internal
6
+ */
7
+ interface DialogStore<T = ValidValue> {
8
+ /** Array of currently active dialog instances */
9
+ instances: ConfirmInstance<T>[];
10
+ /** Default configuration applied to all dialogs */
11
+ rendererDefaultConfig: UseHookDialogConfig;
12
+ /** Set the default configuration for dialogs */
13
+ setDefaultConfig: (config: UseHookDialogConfig) => void;
14
+ /** Add a new dialog instance to the store */
15
+ addInstance: (instance: ConfirmInstance<T>) => void;
16
+ /** Remove a dialog instance from the store by ID */
17
+ removeInstance: (id: string) => void;
18
+ /** Handle closing a dialog instance */
19
+ handleClose: (id: string, isForceCancel?: boolean) => void;
20
+ /** Handle an action taken on a dialog instance */
21
+ handleAction: (id: string, action: ModalAction) => void;
22
+ /** Get a dialog instance by ID */
23
+ getInstance: (id: string) => ConfirmInstance<T> | undefined;
24
+ /** Get the context of a dialog instance by ID */
25
+ getContext: (id: string) => {
26
+ id: string;
27
+ config: any;
28
+ forceCancel: () => void;
29
+ } | undefined;
30
+ }
31
+ /**
32
+ * Zustand store for managing dialog instances.
33
+ * Provides centralized state management for all active dialogs.
34
+ *
35
+ * @internal
36
+ * @example
37
+ * ```tsx
38
+ * const { addInstance, removeInstance, getInstance } = useDialogStore();
39
+ * ```
40
+ */
41
+ declare const storeDialog: import('zustand').UseBoundStore<import('zustand').StoreApi<DialogStore<any>>>;
42
+ export default storeDialog;
@@ -0,0 +1 @@
1
+ import e from"../components/ModalWindow.esm.js";import{storeBaseModal as n}from"@rokku-x/react-hook-modal";import t from"react";import{create as o}from"zustand";const a=o((o,a)=>({instances:[],rendererDefaultConfig:{},setDefaultConfig:e=>{o(()=>({rendererDefaultConfig:e}))},addInstance:c=>{o(e=>({instances:[...e.instances,c]})),c.id=n.getState().actions.pushModal(c.id,t.createElement(e,{config:c.config,modalWindowId:c.id,handleClose:a().handleClose,handleAction:a().handleAction}))},removeInstance:e=>o(n=>({instances:n.instances.filter(n=>n.id!==e)})),handleClose:(e,t=!1)=>{const o=a().getInstance(e);o&&(n.getState().actions.popModal(e),!1!==o.config.rejectOnCancel||t?o.reject(o.config.defaultCancelValue):o.resolve(o.config.defaultCancelValue),a().removeInstance(e))},handleAction:(e,t)=>{const o=a().getInstance(e);o&&(n.getState().actions.popModal(e),t.isCancel&&!1!==o.config.rejectOnCancel?o.reject(t.value):o.resolve(t.value),a().removeInstance(e))},getContext:e=>{const n=a().getInstance(e);if(!n)throw new Error(`Dialog instance with id "${e}" not found.`);return{id:n.id,config:n.config,forceCancel:(n=!0)=>a().handleClose(e,n),forceAction:n=>{a().handleAction(e,n)},forceDefault:()=>{const t=n.config.actions?.flat().find(e=>e.isFocused);if(!t)throw new Error(`No default action (isFocused) defined for dialog instance with id "${e}".`);a().handleAction(e,t)}}},getInstance:e=>a().instances.find(n=>n.id===e)}));export{a as default};
@@ -235,4 +235,26 @@ interface HandleActionCallback {
235
235
  * Friendly Form Data object type
236
236
  */
237
237
  export type FormDataObject = Record<string, FormDataEntryValue | FormDataEntryValue[]>;
238
+ /** Function type for requesting a dialog*/
239
+ export interface RequestDialog<U> {
240
+ <U = FormDataObject>(config: ConfirmConfig & {
241
+ isReturnSubmit: true;
242
+ }): RequestDialogReturnType<U>;
243
+ <U = ValidValue>(config: ConfirmConfig): RequestDialogReturnType<U>;
244
+ }
245
+ /** Context information for a dialog instance */
246
+ export type DialogInstanceContext = {
247
+ id: string;
248
+ config: any;
249
+ forceCancel: (forceReject?: boolean) => void;
250
+ forceAction: (action: ModalAction) => void;
251
+ forceDefault: () => void;
252
+ };
253
+ /** Return type of the requestDialog function with added context property */
254
+ export type RequestDialogReturnType<T> = Promise<T> & {
255
+ id: string;
256
+ context: DialogInstanceContext;
257
+ };
258
+ /** Return type of the useHookDialog hook */
259
+ export type UseHookDialogReturnType<T> = [requestDialog: RequestDialog<T>, getContext: (id: string) => DialogInstanceContext];
238
260
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rokku-x/react-hook-dialog",
3
- "version": "1.0.4",
3
+ "version": "1.2.0",
4
4
  "author": "rokku-x",
5
5
  "repository": {
6
6
  "type": "git",
@@ -9,10 +9,10 @@
9
9
  "main": "dist/index.cjs.js",
10
10
  "module": "dist/index.esm.js",
11
11
  "peerDependencies": {
12
- "@rokku-x/react-hook-modal": "^0.9.0",
13
12
  "react": "^18.0.0",
14
13
  "react-dom": "^18.0.0",
15
- "zustand": "^5.0.10"
14
+ "zustand": "^5.0.10",
15
+ "@rokku-x/react-hook-modal": "^0.9.4"
16
16
  },
17
17
  "exports": {
18
18
  ".": {
@@ -20,6 +20,8 @@
20
20
  "import": "./dist/index.esm.js",
21
21
  "require": "./dist/index.cjs.js"
22
22
  },
23
+ "./react-hook-dialog.css": "./dist/react-hook-dialog.css",
24
+ "./dist/react-hook-dialog.css": "./dist/react-hook-dialog.css",
23
25
  "./package.json": "./package.json"
24
26
  },
25
27
  "bugs": {
@@ -39,6 +41,7 @@
39
41
  "confirmation",
40
42
  "alert",
41
43
  "prompt",
44
+ "confirm",
42
45
  "hook",
43
46
  "typescript",
44
47
  "zustand",
@@ -50,7 +53,10 @@
50
53
  "react-modal"
51
54
  ],
52
55
  "license": "MIT",
53
- "sideEffects": false,
56
+ "sideEffects": [
57
+ "*.css",
58
+ "*.scss"
59
+ ],
54
60
  "types": "dist/index.d.ts",
55
61
  "typesVersions": {
56
62
  "*": {
@@ -1 +0,0 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("zustand").create((e,n)=>({instances:[],addInstance:n=>e(e=>({instances:[...e.instances,n]})),removeInstance:n=>e(e=>({instances:e.instances.filter(e=>e.id!==n)})),getInstance:e=>n().instances.find(n=>n.id===e)}));exports.useDialogStore=e;
@@ -1,28 +0,0 @@
1
- import { ConfirmInstance, ValidValue } from '../types';
2
- /**
3
- * Zustand store interface for managing dialog instances.
4
- * Maintains a centralized list of active dialog instances.
5
- * @internal
6
- */
7
- interface DialogStore<T = ValidValue> {
8
- /** Array of currently active dialog instances */
9
- instances: ConfirmInstance<T>[];
10
- /** Add a new dialog instance to the store */
11
- addInstance: (instance: ConfirmInstance<T>) => void;
12
- /** Remove a dialog instance from the store by ID */
13
- removeInstance: (id: string) => void;
14
- /** Get a dialog instance by ID */
15
- getInstance: (id: string) => ConfirmInstance<T> | undefined;
16
- }
17
- /**
18
- * Zustand store for managing dialog instances.
19
- * Provides centralized state management for all active dialogs.
20
- *
21
- * @internal
22
- * @example
23
- * ```tsx
24
- * const { addInstance, removeInstance, getInstance } = useDialogStore();
25
- * ```
26
- */
27
- export declare const useDialogStore: import('zustand').UseBoundStore<import('zustand').StoreApi<DialogStore<any>>>;
28
- export {};
@@ -1 +0,0 @@
1
- import{create as n}from"zustand";const s=n((n,s)=>({instances:[],addInstance:s=>n(n=>({instances:[...n.instances,s]})),removeInstance:s=>n(n=>({instances:n.instances.filter(n=>n.id!==s)})),getInstance:n=>s().instances.find(s=>s.id===n)}));export{s as useDialogStore};