@entifix/react-controls 0.19.0 → 0.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/dist/i18n/catalog/en.d.ts.map +1 -1
- package/dist/i18n/catalog/en.js +55 -9
- package/dist/i18n/catalog/en.js.map +1 -1
- package/dist/i18n/catalog/es.d.ts +64 -5
- package/dist/i18n/catalog/es.d.ts.map +1 -1
- package/dist/i18n/catalog/es.js +61 -9
- package/dist/i18n/catalog/es.js.map +1 -1
- package/dist/i18n/catalog/index.d.ts +102 -10
- package/dist/i18n/catalog/index.d.ts.map +1 -1
- package/dist/i18n/entity-labels.d.ts +14 -0
- package/dist/i18n/entity-labels.d.ts.map +1 -1
- package/dist/i18n/entity-labels.js +15 -0
- package/dist/i18n/entity-labels.js.map +1 -1
- package/dist/i18n/i18n-context.js +2 -2
- package/dist/i18n/i18n-context.js.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/primitives.d.ts +0 -1
- package/dist/primitives.d.ts.map +1 -1
- package/dist/primitives.js +0 -1
- package/dist/primitives.js.map +1 -1
- package/dist/theme/index.d.ts +1 -0
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/index.js +1 -0
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/theme-context.d.ts +15 -3
- package/dist/theme/theme-context.d.ts.map +1 -1
- package/dist/theme/theme-context.js +49 -23
- package/dist/theme/theme-context.js.map +1 -1
- package/dist/theme/theme-cookie.d.ts +9 -0
- package/dist/theme/theme-cookie.d.ts.map +1 -0
- package/dist/theme/theme-cookie.js +8 -0
- package/dist/theme/theme-cookie.js.map +1 -0
- package/dist/theme/theme-script.d.ts +8 -6
- package/dist/theme/theme-script.d.ts.map +1 -1
- package/dist/theme/theme-script.js +21 -12
- package/dist/theme/theme-script.js.map +1 -1
- package/dist/ui/actions/action-surfaces.d.ts +1 -1
- package/dist/ui/actions/action-surfaces.js +1 -1
- package/dist/ui/actions/action-surfaces.js.map +1 -1
- package/dist/ui/actions/error-actions.d.ts +32 -0
- package/dist/ui/actions/error-actions.d.ts.map +1 -0
- package/dist/ui/actions/error-actions.js +40 -0
- package/dist/ui/actions/error-actions.js.map +1 -0
- package/dist/ui/actions/index.d.ts +3 -0
- package/dist/ui/actions/index.d.ts.map +1 -1
- package/dist/ui/actions/index.js +3 -0
- package/dist/ui/actions/index.js.map +1 -1
- package/dist/ui/actions/shortcut.d.ts +13 -0
- package/dist/ui/actions/shortcut.d.ts.map +1 -0
- package/dist/ui/actions/shortcut.js +18 -0
- package/dist/ui/actions/shortcut.js.map +1 -0
- package/dist/ui/actions/use-verb-invoker.d.ts +55 -0
- package/dist/ui/actions/use-verb-invoker.d.ts.map +1 -0
- package/dist/ui/actions/use-verb-invoker.js +98 -0
- package/dist/ui/actions/use-verb-invoker.js.map +1 -0
- package/dist/ui/atoms/cell-value/cell-value.d.ts.map +1 -1
- package/dist/ui/atoms/cell-value/cell-value.js +28 -14
- package/dist/ui/atoms/cell-value/cell-value.js.map +1 -1
- package/dist/ui/atoms/field-control/field-control.d.ts +1 -0
- package/dist/ui/atoms/field-control/field-control.d.ts.map +1 -1
- package/dist/ui/atoms/field-control/field-control.js +2 -0
- package/dist/ui/atoms/field-control/field-control.js.map +1 -1
- package/dist/ui/atoms/number-input/quantity-input.d.ts +8 -1
- package/dist/ui/atoms/number-input/quantity-input.d.ts.map +1 -1
- package/dist/ui/atoms/number-input/quantity-input.js +2 -1
- package/dist/ui/atoms/number-input/quantity-input.js.map +1 -1
- package/dist/ui/atoms/table/table.d.ts +12 -3
- package/dist/ui/atoms/table/table.d.ts.map +1 -1
- package/dist/ui/atoms/table/table.js +59 -10
- package/dist/ui/atoms/table/table.js.map +1 -1
- package/dist/ui/atoms/text/text.d.ts +2 -2
- package/dist/ui/atoms/text/text.d.ts.map +1 -1
- package/dist/ui/atoms/text/text.js.map +1 -1
- package/dist/ui/molecules/action-bar/action-bar.d.ts +76 -0
- package/dist/ui/molecules/action-bar/action-bar.d.ts.map +1 -0
- package/dist/ui/molecules/action-bar/action-bar.js +218 -0
- package/dist/ui/molecules/action-bar/action-bar.js.map +1 -0
- package/dist/ui/molecules/action-bar/index.d.ts +2 -0
- package/dist/ui/molecules/action-bar/index.d.ts.map +1 -0
- package/dist/ui/molecules/action-bar/index.js +3 -0
- package/dist/ui/molecules/action-bar/index.js.map +1 -0
- package/dist/ui/molecules/bulk-action-bar/bulk-action-bar.d.ts.map +1 -1
- package/dist/ui/molecules/bulk-action-bar/bulk-action-bar.js +8 -10
- package/dist/ui/molecules/bulk-action-bar/bulk-action-bar.js.map +1 -1
- package/dist/ui/molecules/bulk-result/bulk-result.d.ts +3 -3
- package/dist/ui/molecules/bulk-result/bulk-result.js +4 -4
- package/dist/ui/molecules/bulk-result/bulk-result.js.map +1 -1
- package/dist/ui/molecules/confirm-dialog/confirm-dialog.d.ts +24 -9
- package/dist/ui/molecules/confirm-dialog/confirm-dialog.d.ts.map +1 -1
- package/dist/ui/molecules/confirm-dialog/confirm-dialog.js +36 -11
- package/dist/ui/molecules/confirm-dialog/confirm-dialog.js.map +1 -1
- package/dist/ui/molecules/entity-record-card/entity-record-card.d.ts +33 -20
- package/dist/ui/molecules/entity-record-card/entity-record-card.d.ts.map +1 -1
- package/dist/ui/molecules/entity-record-card/entity-record-card.js +53 -20
- package/dist/ui/molecules/entity-record-card/entity-record-card.js.map +1 -1
- package/dist/ui/molecules/filter-builder/filter-builder.d.ts +8 -2
- package/dist/ui/molecules/filter-builder/filter-builder.d.ts.map +1 -1
- package/dist/ui/molecules/filter-builder/filter-builder.js +14 -5
- package/dist/ui/molecules/filter-builder/filter-builder.js.map +1 -1
- package/dist/ui/molecules/filter-chips/filter-chips.d.ts +16 -0
- package/dist/ui/molecules/filter-chips/filter-chips.d.ts.map +1 -0
- package/dist/ui/molecules/filter-chips/filter-chips.js +73 -0
- package/dist/ui/molecules/filter-chips/filter-chips.js.map +1 -0
- package/dist/ui/molecules/filter-chips/index.d.ts +2 -0
- package/dist/ui/molecules/filter-chips/index.d.ts.map +1 -0
- package/dist/ui/molecules/filter-chips/index.js +3 -0
- package/dist/ui/molecules/filter-chips/index.js.map +1 -0
- package/dist/ui/molecules/menu/menu.d.ts +11 -1
- package/dist/ui/molecules/menu/menu.d.ts.map +1 -1
- package/dist/ui/molecules/menu/menu.js +62 -2
- package/dist/ui/molecules/menu/menu.js.map +1 -1
- package/dist/ui/molecules/pagination/pagination.d.ts +5 -0
- package/dist/ui/molecules/pagination/pagination.d.ts.map +1 -1
- package/dist/ui/molecules/pagination/pagination.js +35 -18
- package/dist/ui/molecules/pagination/pagination.js.map +1 -1
- package/dist/ui/molecules/region-state/index.d.ts +2 -0
- package/dist/ui/molecules/region-state/index.d.ts.map +1 -0
- package/dist/ui/molecules/region-state/index.js +3 -0
- package/dist/ui/molecules/region-state/index.js.map +1 -0
- package/dist/ui/molecules/region-state/region-state.d.ts +42 -0
- package/dist/ui/molecules/region-state/region-state.d.ts.map +1 -0
- package/dist/ui/molecules/region-state/region-state.js +83 -0
- package/dist/ui/molecules/region-state/region-state.js.map +1 -0
- package/dist/ui/molecules/table-toolbar/table-toolbar.d.ts +3 -1
- package/dist/ui/molecules/table-toolbar/table-toolbar.d.ts.map +1 -1
- package/dist/ui/molecules/table-toolbar/table-toolbar.js +3 -2
- package/dist/ui/molecules/table-toolbar/table-toolbar.js.map +1 -1
- package/dist/ui/molecules/toast-region/toast-region.d.ts +63 -19
- package/dist/ui/molecules/toast-region/toast-region.d.ts.map +1 -1
- package/dist/ui/molecules/toast-region/toast-region.js +129 -42
- package/dist/ui/molecules/toast-region/toast-region.js.map +1 -1
- package/dist/ui/organisms/command-palette/command-palette.d.ts.map +1 -1
- package/dist/ui/organisms/command-palette/command-palette.js +19 -12
- package/dist/ui/organisms/command-palette/command-palette.js.map +1 -1
- package/dist/ui/organisms/entity-detail-grid/entity-detail-grid.d.ts.map +1 -1
- package/dist/ui/organisms/entity-detail-grid/entity-detail-grid.js +0 -1
- package/dist/ui/organisms/entity-detail-grid/entity-detail-grid.js.map +1 -1
- package/dist/ui/organisms/entity-form/entity-form.d.ts +1 -1
- package/dist/ui/organisms/entity-form/entity-form.d.ts.map +1 -1
- package/dist/ui/organisms/entity-form/entity-form.js +322 -214
- package/dist/ui/organisms/entity-form/entity-form.js.map +1 -1
- package/dist/ui/organisms/entity-form/entity-form.types.d.ts +16 -0
- package/dist/ui/organisms/entity-form/entity-form.types.d.ts.map +1 -1
- package/dist/ui/organisms/entity-form/entity-form.types.js.map +1 -1
- package/dist/ui/organisms/entity-table/entity-table.d.ts +1 -1
- package/dist/ui/organisms/entity-table/entity-table.d.ts.map +1 -1
- package/dist/ui/organisms/entity-table/entity-table.js +332 -197
- package/dist/ui/organisms/entity-table/entity-table.js.map +1 -1
- package/dist/ui/organisms/entity-table/entity-table.types.d.ts +19 -4
- package/dist/ui/organisms/entity-table/entity-table.types.d.ts.map +1 -1
- package/dist/ui/organisms/entity-table/entity-table.types.js.map +1 -1
- package/dist/ui/organisms/entity-table/header-sort.d.ts +18 -0
- package/dist/ui/organisms/entity-table/header-sort.d.ts.map +1 -0
- package/dist/ui/organisms/entity-table/header-sort.js +54 -0
- package/dist/ui/organisms/entity-table/header-sort.js.map +1 -0
- package/dist/ui/organisms/entity-table/pivot.d.ts +25 -0
- package/dist/ui/organisms/entity-table/pivot.d.ts.map +1 -0
- package/dist/ui/organisms/entity-table/pivot.js +106 -0
- package/dist/ui/organisms/entity-table/pivot.js.map +1 -0
- package/dist/ui/organisms/entity-table/use-result-announcement.d.ts +13 -0
- package/dist/ui/organisms/entity-table/use-result-announcement.d.ts.map +1 -0
- package/dist/ui/organisms/entity-table/use-result-announcement.js +31 -0
- package/dist/ui/organisms/entity-table/use-result-announcement.js.map +1 -0
- package/dist/ui/organisms/top-bar/live-indicator.d.ts +11 -0
- package/dist/ui/organisms/top-bar/live-indicator.d.ts.map +1 -0
- package/dist/ui/organisms/top-bar/live-indicator.js +46 -0
- package/dist/ui/organisms/top-bar/live-indicator.js.map +1 -0
- package/dist/ui/organisms/top-bar/top-bar.d.ts +11 -2
- package/dist/ui/organisms/top-bar/top-bar.d.ts.map +1 -1
- package/dist/ui/organisms/top-bar/top-bar.js +14 -5
- package/dist/ui/organisms/top-bar/top-bar.js.map +1 -1
- package/dist/ui/organisms/wizard/wizard.d.ts +1 -1
- package/dist/ui/organisms/wizard/wizard.d.ts.map +1 -1
- package/dist/ui/organisms/wizard/wizard.js +7 -1
- package/dist/ui/organisms/wizard/wizard.js.map +1 -1
- package/dist/ui/organisms/wizard/wizard.types.d.ts +8 -0
- package/dist/ui/organisms/wizard/wizard.types.d.ts.map +1 -1
- package/dist/ui/organisms/wizard/wizard.types.js.map +1 -1
- package/dist/ui/utils/live-updates.d.ts +24 -0
- package/dist/ui/utils/live-updates.d.ts.map +1 -0
- package/dist/ui/utils/live-updates.js +20 -0
- package/dist/ui/utils/live-updates.js.map +1 -0
- package/dist/ui/utils/use-focus-on-arrival.d.ts +14 -0
- package/dist/ui/utils/use-focus-on-arrival.d.ts.map +1 -0
- package/dist/ui/utils/use-focus-on-arrival.js +42 -0
- package/dist/ui/utils/use-focus-on-arrival.js.map +1 -0
- package/dist/ui/utils/use-hotkey.d.ts +44 -25
- package/dist/ui/utils/use-hotkey.d.ts.map +1 -1
- package/dist/ui/utils/use-hotkey.js +80 -48
- package/dist/ui/utils/use-hotkey.js.map +1 -1
- package/package.json +5 -5
- package/dist/ui/molecules/split-button/index.d.ts +0 -2
- package/dist/ui/molecules/split-button/index.d.ts.map +0 -1
- package/dist/ui/molecules/split-button/index.js +0 -3
- package/dist/ui/molecules/split-button/index.js.map +0 -1
- package/dist/ui/molecules/split-button/split-button.d.ts +0 -21
- package/dist/ui/molecules/split-button/split-button.d.ts.map +0 -1
- package/dist/ui/molecules/split-button/split-button.js +0 -35
- package/dist/ui/molecules/split-button/split-button.js.map +0 -1
|
@@ -12,10 +12,20 @@ export interface ThemeProviderProps extends PropsWithChildren {
|
|
|
12
12
|
*/
|
|
13
13
|
defaultDensity?: Density;
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
16
|
-
* The density is
|
|
15
|
+
* The cookie the theme is kept in — namespace it per app so apps don't
|
|
16
|
+
* clobber each other. The density is kept beside it, under
|
|
17
|
+
* `${storageKey}-density`. A cookie rather than local storage, so a server
|
|
18
|
+
* can read the choice and render `data-theme` and `data-density` with the
|
|
19
|
+
* page: no paint in the wrong palette before a script corrects it.
|
|
17
20
|
*/
|
|
18
21
|
storageKey?: string;
|
|
22
|
+
/**
|
|
23
|
+
* The theme and density the server read from those cookies and rendered
|
|
24
|
+
* the page in (`readThemeCookies` in `@entifix/next-shell/server`). Given,
|
|
25
|
+
* the provider starts there and nothing changes after the page loads.
|
|
26
|
+
*/
|
|
27
|
+
initialTheme?: string;
|
|
28
|
+
initialDensity?: Density;
|
|
19
29
|
/**
|
|
20
30
|
* Runtime palettes for brands not shipped as CSS presets (multi-tenant /
|
|
21
31
|
* white-label). Keyed by theme id; injected as `[data-theme='id']{…}` vars
|
|
@@ -37,6 +47,8 @@ export interface ThemeProviderProps extends PropsWithChildren {
|
|
|
37
47
|
* the default. Writing in `setTheme` and `setDensity` alone means mounting,
|
|
38
48
|
* however many times, can never lose what was chosen.
|
|
39
49
|
*/
|
|
40
|
-
export declare function ThemeProvider({ children, themes, defaultTheme, defaultDensity, storageKey, palettes, }: ThemeProviderProps): import("react").JSX.Element;
|
|
50
|
+
export declare function ThemeProvider({ children, themes, defaultTheme, defaultDensity, storageKey, palettes, initialTheme, initialDensity, }: ThemeProviderProps): import("react").JSX.Element;
|
|
51
|
+
/** The theme context, or `null` outside a {@link ThemeProvider}. */
|
|
52
|
+
export declare const useOptionalTheme: () => ThemeContextValue | null;
|
|
41
53
|
export declare function useTheme(): ThemeContextValue;
|
|
42
54
|
//# sourceMappingURL=theme-context.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"theme-context.d.ts","sourceRoot":"","sources":["../../src/theme/theme-context.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"theme-context.d.ts","sourceRoot":"","sources":["../../src/theme/theme-context.tsx"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,iBAAiB,EAMvB,MAAM,OAAO,CAAC;AAIf,OAAO,KAAK,EACV,OAAO,EACP,iBAAiB,EACjB,WAAW,EACX,YAAY,EACb,MAAM,YAAY,CAAC;AAIpB,MAAM,WAAW,kBAAmB,SAAQ,iBAAiB;IAC3D;uEACmE;IACnE,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,kEAAkE;IAClE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;CACzC;AAiCD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,EAC5B,QAAQ,EACR,MAAM,EACN,YAAY,EACZ,cAAc,EACd,UAAyB,EACzB,QAAQ,EACR,YAAY,EACZ,cAAc,GACf,EAAE,kBAAkB,+BAoHpB;AAED,oEAAoE;AACpE,eAAO,MAAM,gBAAgB,QAAO,iBAAiB,GAAG,IAC9B,CAAC;AAE3B,wBAAgB,QAAQ,IAAI,iBAAiB,CAI5C"}
|
|
@@ -1,22 +1,30 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { Effect } from 'effect';
|
|
3
4
|
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
|
5
|
+
import { useUiPreferencesState } from '../preferences/ui-preferences-context.js';
|
|
6
|
+
import { densityCookie, THEME_COOKIE } from './theme-cookie.js';
|
|
4
7
|
const ThemeContext = /*#__PURE__*/ createContext(null);
|
|
5
8
|
const DENSITIES = [
|
|
6
9
|
'comfortable',
|
|
7
10
|
'compact'
|
|
8
11
|
];
|
|
9
12
|
const isDensity = (value)=>DENSITIES.includes(value);
|
|
10
|
-
/** Storage that may be absent or refuse (a private window, blocked site data). */
|
|
13
|
+
/** Storage that may be absent or refuse (a private window, blocked site data). */ /** A year: a preference outlives a session. */ const COOKIE_MAX_AGE = 60 * 60 * 24 * 365;
|
|
14
|
+
/** A first-party cookie's value, or `null` when it is not set. */ function readCookie(name) {
|
|
11
15
|
try {
|
|
12
|
-
|
|
16
|
+
for (const pair of document.cookie.split('; ')){
|
|
17
|
+
const at = pair.indexOf('=');
|
|
18
|
+
if (pair.slice(0, at) === name) return decodeURIComponent(pair.slice(at + 1));
|
|
19
|
+
}
|
|
13
20
|
} catch (unused) {
|
|
14
|
-
|
|
21
|
+
// A sandboxed document refuses cookies; nothing was stored, then.
|
|
15
22
|
}
|
|
23
|
+
return null;
|
|
16
24
|
}
|
|
17
|
-
function
|
|
25
|
+
function writeCookie(name, value) {
|
|
18
26
|
try {
|
|
19
|
-
|
|
27
|
+
document.cookie = `${name}=${encodeURIComponent(value)}; path=/; max-age=${COOKIE_MAX_AGE}; samesite=lax`;
|
|
20
28
|
} catch (unused) {
|
|
21
29
|
// Nothing to remember it in; the choice still applies to this page.
|
|
22
30
|
}
|
|
@@ -34,31 +42,32 @@ function write(key, value) {
|
|
|
34
42
|
* overwrote the stored theme, and a reload in development always came back on
|
|
35
43
|
* the default. Writing in `setTheme` and `setDensity` alone means mounting,
|
|
36
44
|
* however many times, can never lose what was chosen.
|
|
37
|
-
*/ export function ThemeProvider({ children, themes, defaultTheme, defaultDensity, storageKey =
|
|
38
|
-
var _ref;
|
|
45
|
+
*/ export function ThemeProvider({ children, themes, defaultTheme, defaultDensity, storageKey = THEME_COOKIE, palettes, initialTheme, initialDensity }) {
|
|
46
|
+
var _ref, _ref1;
|
|
39
47
|
var _themes_;
|
|
40
|
-
const initial = (_ref =
|
|
48
|
+
const initial = (_ref = (_ref1 = initialTheme != null ? initialTheme : defaultTheme) != null ? _ref1 : (_themes_ = themes[0]) == null ? void 0 : _themes_.id) != null ? _ref : '';
|
|
41
49
|
const [theme, setThemeState] = useState(initial);
|
|
42
|
-
const densityKey =
|
|
50
|
+
const densityKey = densityCookie(storageKey);
|
|
43
51
|
// `undefined` until a density is stored, chosen or defaulted: the document's
|
|
44
52
|
// own `data-density` stands until then.
|
|
45
|
-
const [density, setDensityState] = useState(defaultDensity);
|
|
53
|
+
const [density, setDensityState] = useState(initialDensity != null ? initialDensity : defaultDensity);
|
|
54
|
+
const preferences = useUiPreferencesState();
|
|
46
55
|
const isKnown = useCallback((value)=>typeof value === 'string' && themes.some((t)=>t.id === value), [
|
|
47
56
|
themes
|
|
48
57
|
]);
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
// reads, so running twice changes nothing.
|
|
58
|
+
// Read the stored choice on mount, for a host that could not: a static
|
|
59
|
+
// export has no server to read the cookie, so it renders `initial` and the
|
|
60
|
+
// client corrects after mount. Reading in render would mismatch hydration.
|
|
61
|
+
// Where the server read it, `initial` already is the stored choice and this
|
|
62
|
+
// changes nothing. It only reads, so running twice changes nothing.
|
|
54
63
|
useEffect(()=>{
|
|
55
|
-
const stored =
|
|
64
|
+
const stored = readCookie(storageKey);
|
|
56
65
|
// eslint-disable-next-line react-hooks/set-state-in-effect -- one-shot SSR-safe hydration, see comment above
|
|
57
66
|
if (isKnown(stored)) setThemeState(stored);
|
|
58
67
|
// A stored density wins; otherwise the default, otherwise whatever the
|
|
59
68
|
// document was served with, so the context never reports a density the
|
|
60
69
|
// page is not in.
|
|
61
|
-
const storedDensity =
|
|
70
|
+
const storedDensity = readCookie(densityKey);
|
|
62
71
|
const served = document.documentElement.dataset.density;
|
|
63
72
|
if (isDensity(storedDensity)) setDensityState(storedDensity);
|
|
64
73
|
else if (defaultDensity === undefined && isDensity(served)) {
|
|
@@ -92,27 +101,43 @@ function write(key, value) {
|
|
|
92
101
|
]);
|
|
93
102
|
// Reflect the active theme and density onto the document. Storage is not
|
|
94
103
|
// touched here — see the ⚠️ above.
|
|
104
|
+
//
|
|
105
|
+
// Written only when it differs: a page the server rendered in the stored
|
|
106
|
+
// theme is already right, and even a write of the same value is a change
|
|
107
|
+
// something watching the document would see.
|
|
95
108
|
useEffect(()=>{
|
|
96
|
-
document.documentElement
|
|
109
|
+
const root = document.documentElement;
|
|
110
|
+
if (root.dataset.theme !== theme) root.dataset.theme = theme;
|
|
97
111
|
}, [
|
|
98
112
|
theme
|
|
99
113
|
]);
|
|
100
114
|
useEffect(()=>{
|
|
101
|
-
|
|
115
|
+
const root = document.documentElement;
|
|
116
|
+
if (density !== undefined && root.dataset.density !== density) root.dataset.density = density;
|
|
102
117
|
}, [
|
|
103
118
|
density
|
|
104
119
|
]);
|
|
120
|
+
// The cookie is what the next page is rendered in; the UI-preferences port
|
|
121
|
+
// gets a copy, so a host that keeps preferences server-side has them too.
|
|
122
|
+
const remember = useCallback((cookie, key, value)=>{
|
|
123
|
+
writeCookie(cookie, value);
|
|
124
|
+
void Effect.runPromise(preferences.write(key, value)).catch(()=>undefined);
|
|
125
|
+
}, [
|
|
126
|
+
preferences
|
|
127
|
+
]);
|
|
105
128
|
const setTheme = useCallback((next)=>{
|
|
106
129
|
setThemeState(next);
|
|
107
|
-
|
|
130
|
+
remember(storageKey, 'theme', next);
|
|
108
131
|
}, [
|
|
109
|
-
storageKey
|
|
132
|
+
storageKey,
|
|
133
|
+
remember
|
|
110
134
|
]);
|
|
111
135
|
const setDensity = useCallback((next)=>{
|
|
112
136
|
setDensityState(next);
|
|
113
|
-
|
|
137
|
+
remember(densityKey, 'density', next);
|
|
114
138
|
}, [
|
|
115
|
-
densityKey
|
|
139
|
+
densityKey,
|
|
140
|
+
remember
|
|
116
141
|
]);
|
|
117
142
|
const value = useMemo(()=>({
|
|
118
143
|
theme,
|
|
@@ -132,6 +157,7 @@ function write(key, value) {
|
|
|
132
157
|
children: children
|
|
133
158
|
});
|
|
134
159
|
}
|
|
160
|
+
/** The theme context, or `null` outside a {@link ThemeProvider}. */ export const useOptionalTheme = ()=>useContext(ThemeContext);
|
|
135
161
|
export function useTheme() {
|
|
136
162
|
const ctx = useContext(ThemeContext);
|
|
137
163
|
if (!ctx) throw new Error('useTheme must be used within a ThemeProvider');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/theme/theme-context.tsx"],"sourcesContent":["'use client';\n\nimport {\n createContext,\n type PropsWithChildren,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useState,\n} from 'react';\n\nimport type {\n Density,\n ThemeContextValue,\n ThemeOption,\n ThemePalette,\n} from './types.js';\n\nconst ThemeContext = createContext<ThemeContextValue | null>(null);\n\nexport interface ThemeProviderProps extends PropsWithChildren {\n /** The themes this app exposes. First entry is the default unless\n * `defaultTheme` is given. Drives `data-theme` + the switcher. */\n themes: ThemeOption[];\n /** Theme id used for the first paint / when nothing is stored. */\n defaultTheme?: string;\n /**\n * The density when nothing is stored. Omitted, the provider leaves\n * `data-density` as the document has it until a density is chosen.\n */\n defaultDensity?: Density;\n /**\n * localStorage key — namespace it per app so apps don't clobber each other.\n * The density is stored beside it, under `${storageKey}-density`.\n */\n storageKey?: string;\n /**\n * Runtime palettes for brands not shipped as CSS presets (multi-tenant /\n * white-label). Keyed by theme id; injected as `[data-theme='id']{…}` vars\n * appended after the app stylesheet, so they override static presets.\n */\n palettes?: Record<string, ThemePalette>;\n}\n\nconst DENSITIES: readonly Density[] = ['comfortable', 'compact'];\n\nconst isDensity = (value: unknown): value is Density =>\n DENSITIES.includes(value as Density);\n\n/** Storage that may be absent or refuse (a private window, blocked site data). */\nfunction read(key: string): string | null {\n try {\n return window.localStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\nfunction write(key: string, value: string) {\n try {\n window.localStorage.setItem(key, value);\n } catch {\n // Nothing to remember it in; the choice still applies to this page.\n }\n}\n\n/**\n * Applies the active palette and density by writing `data-theme` and\n * `data-density` on <html>, and remembers what the user chooses. Static palette\n * VALUES live in CSS (@entifix/style presets or an app-local themes.css); this\n * flips the selector. Dynamic palettes passed via `palettes` are injected at\n * runtime under the same `data-theme` mechanism.\n *\n * ⚠️ **Only a choice is written to storage, never a default.** The provider\n * used to persist whatever theme it held from an effect, and under StrictMode\n * the effect ran before the stored value had been read back: the default\n * overwrote the stored theme, and a reload in development always came back on\n * the default. Writing in `setTheme` and `setDensity` alone means mounting,\n * however many times, can never lose what was chosen.\n */\nexport function ThemeProvider({\n children,\n themes,\n defaultTheme,\n defaultDensity,\n storageKey = 'entifix-theme',\n palettes,\n}: ThemeProviderProps) {\n const initial = defaultTheme ?? themes[0]?.id ?? '';\n const [theme, setThemeState] = useState<string>(initial);\n const densityKey = `${storageKey}-density`;\n // `undefined` until a density is stored, chosen or defaulted: the document's\n // own `data-density` stands until then.\n const [density, setDensityState] = useState<Density | undefined>(\n defaultDensity,\n );\n\n const isKnown = useCallback(\n (value: unknown): value is string =>\n typeof value === 'string' && themes.some(t => t.id === value),\n [themes],\n );\n\n // Hydrate from a previously stored preference on mount. This intentionally\n // runs in an effect (not a lazy initializer): localStorage is unavailable\n // during SSR, so the server renders `initial` and the client corrects after\n // mount — reading it in render would cause a hydration mismatch. It only\n // reads, so running twice changes nothing.\n useEffect(() => {\n const stored = read(storageKey);\n // eslint-disable-next-line react-hooks/set-state-in-effect -- one-shot SSR-safe hydration, see comment above\n if (isKnown(stored)) setThemeState(stored);\n // A stored density wins; otherwise the default, otherwise whatever the\n // document was served with, so the context never reports a density the\n // page is not in.\n const storedDensity = read(densityKey);\n const served = document.documentElement.dataset.density;\n if (isDensity(storedDensity)) setDensityState(storedDensity);\n else if (defaultDensity === undefined && isDensity(served)) {\n setDensityState(served);\n }\n }, [storageKey, densityKey, isKnown, defaultDensity]);\n\n // Inject runtime palettes as a single <style> appended to <head>. Being last\n // in the cascade, these win over any static preset defining the same tokens.\n useEffect(() => {\n if (!palettes || Object.keys(palettes).length === 0) return;\n const styleId = `${storageKey}-runtime-palettes`;\n const css = Object.entries(palettes)\n .map(([id, palette]) => {\n const decls = Object.entries(palette)\n .map(([token, value]) => `--color-${token}: ${value};`)\n .join(' ');\n return `[data-theme='${id}'] { ${decls} }`;\n })\n .join('\\n');\n\n let el = document.getElementById(styleId) as HTMLStyleElement | null;\n if (!el) {\n el = document.createElement('style');\n el.id = styleId;\n document.head.appendChild(el);\n }\n el.textContent = css;\n }, [palettes, storageKey]);\n\n // Reflect the active theme and density onto the document. Storage is not\n // touched here — see the ⚠️ above.\n useEffect(() => {\n document.documentElement.dataset.theme = theme;\n }, [theme]);\n useEffect(() => {\n if (density !== undefined)\n document.documentElement.dataset.density = density;\n }, [density]);\n\n const setTheme = useCallback(\n (next: string) => {\n setThemeState(next);\n write(storageKey, next);\n },\n [storageKey],\n );\n const setDensity = useCallback(\n (next: Density) => {\n setDensityState(next);\n write(densityKey, next);\n },\n [densityKey],\n );\n\n const value = useMemo<ThemeContextValue>(\n () => ({\n theme,\n setTheme,\n themes,\n density: density ?? 'comfortable',\n setDensity,\n }),\n [theme, setTheme, themes, density, setDensity],\n );\n\n return (\n <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>\n );\n}\n\nexport function useTheme(): ThemeContextValue {\n const ctx = useContext(ThemeContext);\n if (!ctx) throw new Error('useTheme must be used within a ThemeProvider');\n return ctx;\n}\n"],"names":["createContext","useCallback","useContext","useEffect","useMemo","useState","ThemeContext","DENSITIES","isDensity","value","includes","read","key","window","localStorage","getItem","write","setItem","ThemeProvider","children","themes","defaultTheme","defaultDensity","storageKey","palettes","initial","id","theme","setThemeState","densityKey","density","setDensityState","isKnown","some","t","stored","storedDensity","served","document","documentElement","dataset","undefined","Object","keys","length","styleId","css","entries","map","palette","decls","token","join","el","getElementById","createElement","head","appendChild","textContent","setTheme","next","setDensity","Provider","useTheme","ctx","Error"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/theme/theme-context.tsx"],"sourcesContent":["'use client';\n\nimport { Effect } from 'effect';\nimport {\n createContext,\n type PropsWithChildren,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useState,\n} from 'react';\n\nimport { useUiPreferencesState } from '../preferences/ui-preferences-context.js';\nimport { densityCookie, THEME_COOKIE } from './theme-cookie.js';\nimport type {\n Density,\n ThemeContextValue,\n ThemeOption,\n ThemePalette,\n} from './types.js';\n\nconst ThemeContext = createContext<ThemeContextValue | null>(null);\n\nexport interface ThemeProviderProps extends PropsWithChildren {\n /** The themes this app exposes. First entry is the default unless\n * `defaultTheme` is given. Drives `data-theme` + the switcher. */\n themes: ThemeOption[];\n /** Theme id used for the first paint / when nothing is stored. */\n defaultTheme?: string;\n /**\n * The density when nothing is stored. Omitted, the provider leaves\n * `data-density` as the document has it until a density is chosen.\n */\n defaultDensity?: Density;\n /**\n * The cookie the theme is kept in — namespace it per app so apps don't\n * clobber each other. The density is kept beside it, under\n * `${storageKey}-density`. A cookie rather than local storage, so a server\n * can read the choice and render `data-theme` and `data-density` with the\n * page: no paint in the wrong palette before a script corrects it.\n */\n storageKey?: string;\n /**\n * The theme and density the server read from those cookies and rendered\n * the page in (`readThemeCookies` in `@entifix/next-shell/server`). Given,\n * the provider starts there and nothing changes after the page loads.\n */\n initialTheme?: string;\n initialDensity?: Density;\n /**\n * Runtime palettes for brands not shipped as CSS presets (multi-tenant /\n * white-label). Keyed by theme id; injected as `[data-theme='id']{…}` vars\n * appended after the app stylesheet, so they override static presets.\n */\n palettes?: Record<string, ThemePalette>;\n}\n\nconst DENSITIES: readonly Density[] = ['comfortable', 'compact'];\n\nconst isDensity = (value: unknown): value is Density =>\n DENSITIES.includes(value as Density);\n\n/** Storage that may be absent or refuse (a private window, blocked site data). */\n/** A year: a preference outlives a session. */\nconst COOKIE_MAX_AGE = 60 * 60 * 24 * 365;\n\n/** A first-party cookie's value, or `null` when it is not set. */\nfunction readCookie(name: string): string | null {\n try {\n for (const pair of document.cookie.split('; ')) {\n const at = pair.indexOf('=');\n if (pair.slice(0, at) === name)\n return decodeURIComponent(pair.slice(at + 1));\n }\n } catch {\n // A sandboxed document refuses cookies; nothing was stored, then.\n }\n return null;\n}\n\nfunction writeCookie(name: string, value: string) {\n try {\n document.cookie = `${name}=${encodeURIComponent(value)}; path=/; max-age=${COOKIE_MAX_AGE}; samesite=lax`;\n } catch {\n // Nothing to remember it in; the choice still applies to this page.\n }\n}\n\n/**\n * Applies the active palette and density by writing `data-theme` and\n * `data-density` on <html>, and remembers what the user chooses. Static palette\n * VALUES live in CSS (@entifix/style presets or an app-local themes.css); this\n * flips the selector. Dynamic palettes passed via `palettes` are injected at\n * runtime under the same `data-theme` mechanism.\n *\n * ⚠️ **Only a choice is written to storage, never a default.** The provider\n * used to persist whatever theme it held from an effect, and under StrictMode\n * the effect ran before the stored value had been read back: the default\n * overwrote the stored theme, and a reload in development always came back on\n * the default. Writing in `setTheme` and `setDensity` alone means mounting,\n * however many times, can never lose what was chosen.\n */\nexport function ThemeProvider({\n children,\n themes,\n defaultTheme,\n defaultDensity,\n storageKey = THEME_COOKIE,\n palettes,\n initialTheme,\n initialDensity,\n}: ThemeProviderProps) {\n const initial = initialTheme ?? defaultTheme ?? themes[0]?.id ?? '';\n const [theme, setThemeState] = useState<string>(initial);\n const densityKey = densityCookie(storageKey);\n // `undefined` until a density is stored, chosen or defaulted: the document's\n // own `data-density` stands until then.\n const [density, setDensityState] = useState<Density | undefined>(\n initialDensity ?? defaultDensity,\n );\n const preferences = useUiPreferencesState();\n\n const isKnown = useCallback(\n (value: unknown): value is string =>\n typeof value === 'string' && themes.some(t => t.id === value),\n [themes],\n );\n\n // Read the stored choice on mount, for a host that could not: a static\n // export has no server to read the cookie, so it renders `initial` and the\n // client corrects after mount. Reading in render would mismatch hydration.\n // Where the server read it, `initial` already is the stored choice and this\n // changes nothing. It only reads, so running twice changes nothing.\n useEffect(() => {\n const stored = readCookie(storageKey);\n // eslint-disable-next-line react-hooks/set-state-in-effect -- one-shot SSR-safe hydration, see comment above\n if (isKnown(stored)) setThemeState(stored);\n // A stored density wins; otherwise the default, otherwise whatever the\n // document was served with, so the context never reports a density the\n // page is not in.\n const storedDensity = readCookie(densityKey);\n const served = document.documentElement.dataset.density;\n if (isDensity(storedDensity)) setDensityState(storedDensity);\n else if (defaultDensity === undefined && isDensity(served)) {\n setDensityState(served);\n }\n }, [storageKey, densityKey, isKnown, defaultDensity]);\n\n // Inject runtime palettes as a single <style> appended to <head>. Being last\n // in the cascade, these win over any static preset defining the same tokens.\n useEffect(() => {\n if (!palettes || Object.keys(palettes).length === 0) return;\n const styleId = `${storageKey}-runtime-palettes`;\n const css = Object.entries(palettes)\n .map(([id, palette]) => {\n const decls = Object.entries(palette)\n .map(([token, value]) => `--color-${token}: ${value};`)\n .join(' ');\n return `[data-theme='${id}'] { ${decls} }`;\n })\n .join('\\n');\n\n let el = document.getElementById(styleId) as HTMLStyleElement | null;\n if (!el) {\n el = document.createElement('style');\n el.id = styleId;\n document.head.appendChild(el);\n }\n el.textContent = css;\n }, [palettes, storageKey]);\n\n // Reflect the active theme and density onto the document. Storage is not\n // touched here — see the ⚠️ above.\n //\n // Written only when it differs: a page the server rendered in the stored\n // theme is already right, and even a write of the same value is a change\n // something watching the document would see.\n useEffect(() => {\n const root = document.documentElement;\n if (root.dataset.theme !== theme) root.dataset.theme = theme;\n }, [theme]);\n useEffect(() => {\n const root = document.documentElement;\n if (density !== undefined && root.dataset.density !== density)\n root.dataset.density = density;\n }, [density]);\n\n // The cookie is what the next page is rendered in; the UI-preferences port\n // gets a copy, so a host that keeps preferences server-side has them too.\n const remember = useCallback(\n (cookie: string, key: 'theme' | 'density', value: string) => {\n writeCookie(cookie, value);\n void Effect.runPromise(preferences.write(key, value)).catch(\n () => undefined,\n );\n },\n [preferences],\n );\n const setTheme = useCallback(\n (next: string) => {\n setThemeState(next);\n remember(storageKey, 'theme', next);\n },\n [storageKey, remember],\n );\n const setDensity = useCallback(\n (next: Density) => {\n setDensityState(next);\n remember(densityKey, 'density', next);\n },\n [densityKey, remember],\n );\n\n const value = useMemo<ThemeContextValue>(\n () => ({\n theme,\n setTheme,\n themes,\n density: density ?? 'comfortable',\n setDensity,\n }),\n [theme, setTheme, themes, density, setDensity],\n );\n\n return (\n <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>\n );\n}\n\n/** The theme context, or `null` outside a {@link ThemeProvider}. */\nexport const useOptionalTheme = (): ThemeContextValue | null =>\n useContext(ThemeContext);\n\nexport function useTheme(): ThemeContextValue {\n const ctx = useContext(ThemeContext);\n if (!ctx) throw new Error('useTheme must be used within a ThemeProvider');\n return ctx;\n}\n"],"names":["Effect","createContext","useCallback","useContext","useEffect","useMemo","useState","useUiPreferencesState","densityCookie","THEME_COOKIE","ThemeContext","DENSITIES","isDensity","value","includes","COOKIE_MAX_AGE","readCookie","name","pair","document","cookie","split","at","indexOf","slice","decodeURIComponent","writeCookie","encodeURIComponent","ThemeProvider","children","themes","defaultTheme","defaultDensity","storageKey","palettes","initialTheme","initialDensity","initial","id","theme","setThemeState","densityKey","density","setDensityState","preferences","isKnown","some","t","stored","storedDensity","served","documentElement","dataset","undefined","Object","keys","length","styleId","css","entries","map","palette","decls","token","join","el","getElementById","createElement","head","appendChild","textContent","root","remember","key","runPromise","write","catch","setTheme","next","setDensity","Provider","useOptionalTheme","useTheme","ctx","Error"],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The cookie a theme is kept in unless a provider names another; the density
|
|
3
|
+
* is kept beside it, under `${name}-density`. Server-safe, so a layout and the
|
|
4
|
+
* inline script can name it without a client boundary.
|
|
5
|
+
*/
|
|
6
|
+
export declare const THEME_COOKIE = "entifix-theme";
|
|
7
|
+
/** The density's cookie, beside the theme's. */
|
|
8
|
+
export declare const densityCookie: (themeCookie: string) => string;
|
|
9
|
+
//# sourceMappingURL=theme-cookie.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"theme-cookie.d.ts","sourceRoot":"","sources":["../../src/theme/theme-cookie.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,eAAO,MAAM,YAAY,kBAAkB,CAAC;AAE5C,gDAAgD;AAChD,eAAO,MAAM,aAAa,GAAI,aAAa,MAAM,KAAG,MAC1B,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The cookie a theme is kept in unless a provider names another; the density
|
|
3
|
+
* is kept beside it, under `${name}-density`. Server-safe, so a layout and the
|
|
4
|
+
* inline script can name it without a client boundary.
|
|
5
|
+
*/ export const THEME_COOKIE = 'entifix-theme';
|
|
6
|
+
/** The density's cookie, beside the theme's. */ export const densityCookie = (themeCookie)=>`${themeCookie}-density`;
|
|
7
|
+
|
|
8
|
+
//# sourceMappingURL=theme-cookie.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/theme/theme-cookie.ts"],"sourcesContent":["/**\n * The cookie a theme is kept in unless a provider names another; the density\n * is kept beside it, under `${name}-density`. Server-safe, so a layout and the\n * inline script can name it without a client boundary.\n */\nexport const THEME_COOKIE = 'entifix-theme';\n\n/** The density's cookie, beside the theme's. */\nexport const densityCookie = (themeCookie: string): string =>\n `${themeCookie}-density`;\n"],"names":["THEME_COOKIE","densityCookie","themeCookie"],"mappings":"AAAA;;;;CAIC,GACD,OAAO,MAAMA,eAAe,gBAAgB;AAE5C,8CAA8C,GAC9C,OAAO,MAAMC,gBAAgB,CAACC,cAC5B,GAAGA,YAAY,QAAQ,CAAC,CAAC"}
|
|
@@ -4,7 +4,7 @@ export interface ThemeScriptProps {
|
|
|
4
4
|
themes: readonly ThemeOption[];
|
|
5
5
|
/** Painted when nothing valid is stored; the first theme otherwise. */
|
|
6
6
|
defaultTheme?: string;
|
|
7
|
-
/** The
|
|
7
|
+
/** The cookie `ThemeProvider` keeps the choice in. */
|
|
8
8
|
storageKey?: string;
|
|
9
9
|
/** For a Content-Security-Policy that allows inline scripts by nonce. */
|
|
10
10
|
nonce?: string;
|
|
@@ -15,11 +15,13 @@ export interface ThemeScriptProps {
|
|
|
15
15
|
* in <head>: it runs ahead of any stylesheet applying and long before React
|
|
16
16
|
* hydrates.
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
18
|
+
* For a host with no server to read the theme cookie — a static export. One
|
|
19
|
+
* that has a server renders the attributes from `readThemeCookies` instead,
|
|
20
|
+
* and needs no script at all.
|
|
21
|
+
*
|
|
22
|
+
* The stored choice wins; otherwise `defaultTheme`. A stored density is
|
|
23
|
+
* applied the same way; with none stored the document's own `data-density`
|
|
24
|
+
* stands. Nothing is written: only a choice is ever stored.
|
|
23
25
|
*
|
|
24
26
|
* Render it from a server component; it ships no client code.
|
|
25
27
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"theme-script.d.ts","sourceRoot":"","sources":["../../src/theme/theme-script.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"theme-script.d.ts","sourceRoot":"","sources":["../../src/theme/theme-script.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,WAAW,gBAAgB;IAC/B,kDAAkD;IAClD,MAAM,EAAE,SAAS,WAAW,EAAE,CAAC;IAC/B,uEAAuE;IACvE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sDAAsD;IACtD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yEAAyE;IACzE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,WAAW,CAAC,EAC1B,MAAM,EACN,YAAY,EACZ,UAAyB,EACzB,KAAK,GACN,EAAE,gBAAgB,+BA0BlB"}
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { THEME_COOKIE } from './theme-cookie.js';
|
|
2
3
|
/**
|
|
3
4
|
* Sets `data-theme` on <html> before the first paint, so a server-rendered or
|
|
4
5
|
* statically exported page never flashes the wrong palette. Render it inline
|
|
5
6
|
* in <head>: it runs ahead of any stylesheet applying and long before React
|
|
6
7
|
* hydrates.
|
|
7
8
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* For a host with no server to read the theme cookie — a static export. One
|
|
10
|
+
* that has a server renders the attributes from `readThemeCookies` instead,
|
|
11
|
+
* and needs no script at all.
|
|
12
|
+
*
|
|
13
|
+
* The stored choice wins; otherwise `defaultTheme`. A stored density is
|
|
14
|
+
* applied the same way; with none stored the document's own `data-density`
|
|
15
|
+
* stands. Nothing is written: only a choice is ever stored.
|
|
13
16
|
*
|
|
14
17
|
* Render it from a server component; it ships no client code.
|
|
15
|
-
*/ export function ThemeScript({ themes, defaultTheme, storageKey =
|
|
18
|
+
*/ export function ThemeScript({ themes, defaultTheme, storageKey = THEME_COOKIE, nonce }) {
|
|
16
19
|
var _ref;
|
|
17
20
|
const ids = themes.map((theme)=>theme.id);
|
|
18
21
|
const fallback = (_ref = defaultTheme != null ? defaultTheme : ids[0]) != null ? _ref : '';
|
|
@@ -20,13 +23,19 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
20
23
|
try {
|
|
21
24
|
var themes = ${JSON.stringify(ids)};
|
|
22
25
|
var key = ${JSON.stringify(storageKey)};
|
|
23
|
-
var
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
var read = function (name) {
|
|
27
|
+
var pairs = document.cookie.split('; ');
|
|
28
|
+
for (var i = 0; i < pairs.length; i++) {
|
|
29
|
+
var at = pairs[i].indexOf('=');
|
|
30
|
+
if (pairs[i].slice(0, at) === name)
|
|
31
|
+
return decodeURIComponent(pairs[i].slice(at + 1));
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
};
|
|
35
|
+
var theme = read(key);
|
|
36
|
+
if (themes.indexOf(theme) === -1) theme = ${JSON.stringify(fallback)};
|
|
28
37
|
document.documentElement.dataset.theme = theme;
|
|
29
|
-
var density =
|
|
38
|
+
var density = read(key + '-density');
|
|
30
39
|
if (density === 'comfortable' || density === 'compact') {
|
|
31
40
|
document.documentElement.dataset.density = density;
|
|
32
41
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/theme/theme-script.tsx"],"sourcesContent":["import type { ThemeOption } from './types.js';\n\nexport interface ThemeScriptProps {\n /** The same registry `ThemeProvider` is given. */\n themes: readonly ThemeOption[];\n /** Painted when nothing valid is stored; the first theme otherwise. */\n defaultTheme?: string;\n /** The
|
|
1
|
+
{"version":3,"sources":["../../src/theme/theme-script.tsx"],"sourcesContent":["import { THEME_COOKIE } from './theme-cookie.js';\nimport type { ThemeOption } from './types.js';\n\nexport interface ThemeScriptProps {\n /** The same registry `ThemeProvider` is given. */\n themes: readonly ThemeOption[];\n /** Painted when nothing valid is stored; the first theme otherwise. */\n defaultTheme?: string;\n /** The cookie `ThemeProvider` keeps the choice in. */\n storageKey?: string;\n /** For a Content-Security-Policy that allows inline scripts by nonce. */\n nonce?: string;\n}\n\n/**\n * Sets `data-theme` on <html> before the first paint, so a server-rendered or\n * statically exported page never flashes the wrong palette. Render it inline\n * in <head>: it runs ahead of any stylesheet applying and long before React\n * hydrates.\n *\n * For a host with no server to read the theme cookie — a static export. One\n * that has a server renders the attributes from `readThemeCookies` instead,\n * and needs no script at all.\n *\n * The stored choice wins; otherwise `defaultTheme`. A stored density is\n * applied the same way; with none stored the document's own `data-density`\n * stands. Nothing is written: only a choice is ever stored.\n *\n * Render it from a server component; it ships no client code.\n */\nexport function ThemeScript({\n themes,\n defaultTheme,\n storageKey = THEME_COOKIE,\n nonce,\n}: ThemeScriptProps) {\n const ids = themes.map(theme => theme.id);\n const fallback = defaultTheme ?? ids[0] ?? '';\n const script = `(function () {\n try {\n var themes = ${JSON.stringify(ids)};\n var key = ${JSON.stringify(storageKey)};\n var read = function (name) {\n var pairs = document.cookie.split('; ');\n for (var i = 0; i < pairs.length; i++) {\n var at = pairs[i].indexOf('=');\n if (pairs[i].slice(0, at) === name)\n return decodeURIComponent(pairs[i].slice(at + 1));\n }\n return null;\n };\n var theme = read(key);\n if (themes.indexOf(theme) === -1) theme = ${JSON.stringify(fallback)};\n document.documentElement.dataset.theme = theme;\n var density = read(key + '-density');\n if (density === 'comfortable' || density === 'compact') {\n document.documentElement.dataset.density = density;\n }\n } catch (error) {}\n})();`;\n return <script nonce={nonce} dangerouslySetInnerHTML={{ __html: script }} />;\n}\n"],"names":["THEME_COOKIE","ThemeScript","themes","defaultTheme","storageKey","nonce","ids","map","theme","id","fallback","script","JSON","stringify","dangerouslySetInnerHTML","__html"],"mappings":";AAAA,SAASA,YAAY,QAAQ,oBAAoB;AAcjD;;;;;;;;;;;;;;;CAeC,GACD,OAAO,SAASC,YAAY,EAC1BC,MAAM,EACNC,YAAY,EACZC,aAAaJ,YAAY,EACzBK,KAAK,EACY;QAEAF;IADjB,MAAMG,MAAMJ,OAAOK,GAAG,CAACC,CAAAA,QAASA,MAAMC,EAAE;IACxC,MAAMC,YAAWP,OAAAA,uBAAAA,eAAgBG,GAAG,CAAC,EAAE,YAAtBH,OAA0B;IAC3C,MAAMQ,SAAS,CAAC;;iBAED,EAAEC,KAAKC,SAAS,CAACP,KAAK;cACzB,EAAEM,KAAKC,SAAS,CAACT,YAAY;;;;;;;;;;;8CAWG,EAAEQ,KAAKC,SAAS,CAACH,UAAU;;;;;;;KAOpE,CAAC;IACJ,qBAAO,KAACC;QAAON,OAAOA;QAAOS,yBAAyB;YAAEC,QAAQJ;QAAO;;AACzE"}
|
|
@@ -18,7 +18,7 @@ export type ActionSurface = (typeof ACTION_SURFACES)[number];
|
|
|
18
18
|
* A constant rather than a literal in JSX, because `react/jsx-no-literals`
|
|
19
19
|
* fails the build on written-in copy and is right to: it cannot tell a
|
|
20
20
|
* typographic glyph from a forgotten Spanish string. The trigger's *name* is a
|
|
21
|
-
* catalog key (`
|
|
21
|
+
* catalog key (`actions.more`, `actions.forRecord`) — this is only the mark,
|
|
22
22
|
* and it is never read aloud.
|
|
23
23
|
*/
|
|
24
24
|
export declare const OVERFLOW_GLYPH = "\u22EF";
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
* A constant rather than a literal in JSX, because `react/jsx-no-literals`
|
|
44
44
|
* fails the build on written-in copy and is right to: it cannot tell a
|
|
45
45
|
* typographic glyph from a forgotten Spanish string. The trigger's *name* is a
|
|
46
|
-
* catalog key (`
|
|
46
|
+
* catalog key (`actions.more`, `actions.forRecord`) — this is only the mark,
|
|
47
47
|
* and it is never read aloud.
|
|
48
48
|
*/ export const OVERFLOW_GLYPH = '\u22ef';
|
|
49
49
|
/** Which surface renders one cell. Throws on a cell no surface owns. */ function surfaceForCell(key, binding, placement) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/ui/actions/action-surfaces.ts"],"sourcesContent":["import {\n type UseCaseBinding,\n type UseCaseDescriptor,\n type UseCasePlacement,\n} from '@entifix/core';\n\n/**\n * Where a declared verb is rendered.\n *\n * The use-case descriptor has two independent axes, and at first only one\n * surface, so four of the nine cells had nowhere to go: `EntityForm` filtered\n * `binding === 'entity' && placement !== 'context-dependent'` and everything\n * else was dropped without a word. This is the map that closes it —\n * **placement decides the surface, binding decides the payload** — and it lives\n * in one module so no surface re-derives it and the two axes cannot drift\n * apart again.\n */\nexport const ACTION_SURFACES = [\n 'form-header',\n 'form-footer',\n 'row-menu',\n 'bulk-bar',\n 'table-toolbar',\n 'command-palette',\n] as const;\nexport type ActionSurface = (typeof ACTION_SURFACES)[number];\n\n/**\n * The nine cells, exhaustively.\n *\n * `collection` + `determining` is deliberately absent, and asking for it\n * throws rather than returning `undefined`. A determining action *finalizes a\n * page* — it is the footer that ends an object page — and a list screen has no\n * such thing to finalize, which is Fiori's own rule. Dropping it silently is\n * the fault this repo already writes an assertion against elsewhere: the verb\n * would be declared, granted, exported, pass every check a register runs,\n * and simply never appear, which reads to its author as \"the permission is\n * wrong\" and sends them looking in the wrong place entirely.\n */\nconst SURFACE_BY_CELL: Record<string, ActionSurface> = {\n 'entity:context-independent': 'form-header',\n 'entity:determining': 'form-footer',\n 'entity:context-dependent': 'row-menu',\n 'collection:context-dependent': 'bulk-bar',\n 'collection:context-independent': 'table-toolbar',\n 'unbound:context-dependent': 'command-palette',\n 'unbound:context-independent': 'command-palette',\n 'unbound:determining': 'command-palette',\n};\n\n/**\n * The overflow trigger's glyph.\n *\n * A constant rather than a literal in JSX, because `react/jsx-no-literals`\n * fails the build on written-in copy and is right to: it cannot tell a\n * typographic glyph from a forgotten Spanish string. The trigger's *name* is a\n * catalog key (`
|
|
1
|
+
{"version":3,"sources":["../../../src/ui/actions/action-surfaces.ts"],"sourcesContent":["import {\n type UseCaseBinding,\n type UseCaseDescriptor,\n type UseCasePlacement,\n} from '@entifix/core';\n\n/**\n * Where a declared verb is rendered.\n *\n * The use-case descriptor has two independent axes, and at first only one\n * surface, so four of the nine cells had nowhere to go: `EntityForm` filtered\n * `binding === 'entity' && placement !== 'context-dependent'` and everything\n * else was dropped without a word. This is the map that closes it —\n * **placement decides the surface, binding decides the payload** — and it lives\n * in one module so no surface re-derives it and the two axes cannot drift\n * apart again.\n */\nexport const ACTION_SURFACES = [\n 'form-header',\n 'form-footer',\n 'row-menu',\n 'bulk-bar',\n 'table-toolbar',\n 'command-palette',\n] as const;\nexport type ActionSurface = (typeof ACTION_SURFACES)[number];\n\n/**\n * The nine cells, exhaustively.\n *\n * `collection` + `determining` is deliberately absent, and asking for it\n * throws rather than returning `undefined`. A determining action *finalizes a\n * page* — it is the footer that ends an object page — and a list screen has no\n * such thing to finalize, which is Fiori's own rule. Dropping it silently is\n * the fault this repo already writes an assertion against elsewhere: the verb\n * would be declared, granted, exported, pass every check a register runs,\n * and simply never appear, which reads to its author as \"the permission is\n * wrong\" and sends them looking in the wrong place entirely.\n */\nconst SURFACE_BY_CELL: Record<string, ActionSurface> = {\n 'entity:context-independent': 'form-header',\n 'entity:determining': 'form-footer',\n 'entity:context-dependent': 'row-menu',\n 'collection:context-dependent': 'bulk-bar',\n 'collection:context-independent': 'table-toolbar',\n 'unbound:context-dependent': 'command-palette',\n 'unbound:context-independent': 'command-palette',\n 'unbound:determining': 'command-palette',\n};\n\n/**\n * The overflow trigger's glyph.\n *\n * A constant rather than a literal in JSX, because `react/jsx-no-literals`\n * fails the build on written-in copy and is right to: it cannot tell a\n * typographic glyph from a forgotten Spanish string. The trigger's *name* is a\n * catalog key (`actions.more`, `actions.forRecord`) — this is only the mark,\n * and it is never read aloud.\n */\nexport const OVERFLOW_GLYPH = '\\u22ef';\n\n/** Which surface renders one cell. Throws on a cell no surface owns. */\nfunction surfaceForCell(\n key: string,\n binding: UseCaseBinding,\n placement: UseCasePlacement,\n): ActionSurface {\n const surface = SURFACE_BY_CELL[`${binding}:${placement}`];\n\n if (surface === undefined) {\n throw new Error(\n `The use case \"${key}\" is declared ${binding} + ${placement}, which no surface renders. ` +\n 'A `determining` action finalizes a page — it is an object page’s footer — and a list ' +\n 'screen has no page to finalize, so a collection-bound verb cannot be determining. ' +\n 'Declare it `context-dependent` to reach the bulk bar, or `context-independent` to sit ' +\n 'in the table toolbar.',\n );\n }\n\n return surface;\n}\n\n/**\n * Every surface this descriptor renders on.\n *\n * ⚠️ **A verb may name more than one cell**, and that is what makes publishing\n * reachable from a form header, a row menu and a selection without becoming\n * three permissions. The primary `binding` + `placement` come first, then\n * whatever `alsoAt` adds, and each is validated the same way — an illegal cell\n * in `alsoAt` throws exactly as one in the primary pair does.\n *\n * Duplicates are collapsed. A descriptor that named the same cell twice would\n * otherwise render the verb twice in one menu, which reads as a bug in the\n * screen rather than in the declaration.\n */\nexport function surfacesFor(\n descriptor: UseCaseDescriptor,\n): readonly ActionSurface[] {\n const cells = [\n { binding: descriptor.binding, placement: descriptor.placement },\n ...(descriptor.alsoAt ?? []),\n ];\n\n return [\n ...new Set(\n cells.map(cell =>\n surfaceForCell(descriptor.key, cell.binding, cell.placement),\n ),\n ),\n ];\n}\n\n/**\n * The single surface this descriptor renders on.\n *\n * Kept for the callers that genuinely have one — the command palette asks \"is\n * this mine?\" of an `unbound` verb, which can only ever be in one place. Any\n * caller that renders a *list* of verbs wants {@link surfacesFor}.\n */\nexport function surfaceFor(descriptor: UseCaseDescriptor): ActionSurface {\n return surfaceForCell(\n descriptor.key,\n descriptor.binding,\n descriptor.placement,\n );\n}\n\n/**\n * The descriptors one surface should render, in declaration order.\n *\n * Every descriptor is passed through {@link surfacesFor}, so an unrenderable\n * cell throws on the **first render of any surface** rather than only on the\n * one that would have shown it — the same reason\n * `assertLinkSourcesAreEditable` runs as one pass over the registry instead of\n * per row: a check that only fires on the screen that was already looking for\n * the verb is a check that fires after the bug is reported. A cell declared in\n * `alsoAt` is validated on that same first render, not on the surface it names.\n */\nexport function useCasesForSurface(\n surface: ActionSurface,\n useCases: readonly UseCaseDescriptor[] | undefined,\n): UseCaseDescriptor[] {\n return (useCases ?? []).filter(descriptor =>\n surfacesFor(descriptor).includes(surface),\n );\n}\n"],"names":["ACTION_SURFACES","SURFACE_BY_CELL","OVERFLOW_GLYPH","surfaceForCell","key","binding","placement","surface","undefined","Error","surfacesFor","descriptor","cells","alsoAt","Set","map","cell","surfaceFor","useCasesForSurface","useCases","filter","includes"],"mappings":"AAMA;;;;;;;;;;CAUC,GACD,OAAO,MAAMA,kBAAkB;IAC7B;IACA;IACA;IACA;IACA;IACA;CACD,CAAU;AAGX;;;;;;;;;;;CAWC,GACD,MAAMC,kBAAiD;IACrD,8BAA8B;IAC9B,sBAAsB;IACtB,4BAA4B;IAC5B,gCAAgC;IAChC,kCAAkC;IAClC,6BAA6B;IAC7B,+BAA+B;IAC/B,uBAAuB;AACzB;AAEA;;;;;;;;CAQC,GACD,OAAO,MAAMC,iBAAiB,SAAS;AAEvC,sEAAsE,GACtE,SAASC,eACPC,GAAW,EACXC,OAAuB,EACvBC,SAA2B;IAE3B,MAAMC,UAAUN,eAAe,CAAC,GAAGI,QAAQ,CAAC,EAAEC,WAAW,CAAC;IAE1D,IAAIC,YAAYC,WAAW;QACzB,MAAM,IAAIC,MACR,CAAC,cAAc,EAAEL,IAAI,cAAc,EAAEC,QAAQ,GAAG,EAAEC,UAAU,4BAA4B,CAAC,GACvF,0FACA,uFACA,2FACA;IAEN;IAEA,OAAOC;AACT;AAEA;;;;;;;;;;;;CAYC,GACD,OAAO,SAASG,YACdC,UAA6B;QAIvBA;IAFN,MAAMC,QAAQ;QACZ;YAAEP,SAASM,WAAWN,OAAO;YAAEC,WAAWK,WAAWL,SAAS;QAAC;YAC3DK,qBAAAA,WAAWE,MAAM,YAAjBF,qBAAqB,EAAE;KAC5B;IAED,OAAO;WACF,IAAIG,IACLF,MAAMG,GAAG,CAACC,CAAAA,OACRb,eAAeQ,WAAWP,GAAG,EAAEY,KAAKX,OAAO,EAAEW,KAAKV,SAAS;KAGhE;AACH;AAEA;;;;;;CAMC,GACD,OAAO,SAASW,WAAWN,UAA6B;IACtD,OAAOR,eACLQ,WAAWP,GAAG,EACdO,WAAWN,OAAO,EAClBM,WAAWL,SAAS;AAExB;AAEA;;;;;;;;;;CAUC,GACD,OAAO,SAASY,mBACdX,OAAsB,EACtBY,QAAkD;IAElD,OAAO,CAACA,mBAAAA,WAAY,EAAE,EAAEC,MAAM,CAACT,CAAAA,aAC7BD,YAAYC,YAAYU,QAAQ,CAACd;AAErC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
/** What a host can do about a failure, by the failure's `code`. */
|
|
3
|
+
export type ErrorActions = Readonly<Record<string, () => void>>;
|
|
4
|
+
/**
|
|
5
|
+
* Registers the host's next step for each failure code it can resolve —
|
|
6
|
+
* "Choose organization" for `noActiveOrganization` — so a region or a toast
|
|
7
|
+
* showing that failure can offer it ([ADR 0028](../../../../../../docs/adr/0028-states-an-organism-must-render.md)).
|
|
8
|
+
*/
|
|
9
|
+
export declare function ErrorActionsProvider({ actions, children, }: {
|
|
10
|
+
actions: ErrorActions;
|
|
11
|
+
children: ReactNode;
|
|
12
|
+
}): import("react").JSX.Element;
|
|
13
|
+
/** A failure as the organisms hold it. */
|
|
14
|
+
interface CodedFailure {
|
|
15
|
+
code?: unknown;
|
|
16
|
+
details?: Record<string, unknown>;
|
|
17
|
+
}
|
|
18
|
+
/** The action a failure offers: its label, and what it does. */
|
|
19
|
+
export interface ErrorAction {
|
|
20
|
+
readonly label: string;
|
|
21
|
+
readonly onAction: () => void;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The next step for a failure, or `undefined`. Offered only when **both**
|
|
25
|
+
* exist: a handler the host registered for the failure's code, and the
|
|
26
|
+
* catalog's `errors:<code>Action` to name it — the code stays the catalog key,
|
|
27
|
+
* so a button never shows a key, and copy without a handler never shows a
|
|
28
|
+
* button that does nothing.
|
|
29
|
+
*/
|
|
30
|
+
export declare function useErrorAction(): (failure: CodedFailure | undefined) => ErrorAction | undefined;
|
|
31
|
+
export {};
|
|
32
|
+
//# sourceMappingURL=error-actions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-actions.d.ts","sourceRoot":"","sources":["../../../src/ui/actions/error-actions.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAiB,KAAK,SAAS,EAAc,MAAM,OAAO,CAAC;AAIlE,mEAAmE;AACnE,MAAM,MAAM,YAAY,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC,CAAC,CAAC;AAIhE;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,OAAO,EACP,QAAQ,GACT,EAAE;IACD,OAAO,EAAE,YAAY,CAAC;IACtB,QAAQ,EAAE,SAAS,CAAC;CACrB,+BAMA;AAED,0CAA0C;AAC1C,UAAU,YAAY;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,gEAAgE;AAChE,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAC;CAC/B;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,IAAI,CAChC,OAAO,EAAE,YAAY,GAAG,SAAS,KAC9B,WAAW,GAAG,SAAS,CAe3B"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, useContext } from 'react';
|
|
4
|
+
import { useTranslateKey } from '../../i18n/index.js';
|
|
5
|
+
const ErrorActionsContext = /*#__PURE__*/ createContext({});
|
|
6
|
+
/**
|
|
7
|
+
* Registers the host's next step for each failure code it can resolve —
|
|
8
|
+
* "Choose organization" for `noActiveOrganization` — so a region or a toast
|
|
9
|
+
* showing that failure can offer it ([ADR 0028](../../../../../../docs/adr/0028-states-an-organism-must-render.md)).
|
|
10
|
+
*/ export function ErrorActionsProvider({ actions, children }) {
|
|
11
|
+
return /*#__PURE__*/ _jsx(ErrorActionsContext.Provider, {
|
|
12
|
+
value: actions,
|
|
13
|
+
children: children
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The next step for a failure, or `undefined`. Offered only when **both**
|
|
18
|
+
* exist: a handler the host registered for the failure's code, and the
|
|
19
|
+
* catalog's `errors:<code>Action` to name it — the code stays the catalog key,
|
|
20
|
+
* so a button never shows a key, and copy without a handler never shows a
|
|
21
|
+
* button that does nothing.
|
|
22
|
+
*/ export function useErrorAction() {
|
|
23
|
+
const actions = useContext(ErrorActionsContext);
|
|
24
|
+
const translateKey = useTranslateKey();
|
|
25
|
+
return (failure)=>{
|
|
26
|
+
var _failure_details;
|
|
27
|
+
const code = typeof (failure == null ? void 0 : failure.code) === 'string' ? failure.code : failure == null ? void 0 : (_failure_details = failure.details) == null ? void 0 : _failure_details['code'];
|
|
28
|
+
if (typeof code !== 'string') return undefined;
|
|
29
|
+
const onAction = actions[code];
|
|
30
|
+
if (onAction === undefined) return undefined;
|
|
31
|
+
// A missing key comes back as itself, without its namespace.
|
|
32
|
+
const label = translateKey(`errors:${code}Action`);
|
|
33
|
+
return label === `${code}Action` ? undefined : {
|
|
34
|
+
label,
|
|
35
|
+
onAction
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
//# sourceMappingURL=error-actions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/ui/actions/error-actions.tsx"],"sourcesContent":["'use client';\n\nimport { createContext, type ReactNode, useContext } from 'react';\n\nimport { useTranslateKey } from '../../i18n/index.js';\n\n/** What a host can do about a failure, by the failure's `code`. */\nexport type ErrorActions = Readonly<Record<string, () => void>>;\n\nconst ErrorActionsContext = createContext<ErrorActions>({});\n\n/**\n * Registers the host's next step for each failure code it can resolve —\n * \"Choose organization\" for `noActiveOrganization` — so a region or a toast\n * showing that failure can offer it ([ADR 0028](../../../../../../docs/adr/0028-states-an-organism-must-render.md)).\n */\nexport function ErrorActionsProvider({\n actions,\n children,\n}: {\n actions: ErrorActions;\n children: ReactNode;\n}) {\n return (\n <ErrorActionsContext.Provider value={actions}>\n {children}\n </ErrorActionsContext.Provider>\n );\n}\n\n/** A failure as the organisms hold it. */\ninterface CodedFailure {\n code?: unknown;\n details?: Record<string, unknown>;\n}\n\n/** The action a failure offers: its label, and what it does. */\nexport interface ErrorAction {\n readonly label: string;\n readonly onAction: () => void;\n}\n\n/**\n * The next step for a failure, or `undefined`. Offered only when **both**\n * exist: a handler the host registered for the failure's code, and the\n * catalog's `errors:<code>Action` to name it — the code stays the catalog key,\n * so a button never shows a key, and copy without a handler never shows a\n * button that does nothing.\n */\nexport function useErrorAction(): (\n failure: CodedFailure | undefined,\n) => ErrorAction | undefined {\n const actions = useContext(ErrorActionsContext);\n const translateKey = useTranslateKey();\n return failure => {\n const code =\n typeof failure?.code === 'string'\n ? failure.code\n : failure?.details?.['code'];\n if (typeof code !== 'string') return undefined;\n const onAction = actions[code];\n if (onAction === undefined) return undefined;\n // A missing key comes back as itself, without its namespace.\n const label = translateKey(`errors:${code}Action`);\n return label === `${code}Action` ? undefined : { label, onAction };\n };\n}\n"],"names":["createContext","useContext","useTranslateKey","ErrorActionsContext","ErrorActionsProvider","actions","children","Provider","value","useErrorAction","translateKey","failure","code","details","undefined","onAction","label"],"mappings":"AAAA;;AAEA,SAASA,aAAa,EAAkBC,UAAU,QAAQ,QAAQ;AAElE,SAASC,eAAe,QAAQ,sBAAsB;AAKtD,MAAMC,oCAAsBH,cAA4B,CAAC;AAEzD;;;;CAIC,GACD,OAAO,SAASI,qBAAqB,EACnCC,OAAO,EACPC,QAAQ,EAIT;IACC,qBACE,KAACH,oBAAoBI,QAAQ;QAACC,OAAOH;kBAClCC;;AAGP;AAcA;;;;;;CAMC,GACD,OAAO,SAASG;IAGd,MAAMJ,UAAUJ,WAAWE;IAC3B,MAAMO,eAAeR;IACrB,OAAOS,CAAAA;YAICA;QAHN,MAAMC,OACJ,QAAOD,2BAAAA,QAASC,IAAI,MAAK,WACrBD,QAAQC,IAAI,GACZD,4BAAAA,mBAAAA,QAASE,OAAO,qBAAhBF,gBAAkB,CAAC,OAAO;QAChC,IAAI,OAAOC,SAAS,UAAU,OAAOE;QACrC,MAAMC,WAAWV,OAAO,CAACO,KAAK;QAC9B,IAAIG,aAAaD,WAAW,OAAOA;QACnC,6DAA6D;QAC7D,MAAME,QAAQN,aAAa,CAAC,OAAO,EAAEE,KAAK,MAAM,CAAC;QACjD,OAAOI,UAAU,GAAGJ,KAAK,MAAM,CAAC,GAAGE,YAAY;YAAEE;YAAOD;QAAS;IACnE;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui/actions/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui/actions/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,uBAAuB,CAAC"}
|
package/dist/ui/actions/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/ui/actions/index.ts"],"sourcesContent":["export * from './action-surfaces.js';\nexport * from './render-link.js';\n"],"names":[],"mappings":"AAAA,cAAc,uBAAuB;AACrC,cAAc,mBAAmB"}
|
|
1
|
+
{"version":3,"sources":["../../../src/ui/actions/index.ts"],"sourcesContent":["export * from './action-surfaces.js';\nexport * from './error-actions.js';\nexport * from './render-link.js';\nexport * from './shortcut.js';\nexport * from './use-verb-invoker.js';\n"],"names":[],"mappings":"AAAA,cAAc,uBAAuB;AACrC,cAAc,qBAAqB;AACnC,cAAc,mBAAmB;AACjC,cAAc,gBAAgB;AAC9B,cAAc,wBAAwB"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A declared chord as `aria-keyshortcuts` writes it: `Mod` is ⌘ on macOS and
|
|
3
|
+
* Ctrl elsewhere, and the attribute cannot know which machine it is read on, so
|
|
4
|
+
* it names both — `Mod+S` is `Control+S Meta+S`. A chord with no `Mod` is
|
|
5
|
+
* written once.
|
|
6
|
+
*/
|
|
7
|
+
export declare function ariaKeyShortcuts(chord: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* A declared chord as a person reads it on their own machine: `⌘S` on a Mac,
|
|
10
|
+
* `Ctrl+S` elsewhere — what the command palette shows beside an action.
|
|
11
|
+
*/
|
|
12
|
+
export declare function shortcutLabel(chord: string, mac?: boolean): string;
|
|
13
|
+
//# sourceMappingURL=shortcut.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shortcut.d.ts","sourceRoot":"","sources":["../../../src/ui/actions/shortcut.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQtD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,EACb,GAAG,UAA6C,GAC/C,MAAM,CAKR"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A declared chord as `aria-keyshortcuts` writes it: `Mod` is ⌘ on macOS and
|
|
3
|
+
* Ctrl elsewhere, and the attribute cannot know which machine it is read on, so
|
|
4
|
+
* it names both — `Mod+S` is `Control+S Meta+S`. A chord with no `Mod` is
|
|
5
|
+
* written once.
|
|
6
|
+
*/ export function ariaKeyShortcuts(chord) {
|
|
7
|
+
if (!chord.split('+').includes('Mod')) return chord;
|
|
8
|
+
const as = (modifier)=>chord.split('+').map((part)=>part === 'Mod' ? modifier : part).join('+');
|
|
9
|
+
return `${as('Control')} ${as('Meta')}`;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A declared chord as a person reads it on their own machine: `⌘S` on a Mac,
|
|
13
|
+
* `Ctrl+S` elsewhere — what the command palette shows beside an action.
|
|
14
|
+
*/ export function shortcutLabel(chord, mac = /Mac|iPhone|iPad/.test(navigator.platform)) {
|
|
15
|
+
return chord.split('+').map((part)=>part === 'Mod' ? mac ? '⌘' : 'Ctrl' : part).join(mac ? '' : '+');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
//# sourceMappingURL=shortcut.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/ui/actions/shortcut.ts"],"sourcesContent":["/**\n * A declared chord as `aria-keyshortcuts` writes it: `Mod` is ⌘ on macOS and\n * Ctrl elsewhere, and the attribute cannot know which machine it is read on, so\n * it names both — `Mod+S` is `Control+S Meta+S`. A chord with no `Mod` is\n * written once.\n */\nexport function ariaKeyShortcuts(chord: string): string {\n if (!chord.split('+').includes('Mod')) return chord;\n const as = (modifier: string) =>\n chord\n .split('+')\n .map(part => (part === 'Mod' ? modifier : part))\n .join('+');\n return `${as('Control')} ${as('Meta')}`;\n}\n\n/**\n * A declared chord as a person reads it on their own machine: `⌘S` on a Mac,\n * `Ctrl+S` elsewhere — what the command palette shows beside an action.\n */\nexport function shortcutLabel(\n chord: string,\n mac = /Mac|iPhone|iPad/.test(navigator.platform),\n): string {\n return chord\n .split('+')\n .map(part => (part === 'Mod' ? (mac ? '⌘' : 'Ctrl') : part))\n .join(mac ? '' : '+');\n}\n"],"names":["ariaKeyShortcuts","chord","split","includes","as","modifier","map","part","join","shortcutLabel","mac","test","navigator","platform"],"mappings":"AAAA;;;;;CAKC,GACD,OAAO,SAASA,iBAAiBC,KAAa;IAC5C,IAAI,CAACA,MAAMC,KAAK,CAAC,KAAKC,QAAQ,CAAC,QAAQ,OAAOF;IAC9C,MAAMG,KAAK,CAACC,WACVJ,MACGC,KAAK,CAAC,KACNI,GAAG,CAACC,CAAAA,OAASA,SAAS,QAAQF,WAAWE,MACzCC,IAAI,CAAC;IACV,OAAO,GAAGJ,GAAG,WAAW,CAAC,EAAEA,GAAG,SAAS;AACzC;AAEA;;;CAGC,GACD,OAAO,SAASK,cACdR,KAAa,EACbS,MAAM,kBAAkBC,IAAI,CAACC,UAAUC,QAAQ,CAAC;IAEhD,OAAOZ,MACJC,KAAK,CAAC,KACNI,GAAG,CAACC,CAAAA,OAASA,SAAS,QAASG,MAAM,MAAM,SAAUH,MACrDC,IAAI,CAACE,MAAM,KAAK;AACrB"}
|