@jini-ai/admin 0.4.12 → 0.6.10
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/CHANGELOG.md +70 -2
- package/dist/agent-plugins/controllers/agent-plugin-files.controller.js +14 -9
- package/dist/agent-plugins/controllers/agent-plugins.controller.js +12 -9
- package/dist/agent-plugins/package-file-tree.js +54 -51
- package/dist/agent-plugins/react/components/AgentPluginRow.d.ts +1 -1
- package/dist/agent-plugins/react/components/AgentPluginRow.js +9 -2
- package/dist/agent-plugins/react/hooks/AgentPluginInspector.hooks.d.ts +5 -5
- package/dist/agent-plugins/react/hooks/AgentPluginInspector.hooks.js +14 -5
- package/dist/agent-plugins/react/hooks/AgentPluginPanel.hooks.d.ts +15 -13
- package/dist/agent-plugins/react/hooks/AgentPluginPanel.hooks.js +54 -40
- package/dist/agent-plugins/react/hooks/AgentPluginsPage.hooks.d.ts +13 -13
- package/dist/agent-plugins/react/hooks/AgentPluginsPage.hooks.js +23 -11
- package/dist/browser/navigation.js +44 -27
- package/dist/comments/adapters/memory.js +30 -12
- package/dist/comments/conformance/comments-api.conformance.js +55 -33
- package/dist/comments/controllers/comment-queue.controller.js +13 -4
- package/dist/comments/react/hooks/use-comment-queue.hooks.d.ts +17 -2
- package/dist/comments/react/hooks/use-comment-queue.hooks.js +79 -30
- package/dist/comments/react/hooks/use-comment-settings.hooks.js +9 -5
- package/dist/comments/react/index.d.ts +2 -1
- package/dist/comments/react/index.js +1 -1
- package/dist/consent/react/ConsentFields.d.ts +35 -0
- package/dist/consent/react/ConsentFields.js +76 -0
- package/dist/consent/react/ConsentPreviewFrame.d.ts +14 -0
- package/dist/consent/react/ConsentPreviewFrame.js +14 -0
- package/dist/consent/react/ConsentSettingsPanel.d.ts +27 -4
- package/dist/consent/react/ConsentSettingsPanel.js +6 -17
- package/dist/consent/react/index.d.ts +6 -1
- package/dist/consent/react/index.js +2 -0
- package/dist/consent/react/settings-panel.hooks.d.ts +28 -1
- package/dist/consent/react/settings-panel.hooks.js +26 -0
- package/dist/core/entities/rules.js +126 -81
- package/dist/core/manifest/rules.js +10 -10
- package/dist/core/module/create-admin.js +195 -117
- package/dist/core/ports/redirects.d.ts +8 -6
- package/dist/core/ports/redirects.js +8 -6
- package/dist/core/routing/rules.js +9 -4
- package/dist/core/transport/http.js +37 -20
- package/dist/forms/adapters/memory.js +22 -11
- package/dist/forms/conformance/forms-api.conformance.d.ts +6 -4
- package/dist/forms/conformance/forms-api.conformance.js +42 -46
- package/dist/forms/field-settings.js +150 -115
- package/dist/forms/html-rules.js +45 -28
- package/dist/forms/react/components/FieldSettingsRows.d.ts +22 -0
- package/dist/forms/react/components/FieldSettingsRows.js +29 -0
- package/dist/forms/react/components/FieldSettingsSection.d.ts +1 -1
- package/dist/forms/react/components/FieldSettingsSection.js +5 -6
- package/dist/forms/react/components/FormEditorFieldsBody.d.ts +1 -1
- package/dist/forms/react/components/FormEditorFieldsBody.js +4 -15
- package/dist/forms/react/components/FormEditorFieldsBodyParts.d.ts +18 -0
- package/dist/forms/react/components/FormEditorFieldsBodyParts.js +32 -0
- package/dist/forms/react/components/FormSubmissions.d.ts +1 -1
- package/dist/forms/react/components/FormSubmissions.js +4 -20
- package/dist/forms/react/components/FormSubmissionsTable.d.ts +19 -0
- package/dist/forms/react/components/FormSubmissionsTable.js +22 -0
- package/dist/media/adapters/memory.js +31 -26
- package/dist/media/controllers/edit-media.controller.js +2 -1
- package/dist/media/controllers/library.controller.js +48 -35
- package/dist/media/controllers/providers.controller.js +2 -1
- package/dist/media/html-attributes.js +29 -15
- package/dist/media/index.d.ts +1 -1
- package/dist/media/index.js +1 -1
- package/dist/media/react/components/MediaIcons.d.ts +0 -3
- package/dist/media/react/components/MediaIcons.js +0 -9
- package/dist/media/react/components/MediaLightbox.d.ts +2 -1
- package/dist/media/react/components/MediaLightbox.js +11 -5
- package/dist/media/react/hooks/LibraryTab.hooks.d.ts +55 -45
- package/dist/media/react/hooks/LibraryTab.hooks.js +107 -70
- package/dist/media/react/hooks/MediaCard.hooks.d.ts +27 -31
- package/dist/media/react/hooks/MediaCard.hooks.js +52 -26
- package/dist/media/react/hooks/MediaGrid.hooks.d.ts +1 -1
- package/dist/media/react/hooks/MediaLightbox.hooks.d.ts +10 -14
- package/dist/media/react/hooks/MediaLightbox.hooks.js +9 -56
- package/dist/media/react/hooks/MediaPage.hooks.d.ts +6 -0
- package/dist/media/react/hooks/MediaPage.hooks.js +29 -12
- package/dist/media/react/hooks/ProvidersTab.hooks.d.ts +33 -33
- package/dist/media/react/hooks/ProvidersTab.hooks.js +119 -79
- package/dist/media/react/hooks/UploadButton.hooks.d.ts +7 -5
- package/dist/media/react/hooks/UploadButton.hooks.js +36 -24
- package/dist/media/react/index.js +4 -0
- package/dist/media/react/tabs/LibraryTab.d.ts +1 -1
- package/dist/media/react/tabs/LibraryTab.js +2 -2
- package/dist/media/rules.d.ts +7 -0
- package/dist/media/rules.js +25 -2
- package/dist/react/components/ConfirmDialog/ConfirmDialog.js +5 -8
- package/dist/react/components/ConfirmDialog/confirm-dialog-attrs.d.ts +20 -0
- package/dist/react/components/ConfirmDialog/confirm-dialog-attrs.js +24 -0
- package/dist/react/components/InteractiveHtmlEditor/InteractiveHtmlEditor.js +16 -7
- package/dist/react/components/RowMenu/RowMenu.js +16 -10
- package/dist/react/components/Sidebar.js +32 -26
- package/dist/react/entities/EntityEdit.js +17 -14
- package/dist/react/entities/EntityField.d.ts +5 -2
- package/dist/react/entities/EntityField.js +38 -23
- package/dist/react/entities/EntityList.js +6 -5
- package/dist/react/entities/RelationTruncationNotice.d.ts +9 -0
- package/dist/react/entities/RelationTruncationNotice.js +9 -0
- package/dist/react/entities/use-entity-edit.d.ts +4 -2
- package/dist/react/entities/use-entity-edit.js +52 -33
- package/dist/react/shell/AdminShell.js +40 -23
- package/dist/react/shell/model.d.ts +4 -1
- package/dist/react/shell/model.js +3 -2
- package/dist/react/shell/use-admin-shell-session.js +25 -26
- package/dist/redirects/adapters/memory.js +9 -2
- package/dist/redirects/conformance/redirects-api.conformance.js +66 -36
- package/dist/redirects/react/hooks/use-import-redirects-form.hooks.d.ts +1 -1
- package/dist/redirects/react/hooks/use-import-redirects-form.hooks.js +10 -3
- package/dist/redirects/react/hooks/wired.hooks.d.ts +1 -1
- package/dist/security/adapters/http.js +25 -10
- package/dist/security/adapters/memory.js +19 -5
- package/dist/security/controllers/access-tokens.controller.js +27 -26
- package/dist/security/controllers/other-credentials.controller.js +4 -2
- package/dist/security/controllers/root-key.controller.js +17 -9
- package/dist/security/react/hooks/AccessTokensTab.hooks.d.ts +40 -38
- package/dist/security/react/hooks/AccessTokensTab.hooks.js +36 -9
- package/dist/security/react/hooks/RootKeyTab.hooks.d.ts +14 -14
- package/dist/security/react/hooks/RootKeyTab.hooks.js +12 -3
- package/dist/security/react/tabs/RootKeyTab.d.ts +1 -1
- package/dist/security/react/tabs/RootKeyTab.js +12 -3
- package/dist/security/rules.js +45 -25
- package/dist/seo/controllers/seo.controller.js +4 -2
- package/dist/seo/react/components/MediaRefField.d.ts +1 -1
- package/dist/seo/react/components/MediaRefField.js +19 -2
- package/dist/seo/react/hooks/MediaRefField.hooks.js +3 -1
- package/dist/seo/react/hooks/SeoPage.hooks.d.ts +1 -1
- package/dist/seo/react/hooks/use-share-image-warning.hooks.js +48 -34
- package/dist/seo/react/pages/SeoPage.d.ts +4 -4
- package/dist/seo/react/pages/SeoPage.js +26 -6
- package/dist/seo/rules.js +39 -10
- package/dist/source-control/conformance/source-control-api.conformance.js +68 -38
- package/dist/source-control/controllers/source-control.controller.js +20 -8
- package/dist/source-control/react/hooks/ProvidersTab.hooks.d.ts +2 -1
- package/dist/source-control/react/hooks/ProvidersTab.hooks.js +18 -11
- package/dist/widgets/conformance/widgets-api.conformance.js +15 -1
- package/dist/widgets/controllers/instance-editor.controller.js +33 -24
- package/dist/widgets/controllers/library.controller.js +18 -12
- package/dist/widgets/controllers/region-editor.controller.js +6 -5
- package/dist/widgets/react/hooks/page-views.hooks.d.ts +1 -0
- package/dist/widgets/react/hooks/page-views.hooks.js +3 -2
- package/dist/widgets/react/hooks/use-widget-regions.hooks.d.ts +1 -1
- package/dist/widgets/react/hooks/use-widget-regions.hooks.js +9 -3
- package/dist/widgets/react/hooks/use-widgets-library.hooks.js +9 -2
- package/dist/widgets/react/pages/WidgetInstanceEditor.d.ts +1 -1
- package/dist/widgets/react/pages/WidgetInstanceEditor.js +7 -8
- package/dist/widgets/rules.d.ts +14 -2
- package/dist/widgets/rules.js +9 -6
- package/package.json +23 -14
- package/src/themes/default/admin.css +51 -0
- package/src/themes/default/chat.css +21 -0
- package/src/themes/default/manifest.json +29 -0
- package/dist/media/styles.d.ts +0 -4
- package/dist/media/styles.js +0 -5
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ConsentFieldProblem, ConsentSettingsView } from './ConsentSettingsPanel.js';
|
|
2
|
+
/**
|
|
3
|
+
* @file The consent settings split into the three sections a host can place on its own tabs:
|
|
4
|
+
* Banner (copy and links), Look (placement and appearance) and Rules (on/off, region, version).
|
|
5
|
+
* `ConsentSettingsPanel` stacks all three for hosts that want one screen. Every section is
|
|
6
|
+
* controlled: it renders `value`, reports edits as patches and shows the host's `problems` next to
|
|
7
|
+
* the field each one names. Hosts style the plain `jini-` classes.
|
|
8
|
+
*/
|
|
9
|
+
type TextKey = 'title' | 'message' | 'acceptLabel' | 'rejectLabel' | 'preferencesLabel' | 'privacyPolicyUrl' | 'secondaryLinkLabel' | 'secondaryLinkUrl';
|
|
10
|
+
export interface ConsentFieldsProps {
|
|
11
|
+
value: ConsentSettingsView;
|
|
12
|
+
onChange(required: {
|
|
13
|
+
patch: Partial<ConsentSettingsView>;
|
|
14
|
+
}, optional?: Record<string, never>): void;
|
|
15
|
+
disabled?: boolean | undefined;
|
|
16
|
+
/** Server/engine problems; each one with a matching `field` renders under that control. */
|
|
17
|
+
problems?: readonly ConsentFieldProblem[] | undefined;
|
|
18
|
+
/** Host translations keyed by the exact fallback copy; unspecified entries use English. */
|
|
19
|
+
dictionary?: Readonly<Record<string, string>> | undefined;
|
|
20
|
+
/** Host max lengths per text field; the defaults are the engine's loosest bounds. */
|
|
21
|
+
limits?: Readonly<Partial<Record<TextKey | 'policyVersion', number>>> | undefined;
|
|
22
|
+
}
|
|
23
|
+
/** Banner copy and its links. Text is always plain text; the engine never renders it as HTML. */
|
|
24
|
+
export declare function ConsentBannerFields({ value, onChange, disabled, problems, dictionary, limits }: ConsentFieldsProps): import("react").JSX.Element;
|
|
25
|
+
/** On/off, region behavior and the policy version that re-asks visitors when it changes. */
|
|
26
|
+
export declare function ConsentRulesFields({ value, onChange, disabled, problems, dictionary, limits, regionModes }: ConsentFieldsProps & {
|
|
27
|
+
regionModes?: readonly ConsentSettingsView['regionMode'][];
|
|
28
|
+
}): import("react").JSX.Element;
|
|
29
|
+
/** Placement, bounded appearance tokens and allowlisted custom CSS. The engine checks contrast and
|
|
30
|
+
* CSS safety; its problems arrive on the `appearance` field and show under the custom CSS. */
|
|
31
|
+
export declare function ConsentLookFields({ value, onChange, disabled, problems, dictionary, placements }: ConsentFieldsProps & {
|
|
32
|
+
placements?: readonly string[];
|
|
33
|
+
}): import("react").JSX.Element;
|
|
34
|
+
export {};
|
|
35
|
+
//# sourceMappingURL=ConsentFields.d.ts.map
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useId } from 'react';
|
|
3
|
+
import { defaultAdminTheme } from '@jini-ai/ui/theme';
|
|
4
|
+
import { pixelInputValue, problemsFor, useConsentSettingsPanel, usePolicyVersionInput } from './settings-panel.hooks.js';
|
|
5
|
+
const BANNER_FIELDS = [
|
|
6
|
+
{ key: 'title', label: 'Banner title' }, { key: 'message', label: 'Banner message', multiline: true },
|
|
7
|
+
{ key: 'acceptLabel', label: 'Accept button text' }, { key: 'rejectLabel', label: 'Reject button text' },
|
|
8
|
+
{ key: 'preferencesLabel', label: 'Preferences label' }, { key: 'privacyPolicyUrl', label: 'Privacy policy page URL' },
|
|
9
|
+
{ key: 'secondaryLinkLabel', label: 'Second link text' }, { key: 'secondaryLinkUrl', label: 'Second link URL' },
|
|
10
|
+
];
|
|
11
|
+
const REGION_LABELS = {
|
|
12
|
+
auto: 'Automatic (unknown region: opt-in)', 'opt-in': 'Opt-in everywhere (EU / UK)', 'opt-out': 'Opt-out everywhere (California)',
|
|
13
|
+
};
|
|
14
|
+
const PLACEMENT_LABELS = { 'bottom-bar': 'Bottom bar', 'bottom-left': 'Bottom left', 'bottom-right': 'Bottom right', 'top-bar': 'Top bar' };
|
|
15
|
+
// Native color inputs need concrete colors rather than CSS variable expressions.
|
|
16
|
+
const COLOR_TOKENS = [
|
|
17
|
+
{ key: 'backgroundColor', label: 'Background', fallback: defaultAdminTheme.light.bg }, { key: 'textColor', label: 'Text', fallback: defaultAdminTheme.light.text }, { key: 'borderColor', label: 'Border', fallback: defaultAdminTheme.light.border },
|
|
18
|
+
];
|
|
19
|
+
/** The engine's own pixel bounds (`compileConsentCss`); out-of-range values come back as problems. */
|
|
20
|
+
const PIXEL_TOKENS = [
|
|
21
|
+
{ key: 'borderRadius', label: 'Corner radius (px)', min: 0, max: 32 }, { key: 'padding', label: 'Padding (px)', min: 8, max: 32 },
|
|
22
|
+
{ key: 'fontSize', label: 'Text size (px)', min: 16, max: 24 }, { key: 'maxWidth', label: 'Max width (px)', min: 256, max: 960 },
|
|
23
|
+
];
|
|
24
|
+
function translator(dictionary = {}) {
|
|
25
|
+
return (text) => dictionary[text] ?? text;
|
|
26
|
+
}
|
|
27
|
+
/** One field's problems, linked to its control through `aria-describedby`. */
|
|
28
|
+
function FieldProblems({ id, problems }) {
|
|
29
|
+
if (!problems.length)
|
|
30
|
+
return null;
|
|
31
|
+
return _jsx("span", { id: id, className: "jini-consent-field-problems", children: problems.map(problem => _jsx("span", { className: "jini-hint jini-hint-error", children: problem.message }, `${problem.code ?? ''}${problem.message}`)) });
|
|
32
|
+
}
|
|
33
|
+
/** Label + control + problems. Problems sit outside the label so they never join the control's
|
|
34
|
+
* name; they are its description and mark it invalid instead. */
|
|
35
|
+
function Field({ label, field, problems, children }) {
|
|
36
|
+
const id = useId();
|
|
37
|
+
const own = problemsFor({ problems, field });
|
|
38
|
+
const attributes = own.length ? { id, 'aria-invalid': true, 'aria-describedby': `${id}-problems` } : { id };
|
|
39
|
+
return _jsxs("div", { className: "jini-consent-settings-field", children: [_jsx("label", { htmlFor: id, className: "jini-field-label", children: label }), children(attributes), _jsx(FieldProblems, { id: `${id}-problems`, problems: own })] });
|
|
40
|
+
}
|
|
41
|
+
/** Banner copy and its links. Text is always plain text; the engine never renders it as HTML. */
|
|
42
|
+
export function ConsentBannerFields({ value, onChange, disabled = false, problems, dictionary, limits = {} }) {
|
|
43
|
+
const t = translator(dictionary);
|
|
44
|
+
const handlers = useConsentSettingsPanel({ value, onChange });
|
|
45
|
+
return _jsxs("fieldset", { disabled: disabled, className: "jini-consent-settings-fields", children: [_jsx("legend", { children: t('Banner text') }), BANNER_FIELDS.map(({ key, label, multiline }) => _jsx(Field, { label: t(label), field: key, problems: problems, children: aria => multiline
|
|
46
|
+
? _jsx("textarea", { className: "jini-input", value: value[key] ?? '', maxLength: limits[key] ?? 2000, rows: 3, onChange: handlers.text(key), ...aria })
|
|
47
|
+
: _jsx("input", { className: "jini-input", type: "text", value: value[key] ?? '', maxLength: limits[key] ?? 2000, onChange: handlers.text(key), ...aria }) }, key)), _jsx("p", { className: "jini-hint", children: t('Link a published privacy page, such as /privacy. The second link is optional.') })] });
|
|
48
|
+
}
|
|
49
|
+
/** On/off, region behavior and the policy version that re-asks visitors when it changes. */
|
|
50
|
+
export function ConsentRulesFields({ value, onChange, disabled = false, problems, dictionary, limits = {}, regionModes = ['auto', 'opt-in', 'opt-out'] }) {
|
|
51
|
+
const id = useId();
|
|
52
|
+
const t = translator(dictionary);
|
|
53
|
+
const handlers = useConsentSettingsPanel({ value, onChange });
|
|
54
|
+
const modes = regionModes.includes(value.regionMode) ? regionModes : [value.regionMode, ...regionModes];
|
|
55
|
+
return _jsxs("fieldset", { disabled: disabled, className: "jini-consent-settings-fields", children: [_jsx("legend", { children: t('Rules') }), _jsxs("button", { type: "button", className: value.enabled ? 'jini-toggle-row on' : 'jini-toggle-row', role: "switch", "aria-checked": value.enabled, "aria-labelledby": `${id}-enabled`, "aria-describedby": `${id}-hint`, onClick: handlers.toggle, children: [_jsxs("span", { className: "jini-toggle-row-text", children: [_jsx("span", { id: `${id}-enabled`, className: "jini-toggle-row-label", children: t('Enable cookie consent on this site') }), _jsx("span", { id: `${id}-hint`, className: "jini-toggle-row-hint", children: t('Applies to published site pages.') })] }), _jsx("span", { className: "jini-toggle-row-switch", "aria-hidden": "true" })] }), _jsx(Field, { label: t('Region mode'), field: "regionMode", problems: problems, children: aria => _jsx("select", { className: "jini-input", value: value.regionMode, onChange: handlers.region, ...aria, children: modes.map(mode => _jsx("option", { value: mode, children: t(REGION_LABELS[mode]) }, mode)) }) }), _jsx("p", { className: "jini-hint", children: t('California permits optional categories until opt-out. Global Privacy Control always disables analytics and marketing.') }), _jsx(Field, { label: t('Policy version'), field: "policyVersion", problems: problems, children: aria => _jsx(PolicyVersionInput, { value: value.policyVersion, onChange: onChange, maxLength: limits.policyVersion ?? 128, aria: aria }) }), _jsx("p", { className: "jini-hint", children: t('Change the policy version to ask visitors to choose again.') })] });
|
|
56
|
+
}
|
|
57
|
+
/** Placement, bounded appearance tokens and allowlisted custom CSS. The engine checks contrast and
|
|
58
|
+
* CSS safety; its problems arrive on the `appearance` field and show under the custom CSS. */
|
|
59
|
+
export function ConsentLookFields({ value, onChange, disabled = false, problems, dictionary, placements = Object.keys(PLACEMENT_LABELS) }) {
|
|
60
|
+
const t = translator(dictionary);
|
|
61
|
+
const handlers = useConsentSettingsPanel({ value, onChange });
|
|
62
|
+
const appearance = value.appearance ?? {};
|
|
63
|
+
return _jsxs("fieldset", { disabled: disabled, className: "jini-consent-settings-fields", children: [_jsx("legend", { children: t('Look & position') }), _jsx(Field, { label: t('Position'), field: "placement", problems: problems, children: aria => _jsx("select", { className: "jini-input", value: value.placement ?? placements[0], onChange: handlers.placement, ...aria, children: placements.map(placement => _jsx("option", { value: placement, children: t(PLACEMENT_LABELS[placement] ?? placement) }, placement)) }) }), _jsxs("div", { className: "jini-consent-token-grid", children: [COLOR_TOKENS.map(token => _jsx(ColorToken, { label: t(token.label), value: appearance[token.key], fallback: token.fallback, resetLabel: t('Default'), onChange: handlers.token(token.key) }, token.key)), PIXEL_TOKENS.map(token => _jsx(Field, { label: t(token.label), field: token.key, children: attributes => _jsx("input", { className: "jini-input", type: "number", inputMode: "numeric", min: token.min, max: token.max, step: 1, placeholder: t('Default'), value: pixelInputValue({ value: appearance[token.key] }), onChange: event => handlers.token(token.key)(event.target.value === '' ? '' : `${event.target.value}px`), ...attributes }) }, token.key))] }), _jsx(Field, { label: t('Custom CSS'), field: "appearance", problems: problems, children: aria => _jsx("textarea", { className: "jini-input jini-consent-css", value: value.customCss ?? '', rows: 6, spellCheck: false, onChange: handlers.text('customCss'), ...aria }) }), _jsx("p", { className: "jini-hint", children: t('Only banner selectors and safe properties are allowed. Text needs 4.5:1 contrast.') })] });
|
|
64
|
+
}
|
|
65
|
+
/** A native color picker; unset shows the shared theme default and offers no reset. */
|
|
66
|
+
function ColorToken({ label, value, fallback, resetLabel, onChange }) {
|
|
67
|
+
const id = useId();
|
|
68
|
+
return _jsxs("div", { className: "jini-consent-settings-field", children: [_jsx("label", { htmlFor: id, className: "jini-field-label", children: label }), _jsxs("span", { className: "jini-consent-color-row", children: [_jsx("input", { id: id, className: "jini-consent-color", type: "color", value: value ?? fallback, onChange: event => onChange(event.target.value) }), _jsx("code", { className: "jini-hint", children: value ?? resetLabel }), value !== undefined && _jsx("button", { type: "button", className: "jini-btn-link", "aria-label": `${resetLabel}: ${label}`, onClick: () => onChange(''), children: resetLabel })] })] });
|
|
69
|
+
}
|
|
70
|
+
/** Keep transient empty edits local; only a valid policy version reaches autosave.
|
|
71
|
+
* Native validity gives keyboard users an actionable error. @complexity O(version length), <=128. */
|
|
72
|
+
function PolicyVersionInput({ value, onChange, maxLength, aria }) {
|
|
73
|
+
const { draft, change, blur } = usePolicyVersionInput({ value, onChange });
|
|
74
|
+
return _jsx("input", { className: "jini-input", type: "text", value: draft, required: true, pattern: String.raw `.*\S.*`, maxLength: maxLength, onChange: change, onBlur: blur, ...aria });
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=ConsentFields.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type ConsentPreviewWidth } from './settings-panel.hooks.js';
|
|
2
|
+
export interface ConsentPreviewFrameProps {
|
|
3
|
+
/** The engine-gated page; `null` while it loads or when the host could not render it. */
|
|
4
|
+
html: string | null;
|
|
5
|
+
/** Shown in place of the frame while `html` is null. */
|
|
6
|
+
status?: string;
|
|
7
|
+
initialWidth?: ConsentPreviewWidth;
|
|
8
|
+
dictionary?: Readonly<Record<string, string>>;
|
|
9
|
+
}
|
|
10
|
+
/** The real rendered page with the banner, inert. The HTML is only ever the `srcdoc` of an iframe
|
|
11
|
+
* with an empty `sandbox`: no scripts, forms, popups or same-origin access, and it never enters the
|
|
12
|
+
* host DOM. Phone is 320px, the narrowest width the banner must still work at. */
|
|
13
|
+
export declare function ConsentPreviewFrame({ html, status, initialWidth, dictionary }: ConsentPreviewFrameProps): import("react").JSX.Element;
|
|
14
|
+
//# sourceMappingURL=ConsentPreviewFrame.d.ts.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useConsentPreviewWidth } from './settings-panel.hooks.js';
|
|
3
|
+
const WIDTHS = [{ id: 'phone', label: 'Phone' }, { id: 'desktop', label: 'Desktop' }];
|
|
4
|
+
/** The real rendered page with the banner, inert. The HTML is only ever the `srcdoc` of an iframe
|
|
5
|
+
* with an empty `sandbox`: no scripts, forms, popups or same-origin access, and it never enters the
|
|
6
|
+
* host DOM. Phone is 320px, the narrowest width the banner must still work at. */
|
|
7
|
+
export function ConsentPreviewFrame({ html, status = '', initialWidth = 'desktop', dictionary = {} }) {
|
|
8
|
+
const t = (text) => dictionary[text] ?? text;
|
|
9
|
+
const { width, setWidth } = useConsentPreviewWidth({ initial: initialWidth });
|
|
10
|
+
return _jsxs("section", { className: "jini-consent-preview", "aria-label": t('Banner preview'), children: [_jsx("div", { className: "jini-consent-preview-widths", role: "group", "aria-label": t('Preview width'), children: WIDTHS.map(option => _jsx("button", { type: "button", className: "jini-btn-secondary", "aria-pressed": width === option.id, onClick: () => setWidth(option.id), children: t(option.label) }, option.id)) }), html === null
|
|
11
|
+
? _jsx("p", { className: "jini-hint jini-consent-preview-empty", children: status })
|
|
12
|
+
: _jsx("iframe", { className: `jini-consent-preview-frame is-${width}`, title: t('Banner preview'), sandbox: "", srcDoc: html, style: { width: width === 'phone' ? '320px' : '100%' } })] });
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=ConsentPreviewFrame.js.map
|
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
import { type ConsentFieldsProps } from './ConsentFields.js';
|
|
2
|
+
/** Bounded appearance tokens, as the engine accepts them: opaque hex colors and `Npx` lengths. */
|
|
3
|
+
export interface ConsentAppearanceView {
|
|
4
|
+
backgroundColor?: string;
|
|
5
|
+
textColor?: string;
|
|
6
|
+
borderColor?: string;
|
|
7
|
+
borderRadius?: string;
|
|
8
|
+
padding?: string;
|
|
9
|
+
fontSize?: string;
|
|
10
|
+
maxWidth?: string;
|
|
11
|
+
}
|
|
12
|
+
/** The host's consent configuration. The look fields are optional so older hosts stay valid. */
|
|
2
13
|
export interface ConsentSettingsView {
|
|
3
14
|
enabled: boolean;
|
|
4
15
|
regionMode: 'auto' | 'opt-in' | 'opt-out';
|
|
@@ -9,8 +20,19 @@ export interface ConsentSettingsView {
|
|
|
9
20
|
acceptLabel: string;
|
|
10
21
|
rejectLabel: string;
|
|
11
22
|
preferencesLabel: string;
|
|
23
|
+
placement?: string;
|
|
24
|
+
secondaryLinkLabel?: string;
|
|
25
|
+
secondaryLinkUrl?: string;
|
|
26
|
+
appearance?: ConsentAppearanceView;
|
|
27
|
+
customCss?: string;
|
|
28
|
+
}
|
|
29
|
+
/** One validation result. `field` names the setting it belongs to; field-less problems are the host's to show. */
|
|
30
|
+
export interface ConsentFieldProblem {
|
|
31
|
+
message: string;
|
|
32
|
+
field?: string;
|
|
33
|
+
code?: string;
|
|
12
34
|
}
|
|
13
|
-
export interface ConsentSettingsPanelProps {
|
|
35
|
+
export interface ConsentSettingsPanelProps extends Pick<ConsentFieldsProps, 'problems' | 'limits'> {
|
|
14
36
|
value: ConsentSettingsView;
|
|
15
37
|
onChange(required: {
|
|
16
38
|
patch: Partial<ConsentSettingsView>;
|
|
@@ -21,7 +43,8 @@ export interface ConsentSettingsPanelProps {
|
|
|
21
43
|
/** Host translations keyed by the exact fallback copy; unspecified entries use English. */
|
|
22
44
|
dictionary?: Readonly<Record<string, string>>;
|
|
23
45
|
}
|
|
24
|
-
/**
|
|
46
|
+
/** The composed default for hosts that want one screen: Rules, Banner and Look stacked.
|
|
47
|
+
* Shared jini settings classes keep the panel in the host's design language.
|
|
25
48
|
* Native labels/selects/checkboxes support keyboard use at desktop and narrow widths. */
|
|
26
|
-
export declare function ConsentSettingsPanel({ value, onChange, loading, error, status, dictionary }: ConsentSettingsPanelProps): import("react").JSX.Element;
|
|
49
|
+
export declare function ConsentSettingsPanel({ value, onChange, loading, error, status, dictionary, problems, limits }: ConsentSettingsPanelProps): import("react").JSX.Element;
|
|
27
50
|
//# sourceMappingURL=ConsentSettingsPanel.d.ts.map
|
|
@@ -1,24 +1,13 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useId } from 'react';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
['acceptLabel', 'Accept button text', 2000], ['rejectLabel', 'Reject button text', 2000],
|
|
7
|
-
['preferencesLabel', 'Preferences label', 2000], ['policyVersion', 'Policy version', 128],
|
|
8
|
-
['privacyPolicyUrl', 'Privacy policy page URL', 2000],
|
|
9
|
-
];
|
|
10
|
-
/** Shared jini settings classes keep the panel in the host's design language.
|
|
3
|
+
import { ConsentBannerFields, ConsentLookFields, ConsentRulesFields } from './ConsentFields.js';
|
|
4
|
+
/** The composed default for hosts that want one screen: Rules, Banner and Look stacked.
|
|
5
|
+
* Shared jini settings classes keep the panel in the host's design language.
|
|
11
6
|
* Native labels/selects/checkboxes support keyboard use at desktop and narrow widths. */
|
|
12
|
-
export function ConsentSettingsPanel({ value, onChange, loading, error, status, dictionary = {} }) {
|
|
7
|
+
export function ConsentSettingsPanel({ value, onChange, loading, error, status, dictionary = {}, problems, limits }) {
|
|
13
8
|
const id = useId();
|
|
14
9
|
const t = (text) => dictionary[text] ?? text;
|
|
15
|
-
const
|
|
16
|
-
return _jsxs("section", { className: "jini-settings-section", "aria-labelledby": `${id}-heading`, children: [_jsx("div", { className: "jini-section-head", children: _jsxs("div", { children: [_jsx("h4", { id: `${id}-heading`, children: t('Site cookie consent') }), _jsx("p", { className: "jini-hint", children: t('Provides the consent mechanism, not legal advice. Review your texts, categories, and privacy policy for your site.') })] }) }), _jsx("p", { className: "jini-hint", children: t('Off by default. In opt-in regions, optional scripts stay paused until a visitor chooses. Unclassified third-party scripts use the marketing category. Global Privacy Control disables analytics and marketing.') }),
|
|
17
|
-
}
|
|
18
|
-
/** Keep transient empty edits local; only a valid policy version reaches autosave.
|
|
19
|
-
* Native validity gives keyboard users an actionable error. @complexity O(version length), <=128. */
|
|
20
|
-
function PolicyVersionInput({ value, onChange }) {
|
|
21
|
-
const { draft, change, blur } = usePolicyVersionInput({ value, onChange });
|
|
22
|
-
return _jsx("input", { className: "jini-input", type: "text", value: draft, required: true, pattern: String.raw `.*\S.*`, maxLength: 128, onChange: change, onBlur: blur });
|
|
10
|
+
const fields = { value, onChange, disabled: loading, problems, dictionary, limits };
|
|
11
|
+
return _jsxs("section", { className: "jini-settings-section", "aria-labelledby": `${id}-heading`, children: [_jsx("div", { className: "jini-section-head", children: _jsxs("div", { children: [_jsx("h4", { id: `${id}-heading`, children: t('Site cookie consent') }), _jsx("p", { className: "jini-hint", children: t('Provides the consent mechanism, not legal advice. Review your texts, categories, and privacy policy for your site.') })] }) }), _jsx("p", { className: "jini-hint", children: t('Off by default. In opt-in regions, optional scripts stay paused until a visitor chooses. Unclassified third-party scripts use the marketing category. Global Privacy Control disables analytics and marketing.') }), _jsx(ConsentRulesFields, { ...fields }), _jsx(ConsentBannerFields, { ...fields }), _jsx(ConsentLookFields, { ...fields }), error && _jsx("p", { className: "jini-hint jini-hint-error", role: "alert", children: error }), _jsx("p", { className: "jini-hint", role: "status", children: status })] });
|
|
23
12
|
}
|
|
24
13
|
//# sourceMappingURL=ConsentSettingsPanel.js.map
|
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
export { ConsentSettingsPanel } from './ConsentSettingsPanel.js';
|
|
2
|
-
export type { ConsentSettingsPanelProps, ConsentSettingsView } from './ConsentSettingsPanel.js';
|
|
2
|
+
export type { ConsentAppearanceView, ConsentFieldProblem, ConsentSettingsPanelProps, ConsentSettingsView } from './ConsentSettingsPanel.js';
|
|
3
|
+
export { ConsentBannerFields, ConsentLookFields, ConsentRulesFields } from './ConsentFields.js';
|
|
4
|
+
export type { ConsentFieldsProps } from './ConsentFields.js';
|
|
5
|
+
export { ConsentPreviewFrame } from './ConsentPreviewFrame.js';
|
|
6
|
+
export type { ConsentPreviewFrameProps } from './ConsentPreviewFrame.js';
|
|
7
|
+
export type { ConsentPreviewWidth } from './settings-panel.hooks.js';
|
|
3
8
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,11 +1,30 @@
|
|
|
1
1
|
import { type ChangeEvent, type FocusEvent } from 'react';
|
|
2
|
-
import type { ConsentSettingsPanelProps, ConsentSettingsView } from './ConsentSettingsPanel.js';
|
|
2
|
+
import type { ConsentAppearanceView, ConsentFieldProblem, ConsentSettingsPanelProps, ConsentSettingsView } from './ConsentSettingsPanel.js';
|
|
3
3
|
/** Keep value conversions and toggle behavior out of the view. React owns event signatures. */
|
|
4
4
|
export declare function useConsentSettingsPanel({ value, onChange }: Pick<ConsentSettingsPanelProps, 'value' | 'onChange'>, _options?: {}): {
|
|
5
5
|
toggle: () => void;
|
|
6
6
|
region: (event: ChangeEvent<HTMLSelectElement>) => void;
|
|
7
|
+
placement: (event: ChangeEvent<HTMLSelectElement>) => void;
|
|
7
8
|
text: (key: keyof ConsentSettingsView) => (event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
|
|
9
|
+
/** Empty input unsets the token so the engine default applies again. */
|
|
10
|
+
token: (key: keyof ConsentAppearanceView) => (next: string) => void;
|
|
8
11
|
};
|
|
12
|
+
/** A new token object with `key` set, or removed when `value` is empty. Never mutates the input.
|
|
13
|
+
* @complexity O(t), t <= 7 tokens. */
|
|
14
|
+
export declare function withAppearanceToken({ appearance, key, value }: {
|
|
15
|
+
appearance?: ConsentAppearanceView | undefined;
|
|
16
|
+
key: keyof ConsentAppearanceView;
|
|
17
|
+
value: string;
|
|
18
|
+
}, _options?: {}): ConsentAppearanceView;
|
|
19
|
+
/** `"12px"` → `"12"` for a number input; unset or hand-written values read empty. */
|
|
20
|
+
export declare function pixelInputValue({ value }: {
|
|
21
|
+
value: string | undefined;
|
|
22
|
+
}, _options?: {}): string;
|
|
23
|
+
/** The problems a host attached to one field, in their original order. O(p). */
|
|
24
|
+
export declare function problemsFor({ problems, field }: {
|
|
25
|
+
problems?: readonly ConsentFieldProblem[] | undefined;
|
|
26
|
+
field: string;
|
|
27
|
+
}, _options?: {}): ConsentFieldProblem[];
|
|
9
28
|
/** Keep transient empty edits local; only a valid policy version reaches autosave.
|
|
10
29
|
* Native validity gives keyboard users an actionable error. Valid edits save before navigation.
|
|
11
30
|
* @complexity O(version length), <=128. */
|
|
@@ -16,4 +35,12 @@ export declare function usePolicyVersionInput({ value, onChange }: Pick<ConsentS
|
|
|
16
35
|
change: (event: ChangeEvent<HTMLInputElement>) => void;
|
|
17
36
|
blur: (event: FocusEvent<HTMLInputElement>) => void;
|
|
18
37
|
};
|
|
38
|
+
export type ConsentPreviewWidth = 'phone' | 'desktop';
|
|
39
|
+
/** The preview's own phone/desktop switch; the host never needs to own it. */
|
|
40
|
+
export declare function useConsentPreviewWidth({ initial }?: {
|
|
41
|
+
initial?: ConsentPreviewWidth;
|
|
42
|
+
}, _options?: {}): {
|
|
43
|
+
width: ConsentPreviewWidth;
|
|
44
|
+
setWidth: import("react").Dispatch<import("react").SetStateAction<ConsentPreviewWidth>>;
|
|
45
|
+
};
|
|
19
46
|
//# sourceMappingURL=settings-panel.hooks.d.ts.map
|
|
@@ -4,9 +4,30 @@ export function useConsentSettingsPanel({ value, onChange }, _options = {}) {
|
|
|
4
4
|
return {
|
|
5
5
|
toggle: () => onChange({ patch: { enabled: !value.enabled } }),
|
|
6
6
|
region: (event) => onChange({ patch: { regionMode: event.target.value } }),
|
|
7
|
+
placement: (event) => onChange({ patch: { placement: event.target.value } }),
|
|
7
8
|
text: (key) => (event) => onChange({ patch: { [key]: event.target.value } }),
|
|
9
|
+
/** Empty input unsets the token so the engine default applies again. */
|
|
10
|
+
token: (key) => (next) => onChange({ patch: { appearance: withAppearanceToken({ appearance: value.appearance, key, value: next }) } }),
|
|
8
11
|
};
|
|
9
12
|
}
|
|
13
|
+
/** A new token object with `key` set, or removed when `value` is empty. Never mutates the input.
|
|
14
|
+
* @complexity O(t), t <= 7 tokens. */
|
|
15
|
+
export function withAppearanceToken({ appearance = {}, key, value }, _options = {}) {
|
|
16
|
+
const next = { ...appearance };
|
|
17
|
+
if (value === '')
|
|
18
|
+
delete next[key];
|
|
19
|
+
else
|
|
20
|
+
next[key] = value;
|
|
21
|
+
return next;
|
|
22
|
+
}
|
|
23
|
+
/** `"12px"` → `"12"` for a number input; unset or hand-written values read empty. */
|
|
24
|
+
export function pixelInputValue({ value }, _options = {}) {
|
|
25
|
+
return /^(\d+(?:\.\d+)?)px$/.exec(value ?? '')?.[1] ?? '';
|
|
26
|
+
}
|
|
27
|
+
/** The problems a host attached to one field, in their original order. O(p). */
|
|
28
|
+
export function problemsFor({ problems = [], field }, _options = {}) {
|
|
29
|
+
return problems.filter(problem => problem.field === field);
|
|
30
|
+
}
|
|
10
31
|
/** Keep transient empty edits local; only a valid policy version reaches autosave.
|
|
11
32
|
* Native validity gives keyboard users an actionable error. Valid edits save before navigation.
|
|
12
33
|
* @complexity O(version length), <=128. */
|
|
@@ -27,4 +48,9 @@ export function usePolicyVersionInput({ value, onChange }, _options = {}) {
|
|
|
27
48
|
},
|
|
28
49
|
};
|
|
29
50
|
}
|
|
51
|
+
/** The preview's own phone/desktop switch; the host never needs to own it. */
|
|
52
|
+
export function useConsentPreviewWidth({ initial = 'desktop' } = {}, _options = {}) {
|
|
53
|
+
const [width, setWidth] = useState(initial);
|
|
54
|
+
return { width, setWidth };
|
|
55
|
+
}
|
|
30
56
|
//# sourceMappingURL=settings-panel.hooks.js.map
|