@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,92 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import {} from 'react';
|
|
3
|
+
import { agentHandle } from '@jini-ai/agentic';
|
|
4
|
+
import { resolveTone, toneClassName } from '../../types.js';
|
|
5
|
+
import { useConfirmDialog } from './ConfirmDialog.hooks.js';
|
|
6
|
+
const DEFAULT_CANCEL_LABEL = 'Cancel';
|
|
7
|
+
/** Sub-handle segments appended to the caller's base — literals this component chooses itself,
|
|
8
|
+
* never host data (see this file's "Agent handles" doc comment for why that means neither needs
|
|
9
|
+
* sanitizing, unlike `RowMenu`'s per-item keys). */
|
|
10
|
+
const CONFIRM_HANDLE_ACTION = 'confirm';
|
|
11
|
+
const CANCEL_HANDLE_ACTION = 'cancel';
|
|
12
|
+
/**
|
|
13
|
+
* Plain-language phrase for what confirming actually changes, keyed off the dialog's own
|
|
14
|
+
* three-tier `ConfirmTone` vocabulary (`"default"` / `"warning"` / `"danger"`, documented in
|
|
15
|
+
* `../../types.js`) rather than reparsed from `title` or `body` — see this file's doc comment for
|
|
16
|
+
* why those cannot be safely folded into agent-facing text.
|
|
17
|
+
*
|
|
18
|
+
* @param tone - The dialog's resolved tone (see `resolveTone`).
|
|
19
|
+
* @returns The tier's consequence phrase, or `undefined` for `"default"`, which declares no tier.
|
|
20
|
+
* @complexity O(1).
|
|
21
|
+
*/
|
|
22
|
+
function confirmDialogConsequencePhrase(tone) {
|
|
23
|
+
if (tone === 'danger')
|
|
24
|
+
return 'cannot be undone';
|
|
25
|
+
if (tone === 'warning')
|
|
26
|
+
return 'changes access, but is reversible';
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The confirm action's agent-facing label: what it does (`confirmLabel`), what it applies to
|
|
31
|
+
* (`title`), and — when the tone declares one — its consequence tier.
|
|
32
|
+
*
|
|
33
|
+
* @param confirmLabel - The button's own visible text, e.g. `"Delete"`.
|
|
34
|
+
* @param title - The dialog's title, e.g. `"Delete role?"`.
|
|
35
|
+
* @param tone - The dialog's resolved tone.
|
|
36
|
+
* @returns A label an agent can use to judge whether confirming is safe, without having read
|
|
37
|
+
* `body` (which this function never receives — see this file's doc comment for why).
|
|
38
|
+
* @complexity O(1).
|
|
39
|
+
*/
|
|
40
|
+
function confirmDialogConfirmLabel(confirmLabel, title, tone) {
|
|
41
|
+
const consequence = confirmDialogConsequencePhrase(tone);
|
|
42
|
+
return consequence === undefined ? `${confirmLabel} — ${title}` : `${confirmLabel} — ${title}; ${consequence}`;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The cancel action's agent-facing label. Unlike {@link confirmDialogConfirmLabel}, its consequence
|
|
46
|
+
* is a guarantee of this component's own contract (cancelling never calls `onConfirm`) rather than
|
|
47
|
+
* something read off `tone`, so it needs no tone input at all.
|
|
48
|
+
*
|
|
49
|
+
* @param cancelLabel - The button's own visible text, already defaulted by the caller.
|
|
50
|
+
* @param title - The dialog's title, e.g. `"Delete role?"`.
|
|
51
|
+
* @returns A label stating that this action leaves the titled action unconfirmed.
|
|
52
|
+
* @complexity O(1).
|
|
53
|
+
*/
|
|
54
|
+
function confirmDialogCancelLabel(cancelLabel, title) {
|
|
55
|
+
return `${cancelLabel} — leaves "${title}" unconfirmed; no action taken`;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Builds the `data-agent-*` attribute props for one of this dialog's two actions, or nothing at
|
|
59
|
+
* all when the dialog published no base handle — same shape as `RowMenu.tsx`'s local
|
|
60
|
+
* `rowMenuAgentProps`, kept local and unexported here since only this one component needs it.
|
|
61
|
+
*
|
|
62
|
+
* @param base - The dialog's own handle from the caller, or `undefined` when it published none.
|
|
63
|
+
* @param action - Which action this is — see {@link CONFIRM_HANDLE_ACTION}/{@link CANCEL_HANDLE_ACTION}.
|
|
64
|
+
* @param options - Role and this action's already-composed label.
|
|
65
|
+
* @returns Spreadable attribute props, or `{}` when `base` is `undefined`.
|
|
66
|
+
* @complexity O(1).
|
|
67
|
+
*/
|
|
68
|
+
function confirmDialogAgentProps(base, action, options) {
|
|
69
|
+
return base === undefined ? {} : agentHandle(`${base}-${action}`, options);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Controlled modal confirm — renders the `<dialog>` markup and delegates its open/close and focus
|
|
73
|
+
* lifecycle to {@link useConfirmDialog} (injectable via the `useDialog` prop, defaulted to the real
|
|
74
|
+
* implementation, so a test can supply a fake without mocking modules).
|
|
75
|
+
*
|
|
76
|
+
* Both actions are plain `type="button"` (no `<form method="dialog">`, no `type="submit"`), so there
|
|
77
|
+
* is no browser-assigned "default button" for Enter to reach for at all; confirm can only ever fire
|
|
78
|
+
* from an explicit click or explicit Tab-then-Enter onto it.
|
|
79
|
+
*/
|
|
80
|
+
export function ConfirmDialog({ useDialog = useConfirmDialog, agentHandle: baseHandle, ...props }) {
|
|
81
|
+
const { titleId, dialogRef, cancelRef, handleNativeCancel, handleBackdropClick } = useDialog(props.open, props.pending, props.onCancel);
|
|
82
|
+
const tone = resolveTone(props);
|
|
83
|
+
const cancelLabel = props.cancelLabel ?? DEFAULT_CANCEL_LABEL;
|
|
84
|
+
return (_jsxs("dialog", { ref: dialogRef, className: "confirm-dialog", "aria-labelledby": titleId, onCancel: handleNativeCancel, onClick: handleBackdropClick, children: [_jsx("h2", { id: titleId, children: props.title }), _jsx("div", { className: "confirm-dialog-body", children: props.body }), _jsxs("div", { className: "confirm-dialog-actions", children: [_jsx("button", { ref: cancelRef, type: "button", className: "btn-secondary", disabled: props.pending, onClick: props.onCancel, ...confirmDialogAgentProps(baseHandle, CANCEL_HANDLE_ACTION, {
|
|
85
|
+
role: 'button',
|
|
86
|
+
label: confirmDialogCancelLabel(cancelLabel, props.title),
|
|
87
|
+
}), children: cancelLabel }), _jsx("button", { type: "button", className: toneClassName(tone), disabled: props.pending, onClick: props.onConfirm, ...confirmDialogAgentProps(baseHandle, CONFIRM_HANDLE_ACTION, {
|
|
88
|
+
role: 'button',
|
|
89
|
+
label: confirmDialogConfirmLabel(props.confirmLabel, props.title, tone),
|
|
90
|
+
}), children: props.confirmLabel })] })] }));
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=ConfirmDialog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConfirmDialog.js","sourceRoot":"","sources":["../../../../src/react/components/ConfirmDialog/ConfirmDialog.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type ConfirmTone } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* @file Shared modal confirmation primitive — the replacement for `window.confirm`, which blocks
|
|
5
|
+
* the whole tab, cannot carry destructive-vs-neutral styling, and reads as a browser artifact
|
|
6
|
+
* rather than part of the product.
|
|
7
|
+
*
|
|
8
|
+
* Built on the native `<dialog>` element (`showModal()`/`close()`) rather than a plain-`<div>`
|
|
9
|
+
* overlay + backdrop idiom: that shape hand-rolls focus trapping, Escape handling, and a backdrop
|
|
10
|
+
* element, all of which the browser's own top layer gives a real `<dialog>` for free, including
|
|
11
|
+
* correct stacking above everything else on the page without a chosen `z-index`.
|
|
12
|
+
*
|
|
13
|
+
* `dialog.showModal()`/`dialog.close()` are guarded by a `typeof` check rather than called
|
|
14
|
+
* unconditionally: jsdom (this package's React test environment, verified directly rather than
|
|
15
|
+
* assumed) does not implement either method at all — calling them throws `TypeError`. The fallback
|
|
16
|
+
* branch below toggles the plain `open` attribute instead, which no browser shipping a real
|
|
17
|
+
* `<dialog>` ever takes (the `showModal`/`close` branch always wins there), so this only changes
|
|
18
|
+
* behavior under jsdom, where it keeps the dialog mountable and its content queryable.
|
|
19
|
+
*
|
|
20
|
+
* Unstyled, like everything in this layer: the `.confirm-dialog` / `.btn-secondary` /
|
|
21
|
+
* `.btn-danger` / `.btn-warning` class names are emitted for the host stylesheet to define.
|
|
22
|
+
*/
|
|
23
|
+
export interface ConfirmDialogProps {
|
|
24
|
+
open: boolean;
|
|
25
|
+
title: string;
|
|
26
|
+
body: ReactNode;
|
|
27
|
+
confirmLabel: string;
|
|
28
|
+
/** @default "Cancel" */
|
|
29
|
+
cancelLabel?: string;
|
|
30
|
+
/** Applies `.btn-warning`/`.btn-danger` to the confirm action. Defaults to `"default"` (no class,
|
|
31
|
+
* the plain primary button). Wins over `destructive` below when both are passed. */
|
|
32
|
+
tone?: ConfirmTone;
|
|
33
|
+
/** @deprecated Use `tone: "danger"` instead — this only ever expressed the danger tier, and the
|
|
34
|
+
* vocabulary has a second one (`"warning"`) this boolean cannot reach. Kept working (mapped to
|
|
35
|
+
* `tone: "danger"` when `tone` is not set) for existing callers rather than a breaking rename. */
|
|
36
|
+
destructive?: boolean;
|
|
37
|
+
/** External in-flight flag. Disables both actions and blocks Escape/backdrop dismissal so a
|
|
38
|
+
* request already underway cannot be raced by a second dismiss. */
|
|
39
|
+
pending?: boolean;
|
|
40
|
+
onConfirm: () => void;
|
|
41
|
+
onCancel: () => void;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Controlled modal confirm. The caller keeps this mounted and toggles `open` — it is never
|
|
45
|
+
* conditionally rendered by its parent — so the effect below has a stable `<dialog>` element to
|
|
46
|
+
* call `showModal()`/`close()` on and to restore focus through when it closes.
|
|
47
|
+
*
|
|
48
|
+
* Focus moves to the *cancel* action on open, not confirm — an operator whose first keystroke after
|
|
49
|
+
* a slow read-through is Enter should land on the safe action, not the destructive one. Both
|
|
50
|
+
* actions are plain `type="button"` (no `<form method="dialog">`, no `type="submit"`), so there is
|
|
51
|
+
* no browser-assigned "default button" for Enter to reach for at all; confirm can only ever fire
|
|
52
|
+
* from an explicit click or explicit Tab-then-Enter onto it.
|
|
53
|
+
*
|
|
54
|
+
* @complexity O(1) per open/close transition — one `showModal`/`close` call and one focus move.
|
|
55
|
+
*/
|
|
56
|
+
export declare function ConfirmDialog(props: ConfirmDialogProps): import("react").JSX.Element;
|
|
57
|
+
//# sourceMappingURL=ConfirmDialog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConfirmDialog.d.ts","sourceRoot":"","sources":["../../../src/react/components/ConfirmDialog.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA6C,KAAK,SAAS,EAAuB,MAAM,OAAO,CAAC;AACvG,OAAO,EAA8B,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAE3E;;;;;;;;;;;;;;;;;;;GAmBG;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;CACtB;AAID;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,kBAAkB,+BA0FtD"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useId, useRef } from 'react';
|
|
3
|
+
import { resolveTone, toneClassName } from '../types.js';
|
|
4
|
+
const DEFAULT_CANCEL_LABEL = 'Cancel';
|
|
5
|
+
/**
|
|
6
|
+
* Controlled modal confirm. The caller keeps this mounted and toggles `open` — it is never
|
|
7
|
+
* conditionally rendered by its parent — so the effect below has a stable `<dialog>` element to
|
|
8
|
+
* call `showModal()`/`close()` on and to restore focus through when it closes.
|
|
9
|
+
*
|
|
10
|
+
* Focus moves to the *cancel* action on open, not confirm — an operator whose first keystroke after
|
|
11
|
+
* a slow read-through is Enter should land on the safe action, not the destructive one. Both
|
|
12
|
+
* actions are plain `type="button"` (no `<form method="dialog">`, no `type="submit"`), so there is
|
|
13
|
+
* no browser-assigned "default button" for Enter to reach for at all; confirm can only ever fire
|
|
14
|
+
* from an explicit click or explicit Tab-then-Enter onto it.
|
|
15
|
+
*
|
|
16
|
+
* @complexity O(1) per open/close transition — one `showModal`/`close` call and one focus move.
|
|
17
|
+
*/
|
|
18
|
+
export function ConfirmDialog(props) {
|
|
19
|
+
// `useId()`, not a string literal — a hardcoded id breaks the moment a screen mounts two
|
|
20
|
+
// `ConfirmDialog`s at once (a delete-role and a delete-policy confirm on one page, both
|
|
21
|
+
// stay-mounted/toggle-`open` per this component's own doc comment above): two `<h2>`s share one
|
|
22
|
+
// id, and `aria-labelledby` resolves via `getElementById`, which always returns the FIRST match
|
|
23
|
+
// in document order regardless of which dialog is actually open — a screen reader announcing
|
|
24
|
+
// "Delete role?" while the operator is about to confirm "Delete policy?", on a destructive
|
|
25
|
+
// action. `useId()` gives every mounted instance its own id for free.
|
|
26
|
+
const titleId = useId();
|
|
27
|
+
const dialogRef = useRef(null);
|
|
28
|
+
const cancelRef = useRef(null);
|
|
29
|
+
// Captured at the moment `open` flips true, before focus moves into the dialog — the element that
|
|
30
|
+
// had focus then is, by construction, whatever triggered this dialog (a `RowMenu` item's
|
|
31
|
+
// "Delete", a plain trigger button, …). Restored on close rather than left wherever the browser's
|
|
32
|
+
// own modal-focus algorithm happened to land (its default without this is `<body>`, which drops a
|
|
33
|
+
// keyboard user back to the top of the page).
|
|
34
|
+
const triggerRef = useRef(null);
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
const dialog = dialogRef.current;
|
|
37
|
+
if (!dialog)
|
|
38
|
+
return;
|
|
39
|
+
if (props.open) {
|
|
40
|
+
triggerRef.current = document.activeElement;
|
|
41
|
+
if (typeof dialog.showModal === 'function') {
|
|
42
|
+
if (!dialog.open)
|
|
43
|
+
dialog.showModal();
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
dialog.setAttribute('open', '');
|
|
47
|
+
}
|
|
48
|
+
cancelRef.current?.focus();
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
if (typeof dialog.close === 'function') {
|
|
52
|
+
if (dialog.open)
|
|
53
|
+
dialog.close();
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
dialog.removeAttribute('open');
|
|
57
|
+
}
|
|
58
|
+
if (triggerRef.current instanceof HTMLElement)
|
|
59
|
+
triggerRef.current.focus();
|
|
60
|
+
}
|
|
61
|
+
}, [props.open]);
|
|
62
|
+
function handleNativeCancel(e) {
|
|
63
|
+
// Fires on Escape while a real `showModal()`-opened dialog has focus. Always prevented: the
|
|
64
|
+
// effect above is the single source of truth for open/closed (driven by `props.open`), so
|
|
65
|
+
// letting the browser close the element on its own would desync DOM state from React state.
|
|
66
|
+
// Escape still closes the dialog — it just does so by routing through `onCancel`, same as a
|
|
67
|
+
// Cancel-button click, so the caller's `open` state (and therefore this same effect) is what
|
|
68
|
+
// actually calls `dialog.close()`.
|
|
69
|
+
e.preventDefault();
|
|
70
|
+
if (props.pending)
|
|
71
|
+
return;
|
|
72
|
+
props.onCancel();
|
|
73
|
+
}
|
|
74
|
+
function handleBackdropClick(e) {
|
|
75
|
+
// A `<dialog>` element's own box is sized to its content, not the viewport — a click that lands
|
|
76
|
+
// on the `<dialog>` element itself (as opposed to one of its children) is therefore a click on
|
|
77
|
+
// the backdrop area outside that content box.
|
|
78
|
+
if (props.pending)
|
|
79
|
+
return;
|
|
80
|
+
if (e.target === dialogRef.current)
|
|
81
|
+
props.onCancel();
|
|
82
|
+
}
|
|
83
|
+
return (_jsxs("dialog", { ref: dialogRef, className: "confirm-dialog", "aria-labelledby": titleId, onCancel: handleNativeCancel, onClick: handleBackdropClick, children: [_jsx("h2", { id: titleId, children: props.title }), _jsx("div", { className: "confirm-dialog-body", children: props.body }), _jsxs("div", { className: "confirm-dialog-actions", children: [_jsx("button", { ref: cancelRef, type: "button", className: "btn-secondary", disabled: props.pending, onClick: props.onCancel, children: props.cancelLabel ?? DEFAULT_CANCEL_LABEL }), _jsx("button", { type: "button", className: toneClassName(resolveTone(props)), disabled: props.pending, onClick: props.onConfirm, children: props.confirmLabel })] })] }));
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=ConfirmDialog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConfirmDialog.js","sourceRoot":"","sources":["../../../src/react/components/ConfirmDialog.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* @file The admin list table.
|
|
4
|
+
*
|
|
5
|
+
* ## Designed from the corpus, not from what a table component usually has
|
|
6
|
+
*
|
|
7
|
+
* This replaces 25 hand-rolled `<table>` blocks across one reference admin, every one of which had
|
|
8
|
+
* the identical skeleton — a `.table-scroll` wrapper, a `.list-table`, a `<thead>` of plain `<th>`,
|
|
9
|
+
* and a `<tbody>` mapping rows — wrapped in a ternary that rendered an empty state instead when
|
|
10
|
+
* there was nothing to show. That ternary is the single most duplicated thing in the corpus, which
|
|
11
|
+
* is why `empty` and `loading` are this component's concern rather than each caller's.
|
|
12
|
+
*
|
|
13
|
+
* What the corpus did **not** contain is just as load-bearing:
|
|
14
|
+
*
|
|
15
|
+
* - **No sorting.** Zero of 25.
|
|
16
|
+
* - **No pagination.** Zero of 25.
|
|
17
|
+
* - **No row selection.** Zero of 25 — the checkboxes that appear inside some of those tables are
|
|
18
|
+
* field controls belonging to the row's own data, not a selection model.
|
|
19
|
+
*
|
|
20
|
+
* None of the three is implemented here. A table component that ships sorting nobody asked for has
|
|
21
|
+
* to guess at comparator semantics, controlled-vs-uncontrolled state, and multi-column precedence,
|
|
22
|
+
* and every one of those guesses becomes a compatibility obligation the moment it ships. When a
|
|
23
|
+
* host needs sorting, it will arrive with a real requirement attached and can be added then —
|
|
24
|
+
* additively, since `columns` is already a descriptor list with somewhere to put it.
|
|
25
|
+
*
|
|
26
|
+
* `<tfoot>` appeared exactly once, so it is an optional prop rather than part of the core shape.
|
|
27
|
+
*
|
|
28
|
+
* ## Row actions are not a feature
|
|
29
|
+
*
|
|
30
|
+
* An overflow menu in the last column is just a column whose `cell` renders one. There is no
|
|
31
|
+
* `actions` prop, because there is nothing a dedicated one would do that a `cell` does not already
|
|
32
|
+
* do — and having one would force a second, worse decision about where actions may appear.
|
|
33
|
+
*
|
|
34
|
+
* Three of the 25 gave that column an empty `<th>` carrying only an `aria-label`, which is the
|
|
35
|
+
* right shape: a visible "More" heading is noise, and a nameless column header is a gap for anyone
|
|
36
|
+
* navigating by column. `header` is therefore optional, and `headerLabel` names a headerless one.
|
|
37
|
+
*
|
|
38
|
+
* ## Styling contract
|
|
39
|
+
*
|
|
40
|
+
* Unstyled, like everything in this layer: `.table-scroll` and `.list-table` are emitted for the
|
|
41
|
+
* host stylesheet to define. The wrapper is not decorative — `overflow-x: auto` on it is what makes
|
|
42
|
+
* a wide table scroll instead of breaking the page, and it is also why `RowMenu` portals its popup
|
|
43
|
+
* out to `document.body` rather than positioning inside this box.
|
|
44
|
+
*/
|
|
45
|
+
export interface DataTableColumn<Row> {
|
|
46
|
+
/** Stable identity for this column. Not rendered — used as the React key. */
|
|
47
|
+
key: string;
|
|
48
|
+
/** Header content. Omit for a column that carries no visible heading (a row-actions column);
|
|
49
|
+
* supply {@link headerLabel} when you do, so the column is still named for assistive tech. */
|
|
50
|
+
header?: ReactNode;
|
|
51
|
+
/** Accessible name for a column whose `header` is omitted or purely visual. */
|
|
52
|
+
headerLabel?: string;
|
|
53
|
+
/** Renders one cell. Receives the row and its index. */
|
|
54
|
+
cell: (row: Row, index: number) => ReactNode;
|
|
55
|
+
/** Class on this column's `<th>`. */
|
|
56
|
+
headerClassName?: string;
|
|
57
|
+
/** Class on this column's `<td>`. A function receives the row, for per-row variation. */
|
|
58
|
+
cellClassName?: string | ((row: Row, index: number) => string | undefined);
|
|
59
|
+
}
|
|
60
|
+
export interface DataTableProps<Row> {
|
|
61
|
+
rows: readonly Row[];
|
|
62
|
+
columns: ReadonlyArray<DataTableColumn<Row>>;
|
|
63
|
+
/** Stable React key per row. Required rather than defaulting to the index — an index key on a
|
|
64
|
+
* list that can reorder or delete is a well-known source of state landing on the wrong row, and
|
|
65
|
+
* every table in the corpus had a real id to hand. */
|
|
66
|
+
rowKey: (row: Row, index: number) => string;
|
|
67
|
+
/**
|
|
68
|
+
* Rendered **instead of the table** when there are no rows and `loading` is false. Pass the
|
|
69
|
+
* host's own empty-state markup. Omit it to render an empty `<tbody>` — appropriate when the
|
|
70
|
+
* table is one part of a larger screen that explains the emptiness itself.
|
|
71
|
+
*/
|
|
72
|
+
empty?: ReactNode;
|
|
73
|
+
/** While true, `loadingContent` is rendered instead of the table (or an empty `<tbody>` if none
|
|
74
|
+
* is given). Takes precedence over `empty`, so a slow first fetch never flashes "nothing here"
|
|
75
|
+
* before the rows arrive — the bug that pattern produces in every list screen that gets it
|
|
76
|
+
* backwards. */
|
|
77
|
+
loading?: boolean;
|
|
78
|
+
loadingContent?: ReactNode;
|
|
79
|
+
/** `<tfoot>` content. Rendered as-is: supply your own `<tr>`/`<td>`. */
|
|
80
|
+
footer?: ReactNode;
|
|
81
|
+
/** Accessible name for the table itself. Worth setting when a screen has more than one. */
|
|
82
|
+
label?: string;
|
|
83
|
+
/** `<caption>`. Visible unless the host's CSS hides it; prefer {@link label} for a name that
|
|
84
|
+
* should not paint. */
|
|
85
|
+
caption?: ReactNode;
|
|
86
|
+
/** Class per row, e.g. to mark a disabled or newly-created record. */
|
|
87
|
+
rowClassName?: (row: Row, index: number) => string | undefined;
|
|
88
|
+
/** Appended to `.list-table`. */
|
|
89
|
+
className?: string;
|
|
90
|
+
/** Appended to `.table-scroll`. */
|
|
91
|
+
scrollClassName?: string;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* @complexity O(rows × columns) per render — one pass, no sorting or grouping work.
|
|
95
|
+
*/
|
|
96
|
+
export declare function DataTable<Row>(props: DataTableProps<Row>): import("react").JSX.Element;
|
|
97
|
+
//# sourceMappingURL=DataTable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DataTable.d.ts","sourceRoot":"","sources":["../../../src/react/components/DataTable.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEH,MAAM,WAAW,eAAe,CAAC,GAAG;IAClC,6EAA6E;IAC7E,GAAG,EAAE,MAAM,CAAC;IACZ;mGAC+F;IAC/F,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,+EAA+E;IAC/E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wDAAwD;IACxD,IAAI,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,KAAK,SAAS,CAAC;IAC7C,qCAAqC;IACrC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,yFAAyF;IACzF,aAAa,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC,CAAC;CAC5E;AAED,MAAM,WAAW,cAAc,CAAC,GAAG;IACjC,IAAI,EAAE,SAAS,GAAG,EAAE,CAAC;IACrB,OAAO,EAAE,aAAa,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC;IAC7C;;2DAEuD;IACvD,MAAM,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAC5C;;;;OAIG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB;;;qBAGiB;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,cAAc,CAAC,EAAE,SAAS,CAAC;IAC3B,wEAAwE;IACxE,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,2FAA2F;IAC3F,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;4BACwB;IACxB,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,sEAAsE;IACtE,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IAC/D,iCAAiC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mCAAmC;IACnC,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAYD;;GAEG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,KAAK,EAAE,cAAc,CAAC,GAAG,CAAC,+BAwCxD"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
function resolveCellClassName(column, row, index) {
|
|
3
|
+
return typeof column.cellClassName === 'function'
|
|
4
|
+
? column.cellClassName(row, index)
|
|
5
|
+
: column.cellClassName;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* @complexity O(rows × columns) per render — one pass, no sorting or grouping work.
|
|
9
|
+
*/
|
|
10
|
+
export function DataTable(props) {
|
|
11
|
+
// Loading wins over empty deliberately: `rows` is legitimately `[]` during a first fetch, and
|
|
12
|
+
// checking emptiness first is what makes a list screen flash its empty state before the data
|
|
13
|
+
// lands.
|
|
14
|
+
if (props.loading && props.loadingContent !== undefined)
|
|
15
|
+
return _jsx(_Fragment, { children: props.loadingContent });
|
|
16
|
+
if (!props.loading && props.rows.length === 0 && props.empty !== undefined)
|
|
17
|
+
return _jsx(_Fragment, { children: props.empty });
|
|
18
|
+
return (_jsx("div", { className: ['table-scroll', props.scrollClassName].filter(Boolean).join(' '), children: _jsxs("table", { className: ['list-table', props.className].filter(Boolean).join(' '), "aria-label": props.label, children: [props.caption === undefined ? null : _jsx("caption", { children: props.caption }), _jsx("thead", { children: _jsx("tr", { children: props.columns.map((column) => (_jsx("th", { scope: "col", className: column.headerClassName, "aria-label": column.header === undefined ? column.headerLabel : undefined, children: column.header }, column.key))) }) }), _jsx("tbody", { children: props.rows.map((row, index) => (_jsx("tr", { className: props.rowClassName?.(row, index), children: props.columns.map((column) => (_jsx("td", { className: resolveCellClassName(column, row, index), children: column.cell(row, index) }, column.key))) }, props.rowKey(row, index)))) }), props.footer === undefined ? null : _jsx("tfoot", { children: props.footer })] }) }));
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=DataTable.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DataTable.js","sourceRoot":"","sources":["../../../src/react/components/DataTable.tsx"],"names":[],"mappings":";AAgGA,SAAS,oBAAoB,CAC3B,MAA4B,EAC5B,GAAQ,EACR,KAAa;IAEb,OAAO,OAAO,MAAM,CAAC,aAAa,KAAK,UAAU;QAC/C,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,GAAG,EAAE,KAAK,CAAC;QAClC,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC;AAC3B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,SAAS,CAAM,KAA0B;IACvD,8FAA8F;IAC9F,6FAA6F;IAC7F,SAAS;IACT,IAAI,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,cAAc,KAAK,SAAS;QAAE,OAAO,4BAAG,KAAK,CAAC,cAAc,GAAI,CAAC;IAC5F,IAAI,CAAC,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS;QAAE,OAAO,4BAAG,KAAK,CAAC,KAAK,GAAI,CAAC;IAEtG,OAAO,CACL,cAAK,SAAS,EAAE,CAAC,cAAc,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,YAC/E,iBAAO,SAAS,EAAE,CAAC,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,gBAAc,KAAK,CAAC,KAAK,aACjG,KAAK,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,4BAAU,KAAK,CAAC,OAAO,GAAW,EACxE,0BACE,uBACG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAC7B,aAEE,KAAK,EAAC,KAAK,EACX,SAAS,EAAE,MAAM,CAAC,eAAe,gBACrB,MAAM,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,YAEvE,MAAM,CAAC,MAAM,IALT,MAAM,CAAC,GAAG,CAMZ,CACN,CAAC,GACC,GACC,EACR,0BACG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,CAC9B,aAAmC,SAAS,EAAE,KAAK,CAAC,YAAY,EAAE,CAAC,GAAG,EAAE,KAAK,CAAC,YAC3E,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAC7B,aAAqB,SAAS,EAAE,oBAAoB,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK,CAAC,YACrE,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IADjB,MAAM,CAAC,GAAG,CAEd,CACN,CAAC,IALK,KAAK,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,CAM5B,CACN,CAAC,GACI,EACP,KAAK,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,0BAAQ,KAAK,CAAC,MAAM,GAAS,IAC5D,GACJ,CACP,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type CanvasEmbedPlaceholderDescriptor, type InteractiveHtmlEditorProps as HtmlEditorProps } from '@jini-ai/ui/html-editor';
|
|
2
|
+
/** True when `el` carries any embed marker attribute this convention recognizes — exported for
|
|
3
|
+
* direct unit-testability without mounting the editor. Uses `hasAttributeOnAnyNodeShape` because
|
|
4
|
+
* the `isProtectedElement` predicate is invoked from inside GrapesJS's `isComponent` callback,
|
|
5
|
+
* which is not always called with a real DOM `Element` — see that helper's own doc. */
|
|
6
|
+
export declare function isProtectedEmbedElement(el: Element): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Recognizes a `data-embed-config` marker (`Tovu/src/core/embeds/marker.ts`'s single scanner
|
|
9
|
+
* convention) on a live canvas element and describes it for `@jini-ai/ui/html-editor`'s
|
|
10
|
+
* `applyCanvasEmbedPlaceholders` — see that function's own file header for what it does with the
|
|
11
|
+
* result. Exported for direct unit-testability without mounting the editor, same convention
|
|
12
|
+
* `isProtectedEmbedElement` above already documents.
|
|
13
|
+
*
|
|
14
|
+
* A marker whose `data-embed-config` fails to parse, isn't an object, or has no `type` returns
|
|
15
|
+
* `undefined` — "not a marker this function can describe," the identical degrade-quietly contract
|
|
16
|
+
* `scanEmbedMarkers` (the server-side scanner this mirrors) uses for a rejected marker at render time;
|
|
17
|
+
* this function does not warn or log, since it runs on every keystroke-adjacent canvas re-render, not
|
|
18
|
+
* at a write chokepoint where a loud rejection matters.
|
|
19
|
+
*
|
|
20
|
+
* @complexity O(1) — one attribute read, one `JSON.parse` of a small config object, one table lookup.
|
|
21
|
+
*/
|
|
22
|
+
export declare function describeEmbedPlaceholder(el: Element): CanvasEmbedPlaceholderDescriptor | undefined;
|
|
23
|
+
export type InteractiveHtmlEditorProps = Omit<HtmlEditorProps, 'isProtectedElement' | 'describeEmbedPlaceholder'>;
|
|
24
|
+
export declare function InteractiveHtmlEditor(props: InteractiveHtmlEditorProps): import("react").JSX.Element;
|
|
25
|
+
//# sourceMappingURL=InteractiveHtmlEditor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InteractiveHtmlEditor.d.ts","sourceRoot":"","sources":["../../../../src/react/components/InteractiveHtmlEditor/InteractiveHtmlEditor.tsx"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,gCAAgC,EACrC,KAAK,0BAA0B,IAAI,eAAe,EACnD,MAAM,yBAAyB,CAAC;AAkCjC;;;wFAGwF;AACxF,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,OAAO,GAAG,OAAO,CAE5D;AA2ED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,wBAAwB,CAAC,EAAE,EAAE,OAAO,GAAG,gCAAgC,GAAG,SAAS,CAelG;AAED,MAAM,MAAM,0BAA0B,GAAG,IAAI,CAAC,eAAe,EAAE,oBAAoB,GAAG,0BAA0B,CAAC,CAAC;AAElH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,0BAA0B,+BAQtE"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { InteractiveHtmlEditor as HtmlEditor, hasAttributeOnAnyNodeShape, } from '@jini-ai/ui/html-editor';
|
|
3
|
+
/**
|
|
4
|
+
* @file Tovu-specific composition of `@jini-ai/ui/html-editor`'s generic `InteractiveHtmlEditor`: a
|
|
5
|
+
* Page's `body_html` (the first caller — see `Tovu/apps/admin/src/features/pages/PageEditor.tsx`)
|
|
6
|
+
* can carry `<div data-embed-type="…" data-embed-id="…"></div>` placeholders (and the legacy
|
|
7
|
+
* `data-widget-embed`/`data-form-embed` attributes on migration-era rows) that a separate scanner
|
|
8
|
+
* (`Tovu/src/widgets/html-embeds.ts`) resolves at render time — that scanner requires the div to
|
|
9
|
+
* stay exactly empty and self-closing, and degrades silently, with no error, the moment anything is
|
|
10
|
+
* written inside one. The CURRENT convention has since moved to a single `data-embed-config`
|
|
11
|
+
* attribute carrying a JSON payload (`Tovu/src/core/embeds/marker.ts`); `isProtectedEmbedElement`
|
|
12
|
+
* below has not been updated to recognize it (tracked separately — see that function's own doc), but
|
|
13
|
+
* `describeEmbedPlaceholder` below IS built against the current convention, since it only needs to
|
|
14
|
+
* read the marker, not police edits to it.
|
|
15
|
+
*
|
|
16
|
+
* The generic `@jini-ai/ui` primitive has zero knowledge of either convention; this file is the thin
|
|
17
|
+
* adapter that supplies them as an `isProtectedElement` predicate and a `describeEmbedPlaceholder`
|
|
18
|
+
* labeler, so `PageEditor.tsx` can keep importing `InteractiveHtmlEditor` from `@jini-ai/admin/react`
|
|
19
|
+
* unchanged.
|
|
20
|
+
*/
|
|
21
|
+
/** The attribute either the current (`data-embed-type`) or legacy (`data-widget-embed`,
|
|
22
|
+
* `data-form-embed`) convention uses to mark a Page HTML embed placeholder div — see
|
|
23
|
+
* `Tovu/src/widgets/html-embeds.ts`'s file header for the convention this protects.
|
|
24
|
+
*
|
|
25
|
+
* **Does NOT include `data-embed-config`, the CURRENT marker attribute** (`Tovu/src/core/embeds/
|
|
26
|
+
* marker.ts`) — a known, separately-tracked gap: an embed marker written the current way is still
|
|
27
|
+
* editable/draggable/removable in this editor today. Left alone here deliberately (out of scope for
|
|
28
|
+
* the placeholder-card feature this file's `describeEmbedPlaceholder` was added for); widening this
|
|
29
|
+
* list to close that gap is a one-line fix (`'data-embed-config'` added below) but changes real
|
|
30
|
+
* interaction behavior (an operator who could previously drag/delete a `data-embed-config` marker no
|
|
31
|
+
* longer could), which deserves its own change, not a side effect of an unrelated card feature. */
|
|
32
|
+
const EMBED_MARKER_ATTRIBUTES = ['data-embed-type', 'data-widget-embed', 'data-form-embed'];
|
|
33
|
+
/** True when `el` carries any embed marker attribute this convention recognizes — exported for
|
|
34
|
+
* direct unit-testability without mounting the editor. Uses `hasAttributeOnAnyNodeShape` because
|
|
35
|
+
* the `isProtectedElement` predicate is invoked from inside GrapesJS's `isComponent` callback,
|
|
36
|
+
* which is not always called with a real DOM `Element` — see that helper's own doc. */
|
|
37
|
+
export function isProtectedEmbedElement(el) {
|
|
38
|
+
return EMBED_MARKER_ATTRIBUTES.some((attr) => hasAttributeOnAnyNodeShape(el, attr));
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Friendly card headings for every embed `type` value this codebase names anywhere, split into two
|
|
42
|
+
* groups (traced against `Tovu/src/server/http/site/render.ts`'s `renderHtmlPageBody`/`isPageEmbedType`
|
|
43
|
+
* at the owner's own request, 2026-08-25 — authoritative, not re-derived):
|
|
44
|
+
*
|
|
45
|
+
* - **`media`, `widget`, `post`, `content`** — the ONLY four types `isPageEmbedType` (gating
|
|
46
|
+
* `renderHtmlPageBody`, `render.ts:1238`) accepts, i.e. the complete set a Page's `body_html` — what
|
|
47
|
+
* this editor edits — can ever actually contain. These are the types an operator can realistically
|
|
48
|
+
* hit in the Interactive tab.
|
|
49
|
+
* - **`partial`, `menu`** — `Tovu/src/widgets/resolver-service.ts`'s `THEME_OWNED_MARKER_TYPES`. NOT
|
|
50
|
+
* reachable inside a Page body — they live in THEME TEMPLATES (site header/nav/footer), authored
|
|
51
|
+
* outside this editor entirely, so this component should not grow UI affordances assuming one can
|
|
52
|
+
* appear here. Labeled anyway (rather than left to the generic fallback below) purely so the ONE
|
|
53
|
+
* defensive case — a future change routes theme markup through this same editor, or a marketplace
|
|
54
|
+
* theme's template gets opened here by mistake — still reads as a real word instead of a raw string.
|
|
55
|
+
*
|
|
56
|
+
* **`media` covers BOTH images and video — one type, one label, by design.** `resolveMediaTypeEmbeds`
|
|
57
|
+
* (`resolver-service.ts`) resolves a `media` marker to either a bare `<img>` or `<video>` tag depending
|
|
58
|
+
* on the asset's own kind, but that distinction lives entirely server-side in the resolved OUTPUT, not
|
|
59
|
+
* in the marker's `data-embed-config` this function reads. Telling them apart here would need either a
|
|
60
|
+
* new server endpoint (out of scope — see `identityLabel`'s doc on the identical media-title tradeoff)
|
|
61
|
+
* or guessing from the id string, which is not a real signal and would be actively misleading if wrong.
|
|
62
|
+
* "Media" stays correct for both until a cheap, real signal exists.
|
|
63
|
+
*
|
|
64
|
+
* Matched lower-cased — see `identityLabel`'s doc and `core/embeds/marker.ts`'s own `toEmbedRef` for
|
|
65
|
+
* why the server-side scanner lower-cases `type` the same way. An unregistered type is not an error
|
|
66
|
+
* here (unlike a rejected/unparseable marker) — `type` is deliberately a free string end-to-end (see
|
|
67
|
+
* `Tovu/src/widgets/html-embeds.ts`'s file header) precisely so a new one (a marketplace theme
|
|
68
|
+
* introducing its own, say) never requires a matching change on this list AND never renders nothing —
|
|
69
|
+
* `titleCase` below covers it with a still-legible generic card instead of a blank/thrown result.
|
|
70
|
+
*/
|
|
71
|
+
const EMBED_KIND_LABELS = {
|
|
72
|
+
media: 'Media',
|
|
73
|
+
widget: 'Widget',
|
|
74
|
+
post: 'Post',
|
|
75
|
+
content: 'Content',
|
|
76
|
+
partial: 'Partial',
|
|
77
|
+
menu: 'Menu',
|
|
78
|
+
};
|
|
79
|
+
/** Upper-cases just the first character — `EMBED_KIND_LABELS`'s fallback for a `type` this table
|
|
80
|
+
* doesn't (yet) name, so the card still reads as a word rather than a raw lower-case config value. */
|
|
81
|
+
function titleCase(value) {
|
|
82
|
+
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
83
|
+
}
|
|
84
|
+
/** How much of a bare id to show when no friendlier identity (`name`) is available on the marker's
|
|
85
|
+
* own config — long enough to tell two real ids apart at a glance, short enough the card stays a
|
|
86
|
+
* small chip instead of a wall of digits. */
|
|
87
|
+
const ID_PREVIEW_LENGTH = 8;
|
|
88
|
+
/**
|
|
89
|
+
* The card's identity line: the marker's own `name` when its config carries one (free — no fetch),
|
|
90
|
+
* otherwise a truncated `id`, otherwise an explicit "nothing was set" note rather than a blank line.
|
|
91
|
+
*
|
|
92
|
+
* **Deliberately does not fetch a human title for a `media` marker.** There is no cheap existing admin
|
|
93
|
+
* route for a single media asset's title by id — `AdminAPI.listMedia()` (`apps/admin/src/lib/api.ts`)
|
|
94
|
+
* is the only read, and it returns the ENTIRE workspace's media library, active and trashed, with no
|
|
95
|
+
* id filter or pagination, just to answer one id's `title`. Adding a `GET /media/:id` route is out of
|
|
96
|
+
* this feature's scope (an editor-chrome label is not worth a new server endpoint), and reusing
|
|
97
|
+
* `listMedia()` here would mean an async network call — with its own loading/error/unmount-race
|
|
98
|
+
* handling — inside what is otherwise a synchronous, purely-descriptive function called from a
|
|
99
|
+
* GrapesJS `load` handler. `name`, when authored, covers the common case for free; an id-only marker
|
|
100
|
+
* shows its id.
|
|
101
|
+
*/
|
|
102
|
+
function identityLabel(config) {
|
|
103
|
+
const name = typeof config.name === 'string' && config.name.length > 0 ? config.name : null;
|
|
104
|
+
if (name)
|
|
105
|
+
return name;
|
|
106
|
+
const id = typeof config.id === 'string' && config.id.length > 0 ? config.id : null;
|
|
107
|
+
if (!id)
|
|
108
|
+
return 'no id set';
|
|
109
|
+
return id.length > ID_PREVIEW_LENGTH ? `id ${id.slice(0, ID_PREVIEW_LENGTH)}…` : `id ${id}`;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Recognizes a `data-embed-config` marker (`Tovu/src/core/embeds/marker.ts`'s single scanner
|
|
113
|
+
* convention) on a live canvas element and describes it for `@jini-ai/ui/html-editor`'s
|
|
114
|
+
* `applyCanvasEmbedPlaceholders` — see that function's own file header for what it does with the
|
|
115
|
+
* result. Exported for direct unit-testability without mounting the editor, same convention
|
|
116
|
+
* `isProtectedEmbedElement` above already documents.
|
|
117
|
+
*
|
|
118
|
+
* A marker whose `data-embed-config` fails to parse, isn't an object, or has no `type` returns
|
|
119
|
+
* `undefined` — "not a marker this function can describe," the identical degrade-quietly contract
|
|
120
|
+
* `scanEmbedMarkers` (the server-side scanner this mirrors) uses for a rejected marker at render time;
|
|
121
|
+
* this function does not warn or log, since it runs on every keystroke-adjacent canvas re-render, not
|
|
122
|
+
* at a write chokepoint where a loud rejection matters.
|
|
123
|
+
*
|
|
124
|
+
* @complexity O(1) — one attribute read, one `JSON.parse` of a small config object, one table lookup.
|
|
125
|
+
*/
|
|
126
|
+
export function describeEmbedPlaceholder(el) {
|
|
127
|
+
const raw = el.getAttribute('data-embed-config');
|
|
128
|
+
if (!raw)
|
|
129
|
+
return undefined;
|
|
130
|
+
let parsed;
|
|
131
|
+
try {
|
|
132
|
+
parsed = JSON.parse(raw);
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
|
|
138
|
+
return undefined;
|
|
139
|
+
const config = parsed;
|
|
140
|
+
if (typeof config.type !== 'string' || config.type === '')
|
|
141
|
+
return undefined;
|
|
142
|
+
const type = config.type.toLowerCase();
|
|
143
|
+
return { kindLabel: EMBED_KIND_LABELS[type] ?? titleCase(type), identityLabel: identityLabel(config) };
|
|
144
|
+
}
|
|
145
|
+
export function InteractiveHtmlEditor(props) {
|
|
146
|
+
return (_jsx(HtmlEditor, { ...props, isProtectedElement: isProtectedEmbedElement, describeEmbedPlaceholder: describeEmbedPlaceholder }));
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=InteractiveHtmlEditor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InteractiveHtmlEditor.js","sourceRoot":"","sources":["../../../../src/react/components/InteractiveHtmlEditor/InteractiveHtmlEditor.tsx"],"names":[],"mappings":"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"}
|