@jini-ai/admin 0.1.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/LICENSE +201 -0
- package/README.md +138 -0
- package/dist/browser/index.d.ts +8 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +8 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/navigation.d.ts +39 -0
- package/dist/browser/navigation.d.ts.map +1 -0
- package/dist/browser/navigation.js +113 -0
- package/dist/browser/navigation.js.map +1 -0
- package/dist/core/gated/types.d.ts +87 -0
- package/dist/core/gated/types.d.ts.map +1 -0
- package/dist/core/gated/types.js +37 -0
- package/dist/core/gated/types.js.map +1 -0
- package/dist/core/index.d.ts +20 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +15 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/manifest/rules.d.ts +84 -0
- package/dist/core/manifest/rules.d.ts.map +1 -0
- package/dist/core/manifest/rules.js +130 -0
- package/dist/core/manifest/rules.js.map +1 -0
- package/dist/core/manifest/types.d.ts +154 -0
- package/dist/core/manifest/types.d.ts.map +1 -0
- package/dist/core/manifest/types.js +31 -0
- package/dist/core/manifest/types.js.map +1 -0
- package/dist/core/permissions/rules.d.ts +32 -0
- package/dist/core/permissions/rules.d.ts.map +1 -0
- package/dist/core/permissions/rules.js +34 -0
- package/dist/core/permissions/rules.js.map +1 -0
- package/dist/core/ports/analytics.d.ts +35 -0
- package/dist/core/ports/analytics.d.ts.map +1 -0
- package/dist/core/ports/analytics.js +14 -0
- package/dist/core/ports/analytics.js.map +1 -0
- package/dist/core/ports/auth.d.ts +49 -0
- package/dist/core/ports/auth.d.ts.map +1 -0
- package/dist/core/ports/auth.js +19 -0
- package/dist/core/ports/auth.js.map +1 -0
- package/dist/core/ports/comments.d.ts +90 -0
- package/dist/core/ports/comments.d.ts.map +1 -0
- package/dist/core/ports/comments.js +24 -0
- package/dist/core/ports/comments.js.map +1 -0
- package/dist/core/ports/database.d.ts +101 -0
- package/dist/core/ports/database.d.ts.map +1 -0
- package/dist/core/ports/database.js +2 -0
- package/dist/core/ports/database.js.map +1 -0
- package/dist/core/ports/forms.d.ts +130 -0
- package/dist/core/ports/forms.d.ts.map +1 -0
- package/dist/core/ports/forms.js +62 -0
- package/dist/core/ports/forms.js.map +1 -0
- package/dist/core/ports/identity.d.ts +114 -0
- package/dist/core/ports/identity.d.ts.map +1 -0
- package/dist/core/ports/identity.js +28 -0
- package/dist/core/ports/identity.js.map +1 -0
- package/dist/core/ports/index.d.ts +21 -0
- package/dist/core/ports/index.d.ts.map +1 -0
- package/dist/core/ports/index.js +6 -0
- package/dist/core/ports/index.js.map +1 -0
- package/dist/core/ports/integrations.d.ts +81 -0
- package/dist/core/ports/integrations.d.ts.map +1 -0
- package/dist/core/ports/integrations.js +21 -0
- package/dist/core/ports/integrations.js.map +1 -0
- package/dist/core/ports/media.d.ts +74 -0
- package/dist/core/ports/media.d.ts.map +1 -0
- package/dist/core/ports/media.js +28 -0
- package/dist/core/ports/media.js.map +1 -0
- package/dist/core/ports/members.d.ts +50 -0
- package/dist/core/ports/members.d.ts.map +1 -0
- package/dist/core/ports/members.js +17 -0
- package/dist/core/ports/members.js.map +1 -0
- package/dist/core/ports/menus.d.ts +179 -0
- package/dist/core/ports/menus.d.ts.map +1 -0
- package/dist/core/ports/menus.js +70 -0
- package/dist/core/ports/menus.js.map +1 -0
- package/dist/core/ports/plugins.d.ts +68 -0
- package/dist/core/ports/plugins.d.ts.map +1 -0
- package/dist/core/ports/plugins.js +27 -0
- package/dist/core/ports/plugins.js.map +1 -0
- package/dist/core/ports/recovery.d.ts +171 -0
- package/dist/core/ports/recovery.d.ts.map +1 -0
- package/dist/core/ports/recovery.js +2 -0
- package/dist/core/ports/recovery.js.map +1 -0
- package/dist/core/ports/redirects.d.ts +139 -0
- package/dist/core/ports/redirects.d.ts.map +1 -0
- package/dist/core/ports/redirects.js +55 -0
- package/dist/core/ports/redirects.js.map +1 -0
- package/dist/core/ports/seo.d.ts +147 -0
- package/dist/core/ports/seo.d.ts.map +1 -0
- package/dist/core/ports/seo.js +39 -0
- package/dist/core/ports/seo.js.map +1 -0
- package/dist/core/ports/settings.d.ts +87 -0
- package/dist/core/ports/settings.d.ts.map +1 -0
- package/dist/core/ports/settings.js +23 -0
- package/dist/core/ports/settings.js.map +1 -0
- package/dist/core/ports/workspace.d.ts +44 -0
- package/dist/core/ports/workspace.d.ts.map +1 -0
- package/dist/core/ports/workspace.js +29 -0
- package/dist/core/ports/workspace.js.map +1 -0
- package/dist/core/routing/rules.d.ts +54 -0
- package/dist/core/routing/rules.d.ts.map +1 -0
- package/dist/core/routing/rules.js +113 -0
- package/dist/core/routing/rules.js.map +1 -0
- package/dist/core/routing/types.d.ts +36 -0
- package/dist/core/routing/types.d.ts.map +1 -0
- package/dist/core/routing/types.js +17 -0
- package/dist/core/routing/types.js.map +1 -0
- package/dist/core/transport/errors.d.ts +53 -0
- package/dist/core/transport/errors.d.ts.map +1 -0
- package/dist/core/transport/errors.js +63 -0
- package/dist/core/transport/errors.js.map +1 -0
- package/dist/core/transport/http.d.ts +63 -0
- package/dist/core/transport/http.d.ts.map +1 -0
- package/dist/core/transport/http.js +63 -0
- package/dist/core/transport/http.js.map +1 -0
- package/dist/core/transport/index.d.ts +5 -0
- package/dist/core/transport/index.d.ts.map +1 -0
- package/dist/core/transport/index.js +3 -0
- package/dist/core/transport/index.js.map +1 -0
- package/dist/core/transport/types.d.ts +81 -0
- package/dist/core/transport/types.d.ts.map +1 -0
- package/dist/core/transport/types.js +43 -0
- package/dist/core/transport/types.js.map +1 -0
- package/dist/react/components/ConfirmButton/ConfirmButton.d.ts +40 -0
- package/dist/react/components/ConfirmButton/ConfirmButton.d.ts.map +1 -0
- package/dist/react/components/ConfirmButton/ConfirmButton.hooks.d.ts +22 -0
- package/dist/react/components/ConfirmButton/ConfirmButton.hooks.d.ts.map +1 -0
- package/dist/react/components/ConfirmButton/ConfirmButton.hooks.js +59 -0
- package/dist/react/components/ConfirmButton/ConfirmButton.hooks.js.map +1 -0
- package/dist/react/components/ConfirmButton/ConfirmButton.js +40 -0
- package/dist/react/components/ConfirmButton/ConfirmButton.js.map +1 -0
- package/dist/react/components/ConfirmButton.d.ts +39 -0
- package/dist/react/components/ConfirmButton.d.ts.map +1 -0
- package/dist/react/components/ConfirmButton.js +79 -0
- package/dist/react/components/ConfirmButton.js.map +1 -0
- package/dist/react/components/ConfirmDialog/ConfirmDialog.d.ts +90 -0
- package/dist/react/components/ConfirmDialog/ConfirmDialog.d.ts.map +1 -0
- package/dist/react/components/ConfirmDialog/ConfirmDialog.hooks.d.ts +49 -0
- package/dist/react/components/ConfirmDialog/ConfirmDialog.hooks.d.ts.map +1 -0
- package/dist/react/components/ConfirmDialog/ConfirmDialog.hooks.js +81 -0
- package/dist/react/components/ConfirmDialog/ConfirmDialog.hooks.js.map +1 -0
- package/dist/react/components/ConfirmDialog/ConfirmDialog.js +92 -0
- package/dist/react/components/ConfirmDialog/ConfirmDialog.js.map +1 -0
- package/dist/react/components/ConfirmDialog.d.ts +57 -0
- package/dist/react/components/ConfirmDialog.d.ts.map +1 -0
- package/dist/react/components/ConfirmDialog.js +85 -0
- package/dist/react/components/ConfirmDialog.js.map +1 -0
- package/dist/react/components/DataTable.d.ts +97 -0
- package/dist/react/components/DataTable.d.ts.map +1 -0
- package/dist/react/components/DataTable.js +20 -0
- package/dist/react/components/DataTable.js.map +1 -0
- package/dist/react/components/InteractiveHtmlEditor/InteractiveHtmlEditor.d.ts +25 -0
- package/dist/react/components/InteractiveHtmlEditor/InteractiveHtmlEditor.d.ts.map +1 -0
- package/dist/react/components/InteractiveHtmlEditor/InteractiveHtmlEditor.js +148 -0
- package/dist/react/components/InteractiveHtmlEditor/InteractiveHtmlEditor.js.map +1 -0
- package/dist/react/components/RowMenu/RowMenu.d.ts +77 -0
- package/dist/react/components/RowMenu/RowMenu.d.ts.map +1 -0
- package/dist/react/components/RowMenu/RowMenu.hooks.d.ts +30 -0
- package/dist/react/components/RowMenu/RowMenu.hooks.d.ts.map +1 -0
- package/dist/react/components/RowMenu/RowMenu.hooks.js +154 -0
- package/dist/react/components/RowMenu/RowMenu.hooks.js.map +1 -0
- package/dist/react/components/RowMenu/RowMenu.js +46 -0
- package/dist/react/components/RowMenu/RowMenu.js.map +1 -0
- package/dist/react/components/RowMenu.d.ts +44 -0
- package/dist/react/components/RowMenu.d.ts.map +1 -0
- package/dist/react/components/RowMenu.js +149 -0
- package/dist/react/components/RowMenu.js.map +1 -0
- package/dist/react/components/Sidebar.d.ts +128 -0
- package/dist/react/components/Sidebar.d.ts.map +1 -0
- package/dist/react/components/Sidebar.js +168 -0
- package/dist/react/components/Sidebar.js.map +1 -0
- package/dist/react/hooks/use-nav-sections.d.ts +50 -0
- package/dist/react/hooks/use-nav-sections.d.ts.map +1 -0
- package/dist/react/hooks/use-nav-sections.js +112 -0
- package/dist/react/hooks/use-nav-sections.js.map +1 -0
- package/dist/react/hooks/use-sidebar-rail.d.ts +39 -0
- package/dist/react/hooks/use-sidebar-rail.d.ts.map +1 -0
- package/dist/react/hooks/use-sidebar-rail.js +93 -0
- package/dist/react/hooks/use-sidebar-rail.js.map +1 -0
- package/dist/react/index.d.ts +53 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +53 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/types.d.ts +28 -0
- package/dist/react/types.d.ts.map +1 -0
- package/dist/react/types.js +23 -0
- package/dist/react/types.js.map +1 -0
- package/dist/server/composio/bounded-data.d.ts +36 -0
- package/dist/server/composio/bounded-data.d.ts.map +1 -0
- package/dist/server/composio/bounded-data.js +181 -0
- package/dist/server/composio/bounded-data.js.map +1 -0
- package/dist/server/composio/catalog.d.ts +147 -0
- package/dist/server/composio/catalog.d.ts.map +1 -0
- package/dist/server/composio/catalog.js +126 -0
- package/dist/server/composio/catalog.js.map +1 -0
- package/dist/server/composio/composio-config.d.ts +42 -0
- package/dist/server/composio/composio-config.d.ts.map +1 -0
- package/dist/server/composio/composio-config.js +145 -0
- package/dist/server/composio/composio-config.js.map +1 -0
- package/dist/server/composio/composio-descriptions.d.ts +16 -0
- package/dist/server/composio/composio-descriptions.d.ts.map +1 -0
- package/dist/server/composio/composio-descriptions.js +766 -0
- package/dist/server/composio/composio-descriptions.js.map +1 -0
- package/dist/server/composio/composio.d.ts +364 -0
- package/dist/server/composio/composio.d.ts.map +1 -0
- package/dist/server/composio/composio.js +1902 -0
- package/dist/server/composio/composio.js.map +1 -0
- package/dist/server/composio/errors.d.ts +15 -0
- package/dist/server/composio/errors.d.ts.map +1 -0
- package/dist/server/composio/errors.js +16 -0
- package/dist/server/composio/errors.js.map +1 -0
- package/dist/server/composio/file-lock.d.ts +13 -0
- package/dist/server/composio/file-lock.d.ts.map +1 -0
- package/dist/server/composio/file-lock.js +92 -0
- package/dist/server/composio/file-lock.js.map +1 -0
- package/dist/server/composio/index.d.ts +18 -0
- package/dist/server/composio/index.d.ts.map +1 -0
- package/dist/server/composio/index.js +7 -0
- package/dist/server/composio/index.js.map +1 -0
- package/dist/server/composio/json-schema.d.ts +93 -0
- package/dist/server/composio/json-schema.d.ts.map +1 -0
- package/dist/server/composio/json-schema.js +498 -0
- package/dist/server/composio/json-schema.js.map +1 -0
- package/dist/server/composio/output-protection.d.ts +32 -0
- package/dist/server/composio/output-protection.d.ts.map +1 -0
- package/dist/server/composio/output-protection.js +105 -0
- package/dist/server/composio/output-protection.js.map +1 -0
- package/dist/server/composio/service.d.ts +181 -0
- package/dist/server/composio/service.d.ts.map +1 -0
- package/dist/server/composio/service.js +758 -0
- package/dist/server/composio/service.js.map +1 -0
- package/dist/server/index.d.ts +15 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +15 -0
- package/dist/server/index.js.map +1 -0
- package/package.json +92 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The transport seam — the single primitive every admin route group is built on.
|
|
3
|
+
*
|
|
4
|
+
* ## Why this exists
|
|
5
|
+
*
|
|
6
|
+
* The reference implementation's `lib/api.ts` is 1,548 lines and 134 methods in one flat object literal,
|
|
7
|
+
* every one of them closing over a module-private `request()` and a module-private
|
|
8
|
+
* `BASE = "/api/admin/v1"`. That shape has two costs: the whole object is one unit (you cannot
|
|
9
|
+
* take the 67 generic methods without the 67 CMS-domain ones), and there is no way for a host to
|
|
10
|
+
* add a route group without editing the file.
|
|
11
|
+
*
|
|
12
|
+
* Inverting it — `request` becomes an injected `AdminTransport`, and each route group becomes a
|
|
13
|
+
* factory over it — makes both work:
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* const transport = createHttpTransport({ baseUrl: '/api/admin/v1' });
|
|
17
|
+
*
|
|
18
|
+
* const client = createAdminClient(transport, {
|
|
19
|
+
* identity: createIdentityRoutes, // shipped by @jini-ai/admin
|
|
20
|
+
* media: createMediaRoutes, // shipped by @jini-ai/admin
|
|
21
|
+
* posts: createHostPostRoutes, // the host's own, same signature
|
|
22
|
+
* widgets: createHostWidgetRoutes, // the host's own
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* await client.identity.listUsers(); // Jini's
|
|
26
|
+
* await client.posts.list(); // the host's
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* Both halves share one auth policy, one error class, one place to add retries or tracing. A host
|
|
30
|
+
* adding a route is a factory plus one line in the map — it never edits a Jini file, and it never
|
|
31
|
+
* grows a second `fetch` wrapper with its own subtly different error handling, which is exactly
|
|
32
|
+
* how `api.ts` reached 1,548 lines.
|
|
33
|
+
*
|
|
34
|
+
* ## Why `AdminTransport` is an interface and not just a function
|
|
35
|
+
*
|
|
36
|
+
* A bare `request<T>(path, init)` function would have been enough for HTTP. It is an interface so
|
|
37
|
+
* a host can supply a non-HTTP implementation without any route group knowing: a fleet orchestrator
|
|
38
|
+
* drives many site instances in-process and can hand over a direct-dispatch transport that never opens a
|
|
39
|
+
* socket, and tests can supply a fake that returns fixtures. Both are the same seam, which is the
|
|
40
|
+
* point.
|
|
41
|
+
*/
|
|
42
|
+
export {};
|
|
43
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/core/transport/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { useConfirmButton } from './ConfirmButton.hooks.js';
|
|
2
|
+
export interface ConfirmButtonProps {
|
|
3
|
+
/** Resting-state label, e.g. "Delete". */
|
|
4
|
+
label: string;
|
|
5
|
+
/** Label shown after the first (arming) click, while awaiting the confirming second click. */
|
|
6
|
+
confirmLabel: string;
|
|
7
|
+
/** Fired on the second (confirming) click. The caller owns in-flight/error state after that —
|
|
8
|
+
* this component only owns the armed/disarmed distinction. */
|
|
9
|
+
onConfirm: () => void;
|
|
10
|
+
/** Applies `.btn-danger`. Reserve for genuinely irreversible actions; a reversible-but-access-
|
|
11
|
+
* affecting action (e.g. Disable) should leave this `false` — see {@link ConfirmTone}'s
|
|
12
|
+
* warning-vs-danger split, which this boolean predates and cannot express. */
|
|
13
|
+
destructive?: boolean;
|
|
14
|
+
/** External in-flight flag (e.g. `rowSavingId === row.id`). Shows `pendingLabel` and disables the
|
|
15
|
+
* control. Not itself cancelable — a click already fired `onConfirm` by the time this is true. */
|
|
16
|
+
pending?: boolean;
|
|
17
|
+
pendingLabel?: string;
|
|
18
|
+
/** Disables the control outright — e.g. a built-in row the server would reject anyway. */
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
className?: string;
|
|
21
|
+
/** Overrides the computed accessible name. Use when several identical-looking controls share a
|
|
22
|
+
* page (one "Delete" per table row) and the row's own text doesn't disambiguate them for a
|
|
23
|
+
* screen-reader user navigating control-by-control rather than row-by-row. */
|
|
24
|
+
ariaLabel?: string;
|
|
25
|
+
/** Injectable seam for the armed/disarmed state hook. Defaults to the real
|
|
26
|
+
* {@link useConfirmButton}; a test can pass a fake here to exercise `ConfirmButton`'s rendering
|
|
27
|
+
* without driving the real Escape/outside-click/blur dismissal logic. */
|
|
28
|
+
useConfirmButton?: typeof useConfirmButton;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Two-click in-place confirm button. First click arms it: swaps the visible label to `confirmLabel`
|
|
32
|
+
* and announces the armed state through a `.visually-hidden` live region (a separate region rather
|
|
33
|
+
* than relying on the button's own label swap, since AT support for announcing a text change inside
|
|
34
|
+
* a live-region-less `<button>`'s accessible name is inconsistent across screen readers). Second
|
|
35
|
+
* click fires `onConfirm` and disarms immediately. Arm/disarm state and its Escape/outside-click/
|
|
36
|
+
* blur dismissal live in {@link useConfirmButton} (injectable via the `useConfirmButton` prop,
|
|
37
|
+
* defaulted to the real implementation, so a test can supply a fake without mocking modules).
|
|
38
|
+
*/
|
|
39
|
+
export declare function ConfirmButton({ useConfirmButton: useConfirmButtonState, ...props }: ConfirmButtonProps): import("react").JSX.Element;
|
|
40
|
+
//# sourceMappingURL=ConfirmButton.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConfirmButton.d.ts","sourceRoot":"","sources":["../../../../src/react/components/ConfirmButton/ConfirmButton.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAsB5D,MAAM,WAAW,kBAAkB;IACjC,0CAA0C;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,8FAA8F;IAC9F,YAAY,EAAE,MAAM,CAAC;IACrB;mEAC+D;IAC/D,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB;;mFAE+E;IAC/E,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;uGACmG;IACnG,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0FAA0F;IAC1F,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;mFAE+E;IAC/E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;8EAE0E;IAC1E,gBAAgB,CAAC,EAAE,OAAO,gBAAgB,CAAC;CAC5C;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,EAAE,gBAAgB,EAAE,qBAAwC,EAAE,GAAG,KAAK,EAAE,EAAE,kBAAkB,+BA0CzH"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file `ConfirmButton`'s armed/disarmed state, split out of the component so it can be swapped for
|
|
3
|
+
* a fake via the `useConfirmButton` prop on `ConfirmButtonProps` — see that prop's doc comment in
|
|
4
|
+
* `ConfirmButton.tsx`.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Owns `ConfirmButton`'s armed/disarmed state: arms on the first click, disarms on Escape, an
|
|
8
|
+
* outside mousedown, or a blur — all without firing `onConfirm`, so a control armed and then
|
|
9
|
+
* abandoned (a slow screen-reader read-through, a misclick landing elsewhere) cannot later be fired
|
|
10
|
+
* by an unrelated click landing on the same screen position. The second click while armed fires
|
|
11
|
+
* `onConfirm` and disarms immediately.
|
|
12
|
+
*
|
|
13
|
+
* @complexity O(1) per render/interaction. The two document-level listeners are attached only while
|
|
14
|
+
* armed, not for the component's whole mounted lifetime.
|
|
15
|
+
*/
|
|
16
|
+
export declare function useConfirmButton(pending: boolean | undefined, disabled: boolean | undefined, onConfirm: () => void): {
|
|
17
|
+
confirming: boolean;
|
|
18
|
+
buttonRef: import("react").RefObject<HTMLButtonElement | null>;
|
|
19
|
+
handleClick: () => void;
|
|
20
|
+
handleBlur: () => void;
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=ConfirmButton.hooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConfirmButton.hooks.d.ts","sourceRoot":"","sources":["../../../../src/react/components/ConfirmButton/ConfirmButton.hooks.tsx"],"names":[],"mappings":"AAEA;;;;GAIG;AAEH;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,SAAS,EAAE,QAAQ,EAAE,OAAO,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,IAAI;;;;;EAyClH"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* @file `ConfirmButton`'s armed/disarmed state, split out of the component so it can be swapped for
|
|
4
|
+
* a fake via the `useConfirmButton` prop on `ConfirmButtonProps` — see that prop's doc comment in
|
|
5
|
+
* `ConfirmButton.tsx`.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Owns `ConfirmButton`'s armed/disarmed state: arms on the first click, disarms on Escape, an
|
|
9
|
+
* outside mousedown, or a blur — all without firing `onConfirm`, so a control armed and then
|
|
10
|
+
* abandoned (a slow screen-reader read-through, a misclick landing elsewhere) cannot later be fired
|
|
11
|
+
* by an unrelated click landing on the same screen position. The second click while armed fires
|
|
12
|
+
* `onConfirm` and disarms immediately.
|
|
13
|
+
*
|
|
14
|
+
* @complexity O(1) per render/interaction. The two document-level listeners are attached only while
|
|
15
|
+
* armed, not for the component's whole mounted lifetime.
|
|
16
|
+
*/
|
|
17
|
+
export function useConfirmButton(pending, disabled, onConfirm) {
|
|
18
|
+
const [confirming, setConfirming] = useState(false);
|
|
19
|
+
const buttonRef = useRef(null);
|
|
20
|
+
// Listeners are scoped to the armed window only — an idle (disarmed) ConfirmButton costs nothing
|
|
21
|
+
// beyond the button element itself, so mounting many of them (one per table row) does not add a
|
|
22
|
+
// standing per-row document listener.
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (!confirming)
|
|
25
|
+
return;
|
|
26
|
+
function disarm() {
|
|
27
|
+
setConfirming(false);
|
|
28
|
+
}
|
|
29
|
+
function onDocMouseDown(e) {
|
|
30
|
+
if (!buttonRef.current?.contains(e.target))
|
|
31
|
+
disarm();
|
|
32
|
+
}
|
|
33
|
+
function onKeyDown(e) {
|
|
34
|
+
if (e.key === 'Escape')
|
|
35
|
+
disarm();
|
|
36
|
+
}
|
|
37
|
+
document.addEventListener('mousedown', onDocMouseDown);
|
|
38
|
+
document.addEventListener('keydown', onKeyDown);
|
|
39
|
+
return () => {
|
|
40
|
+
document.removeEventListener('mousedown', onDocMouseDown);
|
|
41
|
+
document.removeEventListener('keydown', onKeyDown);
|
|
42
|
+
};
|
|
43
|
+
}, [confirming]);
|
|
44
|
+
function handleClick() {
|
|
45
|
+
if (pending || disabled)
|
|
46
|
+
return;
|
|
47
|
+
if (!confirming) {
|
|
48
|
+
setConfirming(true);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
setConfirming(false);
|
|
52
|
+
onConfirm();
|
|
53
|
+
}
|
|
54
|
+
function handleBlur() {
|
|
55
|
+
setConfirming(false);
|
|
56
|
+
}
|
|
57
|
+
return { confirming, buttonRef, handleClick, handleBlur };
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=ConfirmButton.hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConfirmButton.hooks.js","sourceRoot":"","sources":["../../../../src/react/components/ConfirmButton/ConfirmButton.hooks.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEpD;;;;GAIG;AAEH;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAA4B,EAAE,QAA6B,EAAE,SAAqB;IACjH,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IACpD,MAAM,SAAS,GAAG,MAAM,CAAoB,IAAI,CAAC,CAAC;IAElD,iGAAiG;IACjG,gGAAgG;IAChG,sCAAsC;IACtC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,UAAU;YAAE,OAAO;QACxB,SAAS,MAAM;YACb,aAAa,CAAC,KAAK,CAAC,CAAC;QACvB,CAAC;QACD,SAAS,cAAc,CAAC,CAAwB;YAC9C,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,MAAc,CAAC;gBAAE,MAAM,EAAE,CAAC;QAC/D,CAAC;QACD,SAAS,SAAS,CAAC,CAA2B;YAC5C,IAAI,CAAC,CAAC,GAAG,KAAK,QAAQ;gBAAE,MAAM,EAAE,CAAC;QACnC,CAAC;QACD,QAAQ,CAAC,gBAAgB,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC;QACvD,QAAQ,CAAC,gBAAgB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;QAChD,OAAO,GAAG,EAAE;YACV,QAAQ,CAAC,mBAAmB,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC;YAC1D,QAAQ,CAAC,mBAAmB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;QACrD,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC;IAEjB,SAAS,WAAW;QAClB,IAAI,OAAO,IAAI,QAAQ;YAAE,OAAO;QAChC,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,aAAa,CAAC,IAAI,CAAC,CAAC;YACpB,OAAO;QACT,CAAC;QACD,aAAa,CAAC,KAAK,CAAC,CAAC;QACrB,SAAS,EAAE,CAAC;IACd,CAAC;IAED,SAAS,UAAU;QACjB,aAAa,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;IAED,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC;AAC5D,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useConfirmButton } from './ConfirmButton.hooks.js';
|
|
3
|
+
/**
|
|
4
|
+
* @file Shared two-click, in-place confirmation control for destructive or access-affecting
|
|
5
|
+
* actions.
|
|
6
|
+
*
|
|
7
|
+
* Exists because confirmation coverage is otherwise a coin flip: some screens guard a delete, some
|
|
8
|
+
* ship a bare single-click Disable, and the ones that do guard each reinvent the interaction. This
|
|
9
|
+
* is the one shape every screen can import instead of rebuilding.
|
|
10
|
+
*
|
|
11
|
+
* Chosen over a `window.confirm` wrapper: `window.confirm` blocks the whole tab and cannot carry
|
|
12
|
+
* destructive-vs-warning styling or a screen-reader-specific announcement. Chosen over a shared
|
|
13
|
+
* modal for the common case because the in-place shape needs no new markup at all — it renders as
|
|
14
|
+
* an ordinary `<button>`, optionally with `.btn-danger`.
|
|
15
|
+
*
|
|
16
|
+
* Not a replacement for {@link ConfirmDialog}: an operation with real context to disclose ("this
|
|
17
|
+
* restores a snapshot from 3 days ago and drops 41 entries") needs a modal that can show it. This
|
|
18
|
+
* is for the common case — a single row action that would otherwise be bare or a `window.confirm`.
|
|
19
|
+
*/
|
|
20
|
+
const DEFAULT_PENDING_LABEL = '…';
|
|
21
|
+
/**
|
|
22
|
+
* Two-click in-place confirm button. First click arms it: swaps the visible label to `confirmLabel`
|
|
23
|
+
* and announces the armed state through a `.visually-hidden` live region (a separate region rather
|
|
24
|
+
* than relying on the button's own label swap, since AT support for announcing a text change inside
|
|
25
|
+
* a live-region-less `<button>`'s accessible name is inconsistent across screen readers). Second
|
|
26
|
+
* click fires `onConfirm` and disarms immediately. Arm/disarm state and its Escape/outside-click/
|
|
27
|
+
* blur dismissal live in {@link useConfirmButton} (injectable via the `useConfirmButton` prop,
|
|
28
|
+
* defaulted to the real implementation, so a test can supply a fake without mocking modules).
|
|
29
|
+
*/
|
|
30
|
+
export function ConfirmButton({ useConfirmButton: useConfirmButtonState = useConfirmButton, ...props }) {
|
|
31
|
+
const { confirming, buttonRef, handleClick, handleBlur } = useConfirmButtonState(props.pending, props.disabled, props.onConfirm);
|
|
32
|
+
const label = props.pending
|
|
33
|
+
? (props.pendingLabel ?? DEFAULT_PENDING_LABEL)
|
|
34
|
+
: confirming
|
|
35
|
+
? props.confirmLabel
|
|
36
|
+
: props.label;
|
|
37
|
+
const className = [props.className, props.destructive ? 'btn-danger' : null].filter(Boolean).join(' ') || undefined;
|
|
38
|
+
return (_jsxs(_Fragment, { children: [_jsx("button", { ref: buttonRef, type: "button", className: className, "data-armed": confirming ? 'true' : undefined, disabled: props.disabled || props.pending, onClick: handleClick, onBlur: handleBlur, "aria-label": props.ariaLabel, children: label }), _jsx("span", { className: "visually-hidden", role: "status", "aria-live": "polite", children: confirming ? `Press "${props.confirmLabel}" to confirm, or press Escape to cancel.` : '' })] }));
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=ConfirmButton.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConfirmButton.js","sourceRoot":"","sources":["../../../../src/react/components/ConfirmButton/ConfirmButton.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAE5D;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,qBAAqB,GAAG,GAAG,CAAC;AA+BlC;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAAC,EAAE,gBAAgB,EAAE,qBAAqB,GAAG,gBAAgB,EAAE,GAAG,KAAK,EAAsB;IACxH,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,GAAG,qBAAqB,CAC9E,KAAK,CAAC,OAAO,EACb,KAAK,CAAC,QAAQ,EACd,KAAK,CAAC,SAAS,CAChB,CAAC;IAEF,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO;QACzB,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,IAAI,qBAAqB,CAAC;QAC/C,CAAC,CAAC,UAAU;YACV,CAAC,CAAC,KAAK,CAAC,YAAY;YACpB,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC;IAClB,MAAM,SAAS,GAAG,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC;IAEpH,OAAO,CACL,8BAOE,iBACE,GAAG,EAAE,SAAS,EACd,IAAI,EAAC,QAAQ,EACb,SAAS,EAAE,SAAS,gBACR,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,EAC3C,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,OAAO,EACzC,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,UAAU,gBACN,KAAK,CAAC,SAAS,YAE1B,KAAK,GACC,EAIT,eAAM,SAAS,EAAC,iBAAiB,EAAC,IAAI,EAAC,QAAQ,eAAW,QAAQ,YAC/D,UAAU,CAAC,CAAC,CAAC,UAAU,KAAK,CAAC,YAAY,0CAA0C,CAAC,CAAC,CAAC,EAAE,GACpF,IACN,CACJ,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface ConfirmButtonProps {
|
|
2
|
+
/** Resting-state label, e.g. "Delete". */
|
|
3
|
+
label: string;
|
|
4
|
+
/** Label shown after the first (arming) click, while awaiting the confirming second click. */
|
|
5
|
+
confirmLabel: string;
|
|
6
|
+
/** Fired on the second (confirming) click. The caller owns in-flight/error state after that —
|
|
7
|
+
* this component only owns the armed/disarmed distinction. */
|
|
8
|
+
onConfirm: () => void;
|
|
9
|
+
/** Applies `.btn-danger`. Reserve for genuinely irreversible actions; a reversible-but-access-
|
|
10
|
+
* affecting action (e.g. Disable) should leave this `false` — see {@link ConfirmTone}'s
|
|
11
|
+
* warning-vs-danger split, which this boolean predates and cannot express. */
|
|
12
|
+
destructive?: boolean;
|
|
13
|
+
/** External in-flight flag (e.g. `rowSavingId === row.id`). Shows `pendingLabel` and disables the
|
|
14
|
+
* control. Not itself cancelable — a click already fired `onConfirm` by the time this is true. */
|
|
15
|
+
pending?: boolean;
|
|
16
|
+
pendingLabel?: string;
|
|
17
|
+
/** Disables the control outright — e.g. a built-in row the server would reject anyway. */
|
|
18
|
+
disabled?: boolean;
|
|
19
|
+
className?: string;
|
|
20
|
+
/** Overrides the computed accessible name. Use when several identical-looking controls share a
|
|
21
|
+
* page (one "Delete" per table row) and the row's own text doesn't disambiguate them for a
|
|
22
|
+
* screen-reader user navigating control-by-control rather than row-by-row. */
|
|
23
|
+
ariaLabel?: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Two-click in-place confirm button. First click arms it: swaps the visible label to `confirmLabel`
|
|
27
|
+
* and announces the armed state through a `.visually-hidden` live region (a separate region rather
|
|
28
|
+
* than relying on the button's own label swap, since AT support for announcing a text change inside
|
|
29
|
+
* a live-region-less `<button>`'s accessible name is inconsistent across screen readers). Second
|
|
30
|
+
* click fires `onConfirm` and disarms immediately. Losing focus, an outside click, or Escape
|
|
31
|
+
* disarms without firing, so a control armed and then abandoned (a slow screen-reader read-through,
|
|
32
|
+
* a misclick landing elsewhere) cannot later be fired by an unrelated click landing on the same
|
|
33
|
+
* screen position.
|
|
34
|
+
*
|
|
35
|
+
* @complexity O(1) per render/interaction. The two document-level listeners are attached only while
|
|
36
|
+
* armed, not for the component's whole mounted lifetime.
|
|
37
|
+
*/
|
|
38
|
+
export declare function ConfirmButton(props: ConfirmButtonProps): import("react").JSX.Element;
|
|
39
|
+
//# sourceMappingURL=ConfirmButton.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConfirmButton.d.ts","sourceRoot":"","sources":["../../../src/react/components/ConfirmButton.tsx"],"names":[],"mappings":"AAsBA,MAAM,WAAW,kBAAkB;IACjC,0CAA0C;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,8FAA8F;IAC9F,YAAY,EAAE,MAAM,CAAC;IACrB;mEAC+D;IAC/D,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB;;mFAE+E;IAC/E,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;uGACmG;IACnG,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0FAA0F;IAC1F,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;mFAE+E;IAC/E,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,+BAuEtD"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
|
+
/**
|
|
4
|
+
* @file Shared two-click, in-place confirmation control for destructive or access-affecting
|
|
5
|
+
* actions.
|
|
6
|
+
*
|
|
7
|
+
* Exists because confirmation coverage is otherwise a coin flip: some screens guard a delete, some
|
|
8
|
+
* ship a bare single-click Disable, and the ones that do guard each reinvent the interaction. This
|
|
9
|
+
* is the one shape every screen can import instead of rebuilding.
|
|
10
|
+
*
|
|
11
|
+
* Chosen over a `window.confirm` wrapper: `window.confirm` blocks the whole tab and cannot carry
|
|
12
|
+
* destructive-vs-warning styling or a screen-reader-specific announcement. Chosen over a shared
|
|
13
|
+
* modal for the common case because the in-place shape needs no new markup at all — it renders as
|
|
14
|
+
* an ordinary `<button>`, optionally with `.btn-danger`.
|
|
15
|
+
*
|
|
16
|
+
* Not a replacement for {@link ConfirmDialog}: an operation with real context to disclose ("this
|
|
17
|
+
* restores a snapshot from 3 days ago and drops 41 entries") needs a modal that can show it. This
|
|
18
|
+
* is for the common case — a single row action that would otherwise be bare or a `window.confirm`.
|
|
19
|
+
*/
|
|
20
|
+
const DEFAULT_PENDING_LABEL = '…';
|
|
21
|
+
/**
|
|
22
|
+
* Two-click in-place confirm button. First click arms it: swaps the visible label to `confirmLabel`
|
|
23
|
+
* and announces the armed state through a `.visually-hidden` live region (a separate region rather
|
|
24
|
+
* than relying on the button's own label swap, since AT support for announcing a text change inside
|
|
25
|
+
* a live-region-less `<button>`'s accessible name is inconsistent across screen readers). Second
|
|
26
|
+
* click fires `onConfirm` and disarms immediately. Losing focus, an outside click, or Escape
|
|
27
|
+
* disarms without firing, so a control armed and then abandoned (a slow screen-reader read-through,
|
|
28
|
+
* a misclick landing elsewhere) cannot later be fired by an unrelated click landing on the same
|
|
29
|
+
* screen position.
|
|
30
|
+
*
|
|
31
|
+
* @complexity O(1) per render/interaction. The two document-level listeners are attached only while
|
|
32
|
+
* armed, not for the component's whole mounted lifetime.
|
|
33
|
+
*/
|
|
34
|
+
export function ConfirmButton(props) {
|
|
35
|
+
const [confirming, setConfirming] = useState(false);
|
|
36
|
+
const buttonRef = useRef(null);
|
|
37
|
+
// Listeners are scoped to the armed window only — an idle (disarmed) ConfirmButton costs nothing
|
|
38
|
+
// beyond the button element itself, so mounting many of them (one per table row) does not add a
|
|
39
|
+
// standing per-row document listener.
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (!confirming)
|
|
42
|
+
return;
|
|
43
|
+
function disarm() {
|
|
44
|
+
setConfirming(false);
|
|
45
|
+
}
|
|
46
|
+
function onDocMouseDown(e) {
|
|
47
|
+
if (!buttonRef.current?.contains(e.target))
|
|
48
|
+
disarm();
|
|
49
|
+
}
|
|
50
|
+
function onKeyDown(e) {
|
|
51
|
+
if (e.key === 'Escape')
|
|
52
|
+
disarm();
|
|
53
|
+
}
|
|
54
|
+
document.addEventListener('mousedown', onDocMouseDown);
|
|
55
|
+
document.addEventListener('keydown', onKeyDown);
|
|
56
|
+
return () => {
|
|
57
|
+
document.removeEventListener('mousedown', onDocMouseDown);
|
|
58
|
+
document.removeEventListener('keydown', onKeyDown);
|
|
59
|
+
};
|
|
60
|
+
}, [confirming]);
|
|
61
|
+
function handleClick() {
|
|
62
|
+
if (props.pending || props.disabled)
|
|
63
|
+
return;
|
|
64
|
+
if (!confirming) {
|
|
65
|
+
setConfirming(true);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
setConfirming(false);
|
|
69
|
+
props.onConfirm();
|
|
70
|
+
}
|
|
71
|
+
const label = props.pending
|
|
72
|
+
? (props.pendingLabel ?? DEFAULT_PENDING_LABEL)
|
|
73
|
+
: confirming
|
|
74
|
+
? props.confirmLabel
|
|
75
|
+
: props.label;
|
|
76
|
+
const className = [props.className, props.destructive ? 'btn-danger' : null].filter(Boolean).join(' ') || undefined;
|
|
77
|
+
return (_jsxs(_Fragment, { children: [_jsx("button", { ref: buttonRef, type: "button", className: className, "data-armed": confirming ? 'true' : undefined, disabled: props.disabled || props.pending, onClick: handleClick, onBlur: () => setConfirming(false), "aria-label": props.ariaLabel, children: label }), _jsx("span", { className: "visually-hidden", role: "status", "aria-live": "polite", children: confirming ? `Press "${props.confirmLabel}" to confirm, or press Escape to cancel.` : '' })] }));
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=ConfirmButton.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConfirmButton.js","sourceRoot":"","sources":["../../../src/react/components/ConfirmButton.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAEpD;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,qBAAqB,GAAG,GAAG,CAAC;AA2BlC;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,aAAa,CAAC,KAAyB;IACrD,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IACpD,MAAM,SAAS,GAAG,MAAM,CAAoB,IAAI,CAAC,CAAC;IAElD,iGAAiG;IACjG,gGAAgG;IAChG,sCAAsC;IACtC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,UAAU;YAAE,OAAO;QACxB,SAAS,MAAM;YACb,aAAa,CAAC,KAAK,CAAC,CAAC;QACvB,CAAC;QACD,SAAS,cAAc,CAAC,CAAwB;YAC9C,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,MAAc,CAAC;gBAAE,MAAM,EAAE,CAAC;QAC/D,CAAC;QACD,SAAS,SAAS,CAAC,CAA2B;YAC5C,IAAI,CAAC,CAAC,GAAG,KAAK,QAAQ;gBAAE,MAAM,EAAE,CAAC;QACnC,CAAC;QACD,QAAQ,CAAC,gBAAgB,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC;QACvD,QAAQ,CAAC,gBAAgB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;QAChD,OAAO,GAAG,EAAE;YACV,QAAQ,CAAC,mBAAmB,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC;YAC1D,QAAQ,CAAC,mBAAmB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;QACrD,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC;IAEjB,SAAS,WAAW;QAClB,IAAI,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,QAAQ;YAAE,OAAO;QAC5C,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,aAAa,CAAC,IAAI,CAAC,CAAC;YACpB,OAAO;QACT,CAAC;QACD,aAAa,CAAC,KAAK,CAAC,CAAC;QACrB,KAAK,CAAC,SAAS,EAAE,CAAC;IACpB,CAAC;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO;QACzB,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,IAAI,qBAAqB,CAAC;QAC/C,CAAC,CAAC,UAAU;YACV,CAAC,CAAC,KAAK,CAAC,YAAY;YACpB,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC;IAClB,MAAM,SAAS,GAAG,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC;IAEpH,OAAO,CACL,8BAOE,iBACE,GAAG,EAAE,SAAS,EACd,IAAI,EAAC,QAAQ,EACb,SAAS,EAAE,SAAS,gBACR,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,EAC3C,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,OAAO,EACzC,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,gBACtB,KAAK,CAAC,SAAS,YAE1B,KAAK,GACC,EAIT,eAAM,SAAS,EAAC,iBAAiB,EAAC,IAAI,EAAC,QAAQ,eAAW,QAAQ,YAC/D,UAAU,CAAC,CAAC,CAAC,UAAU,KAAK,CAAC,YAAY,0CAA0C,CAAC,CAAC,CAAC,EAAE,GACpF,IACN,CACJ,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type ConfirmTone } from '../../types.js';
|
|
3
|
+
import { type UseConfirmDialog } from './ConfirmDialog.hooks.js';
|
|
4
|
+
/**
|
|
5
|
+
* @file Shared modal confirmation primitive — the replacement for `window.confirm`, which blocks
|
|
6
|
+
* the whole tab, cannot carry destructive-vs-neutral styling, and reads as a browser artifact
|
|
7
|
+
* rather than part of the product.
|
|
8
|
+
*
|
|
9
|
+
* Built on the native `<dialog>` element (`showModal()`/`close()`) rather than a plain-`<div>`
|
|
10
|
+
* overlay + backdrop idiom: that shape hand-rolls focus trapping, Escape handling, and a backdrop
|
|
11
|
+
* element, all of which the browser's own top layer gives a real `<dialog>` for free, including
|
|
12
|
+
* correct stacking above everything else on the page without a chosen `z-index`. The `showModal`/
|
|
13
|
+
* `close` calls themselves, and the jsdom fallback they need, live in `ConfirmDialog.hooks.tsx` —
|
|
14
|
+
* see that file's doc comment.
|
|
15
|
+
*
|
|
16
|
+
* Unstyled, like everything in this layer: the `.confirm-dialog` / `.btn-secondary` /
|
|
17
|
+
* `.btn-danger` / `.btn-warning` class names are emitted for the host stylesheet to define.
|
|
18
|
+
*
|
|
19
|
+
* ## Agent handles
|
|
20
|
+
*
|
|
21
|
+
* Given `agentHandle="delete-role"` this publishes:
|
|
22
|
+
*
|
|
23
|
+
* | element | handle | role |
|
|
24
|
+
* |---|---|---|
|
|
25
|
+
* | the confirm action | `delete-role-confirm` | `button` |
|
|
26
|
+
* | the cancel/dismiss action | `delete-role-cancel` | `button` |
|
|
27
|
+
*
|
|
28
|
+
* Both segments are literals this component chooses itself, never host data — unlike `RowMenu`'s
|
|
29
|
+
* per-item keys (arbitrary host strings that can collide after slugifying), `confirm`/`cancel`
|
|
30
|
+
* need no sanitizing. `agentHandle` itself is NOT sanitized either: it is the caller's own explicit
|
|
31
|
+
* choice of name, and a bad one should fail loudly at first render rather than silently answer to a
|
|
32
|
+
* handle the caller never wrote. Omit `agentHandle` and no `data-agent-*` markup is emitted at all.
|
|
33
|
+
*
|
|
34
|
+
* Both handles are present in the DOM as soon as `agentHandle` is supplied, regardless of `open` —
|
|
35
|
+
* this component stays mounted and toggles the native `<dialog>`'s open state rather than being
|
|
36
|
+
* conditionally rendered by its caller (see `useConfirmDialog`'s doc comment), so there is no
|
|
37
|
+
* "closed" render that omits them, the same reasoning `RowMenu`'s always-visible trigger handle
|
|
38
|
+
* documents for itself.
|
|
39
|
+
*
|
|
40
|
+
* This component renders no typed-confirmation input today (no "type DELETE to confirm" field) —
|
|
41
|
+
* if one is added later it needs its own handle under this same scheme (e.g. `<base>-confirm-input`,
|
|
42
|
+
* role `field`).
|
|
43
|
+
*
|
|
44
|
+
* Labels state both the action and its consequence tier, derived from this component's own
|
|
45
|
+
* three-tier `ConfirmTone` vocabulary rather than reparsed from `title`/`body`: `body` is arbitrary
|
|
46
|
+
* `ReactNode` (a caller can pass JSX), not guaranteed to be a string at all, so it cannot be safely
|
|
47
|
+
* folded into agent-facing text — see {@link confirmDialogConsequencePhrase}.
|
|
48
|
+
*/
|
|
49
|
+
export interface ConfirmDialogProps {
|
|
50
|
+
open: boolean;
|
|
51
|
+
title: string;
|
|
52
|
+
body: ReactNode;
|
|
53
|
+
confirmLabel: string;
|
|
54
|
+
/** @default "Cancel" */
|
|
55
|
+
cancelLabel?: string;
|
|
56
|
+
/** Applies `.btn-warning`/`.btn-danger` to the confirm action. Defaults to `"default"` (no class,
|
|
57
|
+
* the plain primary button). Wins over `destructive` below when both are passed. */
|
|
58
|
+
tone?: ConfirmTone;
|
|
59
|
+
/** @deprecated Use `tone: "danger"` instead — this only ever expressed the danger tier, and the
|
|
60
|
+
* vocabulary has a second one (`"warning"`) this boolean cannot reach. Kept working (mapped to
|
|
61
|
+
* `tone: "danger"` when `tone` is not set) for existing callers rather than a breaking rename. */
|
|
62
|
+
destructive?: boolean;
|
|
63
|
+
/** External in-flight flag. Disables both actions and blocks Escape/backdrop dismissal so a
|
|
64
|
+
* request already underway cannot be raced by a second dismiss. */
|
|
65
|
+
pending?: boolean;
|
|
66
|
+
onConfirm: () => void;
|
|
67
|
+
onCancel: () => void;
|
|
68
|
+
/** Injectable seam for the dialog's open/close and focus-management hook. Defaults to the real
|
|
69
|
+
* {@link useConfirmDialog}; a test can pass a fake here to exercise `ConfirmDialog`'s rendering
|
|
70
|
+
* without invoking the native `<dialog>` methods or DOM focus calls at all.
|
|
71
|
+
*
|
|
72
|
+
* Typed against the explicit `UseConfirmDialog` contract rather than `typeof useConfirmDialog`:
|
|
73
|
+
* see `ConfirmDialogController`'s doc comment for why binding a public prop to a concrete
|
|
74
|
+
* implementation's inferred return object is a coupling rather than a convenience. */
|
|
75
|
+
useDialog?: UseConfirmDialog;
|
|
76
|
+
/** This dialog's own agent handle — see this file's "Agent handles" doc comment for the full
|
|
77
|
+
* scheme. Omit and no `data-agent-*` markup is emitted at all. */
|
|
78
|
+
agentHandle?: string;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Controlled modal confirm — renders the `<dialog>` markup and delegates its open/close and focus
|
|
82
|
+
* lifecycle to {@link useConfirmDialog} (injectable via the `useDialog` prop, defaulted to the real
|
|
83
|
+
* implementation, so a test can supply a fake without mocking modules).
|
|
84
|
+
*
|
|
85
|
+
* Both actions are plain `type="button"` (no `<form method="dialog">`, no `type="submit"`), so there
|
|
86
|
+
* is no browser-assigned "default button" for Enter to reach for at all; confirm can only ever fire
|
|
87
|
+
* from an explicit click or explicit Tab-then-Enter onto it.
|
|
88
|
+
*/
|
|
89
|
+
export declare function ConfirmDialog({ useDialog, agentHandle: baseHandle, ...props }: ConfirmDialogProps): import("react").JSX.Element;
|
|
90
|
+
//# sourceMappingURL=ConfirmDialog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConfirmDialog.d.ts","sourceRoot":"","sources":["../../../../src/react/components/ConfirmDialog/ConfirmDialog.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,EAA8B,KAAK,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC9E,OAAO,EAAoB,KAAK,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAEnF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AAEH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,SAAS,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,wBAAwB;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;yFACqF;IACrF,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB;;uGAEmG;IACnG,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;wEACoE;IACpE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB;;;;;;2FAMuF;IACvF,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B;uEACmE;IACnE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AA2ED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,EAAE,SAA4B,EAAE,WAAW,EAAE,UAAU,EAAE,GAAG,KAAK,EAAE,EAAE,kBAAkB,+BAiDpH"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type MouseEvent, type RefObject, type SyntheticEvent } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* @file `ConfirmDialog`'s open/close and focus-management state, split out of the component so it
|
|
4
|
+
* can be swapped for a fake via the `useDialog` prop on `ConfirmDialogProps` — see that prop's doc
|
|
5
|
+
* comment in `ConfirmDialog.tsx`.
|
|
6
|
+
*
|
|
7
|
+
* `dialog.showModal()`/`dialog.close()` are guarded by a `typeof` check rather than called
|
|
8
|
+
* unconditionally: jsdom (this package's React test environment, verified directly rather than
|
|
9
|
+
* assumed) does not implement either method at all — calling them throws `TypeError`. The fallback
|
|
10
|
+
* branch below toggles the plain `open` attribute instead, which no browser shipping a real
|
|
11
|
+
* `<dialog>` ever takes (the `showModal`/`close` branch always wins there), so this only changes
|
|
12
|
+
* behavior under jsdom, where it keeps the dialog mountable and its content queryable.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* What `ConfirmDialog` needs back from whatever drives its dialog lifecycle: the two refs it
|
|
16
|
+
* attaches and the two handlers it wires, plus the id its `aria-labelledby` points at.
|
|
17
|
+
*
|
|
18
|
+
* Declared as its own interface rather than left to be inferred from {@link useConfirmDialog}'s
|
|
19
|
+
* return object. That inference is what made the injectable `useDialog` prop couple to the concrete
|
|
20
|
+
* implementation: `typeof useConfirmDialog` meant every internal detail of the default hook's shape
|
|
21
|
+
* became part of the component's PUBLIC contract, so adding a field for the default's own
|
|
22
|
+
* convenience would silently break every substitute a consumer had already written. Naming the
|
|
23
|
+
* contract inverts that — the hook now has to satisfy the component, not the other way round.
|
|
24
|
+
*/
|
|
25
|
+
export interface ConfirmDialogController {
|
|
26
|
+
/** Id for the dialog's `<h2>`, unique per mounted instance — see {@link useConfirmDialog}. */
|
|
27
|
+
titleId: string;
|
|
28
|
+
dialogRef: RefObject<HTMLDialogElement | null>;
|
|
29
|
+
cancelRef: RefObject<HTMLButtonElement | null>;
|
|
30
|
+
handleNativeCancel: (event: SyntheticEvent<HTMLDialogElement>) => void;
|
|
31
|
+
handleBackdropClick: (event: MouseEvent<HTMLDialogElement>) => void;
|
|
32
|
+
}
|
|
33
|
+
/** The signature `ConfirmDialogProps.useDialog` accepts. Any hook matching this can drive the
|
|
34
|
+
* component; it does not have to be {@link useConfirmDialog}, or even use React state at all. */
|
|
35
|
+
export type UseConfirmDialog = (open: boolean, pending: boolean | undefined, onCancel: () => void) => ConfirmDialogController;
|
|
36
|
+
/**
|
|
37
|
+
* Owns the `<dialog>` element's open/close lifecycle and focus management for `ConfirmDialog`.
|
|
38
|
+
*
|
|
39
|
+
* The caller keeps `ConfirmDialog` mounted and toggles `open` — it is never conditionally rendered
|
|
40
|
+
* by its parent — so the effect below has a stable `<dialog>` element to call `showModal()`/
|
|
41
|
+
* `close()` on and to restore focus through when it closes.
|
|
42
|
+
*
|
|
43
|
+
* Focus moves to the *cancel* action on open, not confirm — an operator whose first keystroke after
|
|
44
|
+
* a slow read-through is Enter should land on the safe action, not the destructive one.
|
|
45
|
+
*
|
|
46
|
+
* @complexity O(1) per open/close transition — one `showModal`/`close` call and one focus move.
|
|
47
|
+
*/
|
|
48
|
+
export declare function useConfirmDialog(open: boolean, pending: boolean | undefined, onCancel: () => void): ConfirmDialogController;
|
|
49
|
+
//# sourceMappingURL=ConfirmDialog.hooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConfirmDialog.hooks.d.ts","sourceRoot":"","sources":["../../../../src/react/components/ConfirmDialog/ConfirmDialog.hooks.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA4B,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,OAAO,CAAC;AAEvG;;;;;;;;;;;GAWG;AAEH;;;;;;;;;;GAUG;AACH,MAAM,WAAW,uBAAuB;IACtC,8FAA8F;IAC9F,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,SAAS,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;IAC/C,SAAS,EAAE,SAAS,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC;IAC/C,kBAAkB,EAAE,CAAC,KAAK,EAAE,cAAc,CAAC,iBAAiB,CAAC,KAAK,IAAI,CAAC;IACvE,mBAAmB,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,iBAAiB,CAAC,KAAK,IAAI,CAAC;CACrE;AAED;kGACkG;AAClG,MAAM,MAAM,gBAAgB,GAAG,CAC7B,IAAI,EAAE,OAAO,EACb,OAAO,EAAE,OAAO,GAAG,SAAS,EAC5B,QAAQ,EAAE,MAAM,IAAI,KACjB,uBAAuB,CAAC;AAE7B;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,OAAO,EACb,OAAO,EAAE,OAAO,GAAG,SAAS,EAC5B,QAAQ,EAAE,MAAM,IAAI,GACnB,uBAAuB,CA4DzB"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { useEffect, useId, useRef } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Owns the `<dialog>` element's open/close lifecycle and focus management for `ConfirmDialog`.
|
|
4
|
+
*
|
|
5
|
+
* The caller keeps `ConfirmDialog` mounted and toggles `open` — it is never conditionally rendered
|
|
6
|
+
* by its parent — so the effect below has a stable `<dialog>` element to call `showModal()`/
|
|
7
|
+
* `close()` on and to restore focus through when it closes.
|
|
8
|
+
*
|
|
9
|
+
* Focus moves to the *cancel* action on open, not confirm — an operator whose first keystroke after
|
|
10
|
+
* a slow read-through is Enter should land on the safe action, not the destructive one.
|
|
11
|
+
*
|
|
12
|
+
* @complexity O(1) per open/close transition — one `showModal`/`close` call and one focus move.
|
|
13
|
+
*/
|
|
14
|
+
export function useConfirmDialog(open, pending, onCancel) {
|
|
15
|
+
// `useId()`, not a string literal — a hardcoded id breaks the moment a screen mounts two
|
|
16
|
+
// `ConfirmDialog`s at once (a delete-role and a delete-policy confirm on one page, both
|
|
17
|
+
// stay-mounted/toggle-`open` per this hook's own doc comment above): two `<h2>`s share one
|
|
18
|
+
// id, and `aria-labelledby` resolves via `getElementById`, which always returns the FIRST match
|
|
19
|
+
// in document order regardless of which dialog is actually open — a screen reader announcing
|
|
20
|
+
// "Delete role?" while the operator is about to confirm "Delete policy?", on a destructive
|
|
21
|
+
// action. `useId()` gives every mounted instance its own id for free.
|
|
22
|
+
const titleId = useId();
|
|
23
|
+
const dialogRef = useRef(null);
|
|
24
|
+
const cancelRef = useRef(null);
|
|
25
|
+
// Captured at the moment `open` flips true, before focus moves into the dialog — the element that
|
|
26
|
+
// had focus then is, by construction, whatever triggered this dialog (a `RowMenu` item's
|
|
27
|
+
// "Delete", a plain trigger button, …). Restored on close rather than left wherever the browser's
|
|
28
|
+
// own modal-focus algorithm happened to land (its default without this is `<body>`, which drops a
|
|
29
|
+
// keyboard user back to the top of the page).
|
|
30
|
+
const triggerRef = useRef(null);
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
const dialog = dialogRef.current;
|
|
33
|
+
if (!dialog)
|
|
34
|
+
return;
|
|
35
|
+
if (open) {
|
|
36
|
+
triggerRef.current = document.activeElement;
|
|
37
|
+
if (typeof dialog.showModal === 'function') {
|
|
38
|
+
if (!dialog.open)
|
|
39
|
+
dialog.showModal();
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
dialog.setAttribute('open', '');
|
|
43
|
+
}
|
|
44
|
+
cancelRef.current?.focus();
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
if (typeof dialog.close === 'function') {
|
|
48
|
+
if (dialog.open)
|
|
49
|
+
dialog.close();
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
dialog.removeAttribute('open');
|
|
53
|
+
}
|
|
54
|
+
if (triggerRef.current instanceof HTMLElement)
|
|
55
|
+
triggerRef.current.focus();
|
|
56
|
+
}
|
|
57
|
+
}, [open]);
|
|
58
|
+
function handleNativeCancel(e) {
|
|
59
|
+
// Fires on Escape while a real `showModal()`-opened dialog has focus. Always prevented: the
|
|
60
|
+
// effect above is the single source of truth for open/closed (driven by `open`), so
|
|
61
|
+
// letting the browser close the element on its own would desync DOM state from React state.
|
|
62
|
+
// Escape still closes the dialog — it just does so by routing through `onCancel`, same as a
|
|
63
|
+
// Cancel-button click, so the caller's `open` state (and therefore this same effect) is what
|
|
64
|
+
// actually calls `dialog.close()`.
|
|
65
|
+
e.preventDefault();
|
|
66
|
+
if (pending)
|
|
67
|
+
return;
|
|
68
|
+
onCancel();
|
|
69
|
+
}
|
|
70
|
+
function handleBackdropClick(e) {
|
|
71
|
+
// A `<dialog>` element's own box is sized to its content, not the viewport — a click that lands
|
|
72
|
+
// on the `<dialog>` element itself (as opposed to one of its children) is therefore a click on
|
|
73
|
+
// the backdrop area outside that content box.
|
|
74
|
+
if (pending)
|
|
75
|
+
return;
|
|
76
|
+
if (e.target === dialogRef.current)
|
|
77
|
+
onCancel();
|
|
78
|
+
}
|
|
79
|
+
return { titleId, dialogRef, cancelRef, handleNativeCancel, handleBackdropClick };
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=ConfirmDialog.hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConfirmDialog.hooks.js","sourceRoot":"","sources":["../../../../src/react/components/ConfirmDialog/ConfirmDialog.hooks.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAwD,MAAM,OAAO,CAAC;AA2CvG;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,gBAAgB,CAC9B,IAAa,EACb,OAA4B,EAC5B,QAAoB;IAEpB,yFAAyF;IACzF,wFAAwF;IACxF,2FAA2F;IAC3F,gGAAgG;IAChG,6FAA6F;IAC7F,2FAA2F;IAC3F,sEAAsE;IACtE,MAAM,OAAO,GAAG,KAAK,EAAE,CAAC;IACxB,MAAM,SAAS,GAAG,MAAM,CAAoB,IAAI,CAAC,CAAC;IAClD,MAAM,SAAS,GAAG,MAAM,CAAoB,IAAI,CAAC,CAAC;IAClD,kGAAkG;IAClG,yFAAyF;IACzF,kGAAkG;IAClG,kGAAkG;IAClG,8CAA8C;IAC9C,MAAM,UAAU,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAC;IAEhD,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC;QACjC,IAAI,CAAC,MAAM;YAAE,OAAO;QACpB,IAAI,IAAI,EAAE,CAAC;YACT,UAAU,CAAC,OAAO,GAAG,QAAQ,CAAC,aAAa,CAAC;YAC5C,IAAI,OAAO,MAAM,CAAC,SAAS,KAAK,UAAU,EAAE,CAAC;gBAC3C,IAAI,CAAC,MAAM,CAAC,IAAI;oBAAE,MAAM,CAAC,SAAS,EAAE,CAAC;YACvC,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,YAAY,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;YAClC,CAAC;YACD,SAAS,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC;QAC7B,CAAC;aAAM,CAAC;YACN,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;gBACvC,IAAI,MAAM,CAAC,IAAI;oBAAE,MAAM,CAAC,KAAK,EAAE,CAAC;YAClC,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;YACjC,CAAC;YACD,IAAI,UAAU,CAAC,OAAO,YAAY,WAAW;gBAAE,UAAU,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QAC5E,CAAC;IACH,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IAEX,SAAS,kBAAkB,CAAC,CAAoC;QAC9D,4FAA4F;QAC5F,oFAAoF;QACpF,4FAA4F;QAC5F,4FAA4F;QAC5F,6FAA6F;QAC7F,mCAAmC;QACnC,CAAC,CAAC,cAAc,EAAE,CAAC;QACnB,IAAI,OAAO;YAAE,OAAO;QACpB,QAAQ,EAAE,CAAC;IACb,CAAC;IAED,SAAS,mBAAmB,CAAC,CAAgC;QAC3D,gGAAgG;QAChG,+FAA+F;QAC/F,8CAA8C;QAC9C,IAAI,OAAO;YAAE,OAAO;QACpB,IAAI,CAAC,CAAC,MAAM,KAAK,SAAS,CAAC,OAAO;YAAE,QAAQ,EAAE,CAAC;IACjD,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,CAAC;AACpF,CAAC"}
|