@adea-ai/ui 0.124.0 → 0.127.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/NOTICE +58 -0
- package/dist/components/theme/index.d.ts +1 -1
- package/dist/components/theme/index.d.ts.map +1 -1
- package/dist/components/theme/index.js +4 -3
- package/dist/components/theme/theme-document.d.ts +31 -1
- package/dist/components/theme/theme-document.d.ts.map +1 -1
- package/dist/components/theme/theme-document.js +25 -5
- package/dist/components/theme/theme-document.js.map +1 -1
- package/dist/components/theme/theme-persistence.d.ts +87 -0
- package/dist/components/theme/theme-persistence.d.ts.map +1 -1
- package/dist/components/theme/theme-persistence.js +65 -3
- package/dist/components/theme/theme-persistence.js.map +1 -1
- package/dist/components/theme/theme-provider.d.ts +49 -40
- package/dist/components/theme/theme-provider.d.ts.map +1 -1
- package/dist/components/theme/theme-provider.js +67 -52
- package/dist/components/theme/theme-provider.js.map +1 -1
- package/dist/components/theme/theme-script.d.ts +100 -0
- package/dist/components/theme/theme-script.d.ts.map +1 -0
- package/dist/components/theme/theme-script.js +57 -0
- package/dist/components/theme/theme-script.js.map +1 -0
- package/dist/components/theme/theme-state.d.ts +85 -2
- package/dist/components/theme/theme-state.d.ts.map +1 -1
- package/dist/components/theme/theme-state.js +41 -2
- package/dist/components/theme/theme-state.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -4
- package/dist/lib/accent-roles.d.ts +124 -0
- package/dist/lib/accent-roles.d.ts.map +1 -0
- package/dist/lib/accent-roles.js +163 -0
- package/dist/lib/accent-roles.js.map +1 -0
- package/dist/lib/font-zoom.d.ts +25 -0
- package/dist/lib/font-zoom.d.ts.map +1 -0
- package/dist/lib/font-zoom.js +45 -0
- package/dist/lib/font-zoom.js.map +1 -0
- package/dist/lib/preference-store.d.ts +126 -0
- package/dist/lib/preference-store.d.ts.map +1 -0
- package/dist/lib/preference-store.js +126 -0
- package/dist/lib/preference-store.js.map +1 -0
- package/dist/lib/theme-surface.d.ts +71 -0
- package/dist/lib/theme-surface.d.ts.map +1 -0
- package/dist/lib/theme-surface.js +62 -0
- package/dist/lib/theme-surface.js.map +1 -0
- package/dist/lib/themes.d.ts +32 -1
- package/dist/lib/themes.d.ts.map +1 -1
- package/dist/lib/themes.js +34 -13
- package/dist/lib/themes.js.map +1 -1
- package/dist/lib/tokens.d.ts.map +1 -1
- package/dist/lib/tokens.js +15 -0
- package/dist/lib/tokens.js.map +1 -1
- package/dist/r/lib.json +20 -0
- package/dist/r/registry.json +25 -0
- package/dist/r/src/components/theme/index.ts +6 -0
- package/dist/r/src/components/theme/theme-document.ts +69 -4
- package/dist/r/src/components/theme/theme-persistence.ts +172 -2
- package/dist/r/src/components/theme/theme-provider.tsx +137 -51
- package/dist/r/src/components/theme/theme-script.ts +201 -0
- package/dist/r/src/components/theme/theme-state.ts +124 -3
- package/dist/r/src/lib/accent-roles.ts +338 -0
- package/dist/r/src/lib/font-zoom.ts +74 -0
- package/dist/r/src/lib/preference-store.ts +216 -0
- package/dist/r/src/lib/theme-surface.ts +110 -0
- package/dist/r/src/lib/themes.ts +58 -19
- package/dist/r/src/lib/tokens.ts +17 -0
- package/dist/r/src/styles/theme.css +38 -0
- package/dist/r/theming.json +5 -0
- package/package.json +1 -1
- package/registry.json +25 -0
- package/src/components/theme/index.ts +6 -0
- package/src/components/theme/theme-document.ts +69 -4
- package/src/components/theme/theme-persistence.ts +172 -2
- package/src/components/theme/theme-provider.tsx +137 -51
- package/src/components/theme/theme-script.ts +201 -0
- package/src/components/theme/theme-state.ts +124 -3
- package/src/index.ts +34 -0
- package/src/lib/accent-roles.ts +338 -0
- package/src/lib/font-zoom.ts +74 -0
- package/src/lib/preference-store.ts +216 -0
- package/src/lib/theme-surface.ts +110 -0
- package/src/lib/themes.ts +58 -19
- package/src/lib/tokens.ts +17 -0
- package/src/styles/theme.css +38 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { normalizeAppearanceEditorFontSettings } from "./appearance-font-settings.js";
|
|
2
|
+
//#region src/lib/font-zoom.ts
|
|
3
|
+
/**
|
|
4
|
+
* Classify the platform zoom chord: Cmd (macOS) or Ctrl (other platforms)
|
|
5
|
+
* with `=`/`+` zooming in, `-` zooming out, and `0` resetting to the System
|
|
6
|
+
* defaults. Unmodified keys, Alt-modified chords, and IME composition never
|
|
7
|
+
* count, so typing and text editing are untouched.
|
|
8
|
+
*/
|
|
9
|
+
function fontZoomShortcut(event) {
|
|
10
|
+
if (event.isComposing || event.altKey) return void 0;
|
|
11
|
+
if (!(event.metaKey || event.ctrlKey)) return void 0;
|
|
12
|
+
if (event.key === "=" || event.key === "+") return "in";
|
|
13
|
+
if (event.key === "-") return "out";
|
|
14
|
+
if (event.key === "0") return "reset";
|
|
15
|
+
}
|
|
16
|
+
/** Clamp a pixel size to the shared range the appearance editor enforces. */
|
|
17
|
+
function clampFontSize(size) {
|
|
18
|
+
return Math.min(32, Math.max(10, size));
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Step every text tier by `delta` pixels, clamped to that range. The axes move
|
|
22
|
+
* together — that is what makes the chord read as zoom — and each keeps its
|
|
23
|
+
* own family choice.
|
|
24
|
+
*/
|
|
25
|
+
function stepAppearanceFontSizes(fonts, delta) {
|
|
26
|
+
const current = normalizeAppearanceEditorFontSettings(fonts).settings;
|
|
27
|
+
return {
|
|
28
|
+
ui: {
|
|
29
|
+
...current.ui,
|
|
30
|
+
size: clampFontSize(current.ui.size + delta)
|
|
31
|
+
},
|
|
32
|
+
content: {
|
|
33
|
+
...current.content,
|
|
34
|
+
size: clampFontSize(current.content.size + delta)
|
|
35
|
+
},
|
|
36
|
+
code: {
|
|
37
|
+
...current.code,
|
|
38
|
+
size: clampFontSize(current.code.size + delta)
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
//#endregion
|
|
43
|
+
export { fontZoomShortcut, stepAppearanceFontSizes };
|
|
44
|
+
|
|
45
|
+
//# sourceMappingURL=font-zoom.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"font-zoom.js","names":[],"sources":["../../src/lib/font-zoom.ts"],"sourcesContent":["/*\n * Copyright (c) 2026 Wing\n * Licensed under the MIT License.\n *\n * The platform zoom chords, mapped onto Adea's three text roles.\n *\n * macOS (and Windows/Linux) zoom shortcuts — Cmd/Ctrl with `=`, `+`, `-`, or\n * `0` — normally scale the browser's page zoom. In Adea every text element is\n * already tied to one of the three appearance font tiers (the UI tier drives\n * the whole `text-*` scale through `--font-ui-scale`; content and code roles\n * scale through their own tokens), so stepping those tiers reproduces zoom\n * while keeping the app inside its tokenized, persisted type system: the page\n * layout, menus, sheets, and terminals reflow live from the same projection\n * the appearance editor writes.\n *\n * Translated from Adea's accepted port (`packages/ui/src/components/font-zoom.ts`) so\n * the shared provider and Adea run one implementation. See NOTICE.\n */\nimport {\n APPEARANCE_EDITOR_FONT_AXES,\n APPEARANCE_EDITOR_FONT_SIZE_MAX,\n APPEARANCE_EDITOR_FONT_SIZE_MIN,\n normalizeAppearanceEditorFontSettings,\n type AppearanceEditorFontSettings,\n} from '#lib/appearance-font-settings'\n\n/** What the user's zoom chord asked for. */\nexport type FontZoomIntent = 'in' | 'out' | 'reset'\n\n/**\n * Classify the platform zoom chord: Cmd (macOS) or Ctrl (other platforms)\n * with `=`/`+` zooming in, `-` zooming out, and `0` resetting to the System\n * defaults. Unmodified keys, Alt-modified chords, and IME composition never\n * count, so typing and text editing are untouched.\n */\nexport function fontZoomShortcut(event: {\n key: string\n metaKey: boolean\n ctrlKey: boolean\n altKey: boolean\n isComposing?: boolean\n}): FontZoomIntent | undefined {\n if (event.isComposing || event.altKey) return undefined\n if (!(event.metaKey || event.ctrlKey)) return undefined\n if (event.key === '=' || event.key === '+') return 'in'\n if (event.key === '-') return 'out'\n if (event.key === '0') return 'reset'\n return undefined\n}\n\n/** Clamp a pixel size to the shared range the appearance editor enforces. */\nfunction clampFontSize(size: number): number {\n return Math.min(APPEARANCE_EDITOR_FONT_SIZE_MAX, Math.max(APPEARANCE_EDITOR_FONT_SIZE_MIN, size))\n}\n\n/**\n * Step every text tier by `delta` pixels, clamped to that range. The axes move\n * together — that is what makes the chord read as zoom — and each keeps its\n * own family choice.\n */\nexport function stepAppearanceFontSizes(\n fonts: unknown,\n delta: 1 | -1\n): AppearanceEditorFontSettings {\n const current = normalizeAppearanceEditorFontSettings(fonts).settings\n return {\n ui: { ...current.ui, size: clampFontSize(current.ui.size + delta) },\n content: { ...current.content, size: clampFontSize(current.content.size + delta) },\n code: { ...current.code, size: clampFontSize(current.code.size + delta) },\n }\n}\n\n/** Every axis name, for callers that want to report or test the stepping. */\nexport const FONT_ZOOM_AXES = APPEARANCE_EDITOR_FONT_AXES\n"],"mappings":";;;;;;;;AAmCA,SAAgB,iBAAiB,OAMF;CAC7B,IAAI,MAAM,eAAe,MAAM,QAAQ,OAAO,KAAA;CAC9C,IAAI,EAAE,MAAM,WAAW,MAAM,UAAU,OAAO,KAAA;CAC9C,IAAI,MAAM,QAAQ,OAAO,MAAM,QAAQ,KAAK,OAAO;CACnD,IAAI,MAAM,QAAQ,KAAK,OAAO;CAC9B,IAAI,MAAM,QAAQ,KAAK,OAAO;AAEhC;;AAGA,SAAS,cAAc,MAAsB;CAC3C,OAAO,KAAK,IAAA,IAAqC,KAAK,IAAA,IAAqC,IAAI,CAAC;AAClG;;;;;;AAOA,SAAgB,wBACd,OACA,OAC8B;CAC9B,MAAM,UAAU,sCAAsC,KAAK,CAAC,CAAC;CAC7D,OAAO;EACL,IAAI;GAAE,GAAG,QAAQ;GAAI,MAAM,cAAc,QAAQ,GAAG,OAAO,KAAK;EAAE;EAClE,SAAS;GAAE,GAAG,QAAQ;GAAS,MAAM,cAAc,QAAQ,QAAQ,OAAO,KAAK;EAAE;EACjF,MAAM;GAAE,GAAG,QAAQ;GAAM,MAAM,cAAc,QAAQ,KAAK,OAAO,KAAK;EAAE;CAC1E;AACF"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A versioned preference store: read one JSON document, refuse to guess about what
|
|
3
|
+
* it cannot read, and keep what it refused.
|
|
4
|
+
*
|
|
5
|
+
* Nothing here knows what a preference *is*. A host hands the engine a key, a
|
|
6
|
+
* version and (through `createVersionedPersistence`) a codec; the engine owns only
|
|
7
|
+
* the part every versioned store gets wrong the same way, which is what happens to
|
|
8
|
+
* a document that is not the one the code expects:
|
|
9
|
+
*
|
|
10
|
+
* - A document that is not JSON, or not a record, or carries a version the code
|
|
11
|
+
* does not know (usually a newer build's) is **quarantined, byte for byte, into a
|
|
12
|
+
* recovery key** before anything else happens. The store then answers as if
|
|
13
|
+
* nothing were stored. Because the quarantine runs at read time, ahead of any
|
|
14
|
+
* write, saving valid preferences later cannot destroy the unread original.
|
|
15
|
+
* - A legacy key is consulted only when the main key is *absent*, never when it is
|
|
16
|
+
* unreadable: a corrupt main document is not "no preference", and falling back to
|
|
17
|
+
* an older, staler key would quietly restore a choice the user has since changed.
|
|
18
|
+
* The legacy key is never deleted.
|
|
19
|
+
* - Blocked storage (a sandboxed iframe, a privacy mode) throws on the mere access
|
|
20
|
+
* of the `localStorage` global. Every path here treats that as "nothing stored".
|
|
21
|
+
*
|
|
22
|
+
* The reads and writes are against a {@link PreferenceStorage}, so the rules run in a
|
|
23
|
+
* test without a DOM.
|
|
24
|
+
*/
|
|
25
|
+
/** The part of `Storage` the engine uses. */
|
|
26
|
+
export type PreferenceStorage = Pick<Storage, 'getItem' | 'setItem'>;
|
|
27
|
+
/** Why an unread document was quarantined. */
|
|
28
|
+
export type RecoveryReason = 'corrupt_json' | 'unsupported_record';
|
|
29
|
+
/**
|
|
30
|
+
* What is written to the recovery key.
|
|
31
|
+
*
|
|
32
|
+
* The shape is a compatibility surface: Adea's stored envelopes already have it, and
|
|
33
|
+
* a tool that inspects a user's storage reads it. Do not rename a field.
|
|
34
|
+
*/
|
|
35
|
+
export type RecoveryEnvelope = Readonly<{
|
|
36
|
+
schemaVersion: 1;
|
|
37
|
+
reason: RecoveryReason;
|
|
38
|
+
/** An ISO-8601 timestamp of the capture; refreshed by each read of the same bytes. */
|
|
39
|
+
capturedAt: string;
|
|
40
|
+
/** The document exactly as it was stored. */
|
|
41
|
+
raw: string;
|
|
42
|
+
}>;
|
|
43
|
+
/**
|
|
44
|
+
* The preference store, or nothing.
|
|
45
|
+
*
|
|
46
|
+
* The `typeof` guard sits inside the `try` for a reason: where storage is blocked,
|
|
47
|
+
* merely *reading* the `localStorage` global throws, so a guard in front of the `try`
|
|
48
|
+
* is itself the crash.
|
|
49
|
+
*/
|
|
50
|
+
export declare function defaultPreferenceStorage(): PreferenceStorage | undefined;
|
|
51
|
+
/** The envelope for a document, stamped with `now`. */
|
|
52
|
+
export declare function recoveryEnvelope(raw: string, reason: RecoveryReason, now?: Date): RecoveryEnvelope;
|
|
53
|
+
/**
|
|
54
|
+
* Quarantine an unread document into `recoveryKey`.
|
|
55
|
+
*
|
|
56
|
+
* Idempotent, so re-reading the same unread value refreshes the capture without
|
|
57
|
+
* losing it. Best effort: a store that cannot be written leaves the caller to fail
|
|
58
|
+
* closed on the defaults, which is the same outcome as not having a recovery key at
|
|
59
|
+
* all. Never throws.
|
|
60
|
+
*
|
|
61
|
+
* Exported so a host quarantines its *other* documents (a theme library, say) with
|
|
62
|
+
* the same envelope. Give each document its own `recoveryKey`: two documents that
|
|
63
|
+
* share one overwrite each other's capture.
|
|
64
|
+
*/
|
|
65
|
+
export declare function quarantineRecord(storage: PreferenceStorage, recoveryKey: string, raw: string, reason: RecoveryReason, now?: () => Date): void;
|
|
66
|
+
/** Where a versioned document lives and which version the code understands. */
|
|
67
|
+
export type VersionedRecordSchema = {
|
|
68
|
+
/** The storage key of the document. */
|
|
69
|
+
key: string;
|
|
70
|
+
/**
|
|
71
|
+
* The only `version` the code reads. Absent, the document is unversioned and any
|
|
72
|
+
* JSON object is accepted; present, a record whose top-level `version` is not
|
|
73
|
+
* exactly this number is unsupported (newer or older), not "close enough".
|
|
74
|
+
*/
|
|
75
|
+
version?: number;
|
|
76
|
+
/** Where an unread document is kept. Default `${key}.recovery`. */
|
|
77
|
+
recoveryKey?: string;
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* What reading the document found.
|
|
81
|
+
*
|
|
82
|
+
* - `absent`: nothing is stored under the key.
|
|
83
|
+
* - `record`: a JSON object this code can read; `raw` is the stored text.
|
|
84
|
+
* - `quarantined`: something was stored and cannot be read. It has already been
|
|
85
|
+
* written to the recovery key.
|
|
86
|
+
* - `unavailable`: the store itself could not be read.
|
|
87
|
+
*/
|
|
88
|
+
export type VersionedRead = {
|
|
89
|
+
status: 'absent';
|
|
90
|
+
} | {
|
|
91
|
+
status: 'record';
|
|
92
|
+
record: Readonly<Record<string, unknown>>;
|
|
93
|
+
raw: string;
|
|
94
|
+
} | {
|
|
95
|
+
status: 'quarantined';
|
|
96
|
+
reason: RecoveryReason;
|
|
97
|
+
raw: string;
|
|
98
|
+
} | {
|
|
99
|
+
status: 'unavailable';
|
|
100
|
+
};
|
|
101
|
+
/** The default recovery key for a document key. */
|
|
102
|
+
export declare function recoveryKeyFor(schema: VersionedRecordSchema): string;
|
|
103
|
+
/**
|
|
104
|
+
* Read the document under `schema.key`.
|
|
105
|
+
*
|
|
106
|
+
* The classification is the contract: a value that is not valid JSON, or is JSON but
|
|
107
|
+
* not an object (`null`, a number, a string), is `corrupt_json`; an object (or array)
|
|
108
|
+
* that is not a record of the supported version is `unsupported_record`. Both are
|
|
109
|
+
* quarantined before this returns. An empty string is a stored value, so it is
|
|
110
|
+
* corrupt rather than absent.
|
|
111
|
+
*/
|
|
112
|
+
export declare function readVersionedRecord(storage: PreferenceStorage, schema: VersionedRecordSchema, now?: () => Date): VersionedRead;
|
|
113
|
+
/**
|
|
114
|
+
* The legacy single-key value, or `null`.
|
|
115
|
+
*
|
|
116
|
+
* Only the caller decides whether to look: the engine's rule is to ask when the main
|
|
117
|
+
* key is `absent`, not when it was quarantined.
|
|
118
|
+
*/
|
|
119
|
+
export declare function readLegacyValue(storage: PreferenceStorage, legacyKey: string): string | null;
|
|
120
|
+
/**
|
|
121
|
+
* Write `document` to `key` as JSON. Returns whether it was stored; a full or blocked
|
|
122
|
+
* store returns `false` and never throws, because losing a preference must not break
|
|
123
|
+
* the switch that tried to save it.
|
|
124
|
+
*/
|
|
125
|
+
export declare function writeVersionedRecord(storage: PreferenceStorage, key: string, document: unknown): boolean;
|
|
126
|
+
//# sourceMappingURL=preference-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preference-store.d.ts","sourceRoot":"","sources":["../../src/lib/preference-store.ts"],"names":[],"mappings":"AAWA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,6CAA6C;AAC7C,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAAC,OAAO,EAAE,SAAS,GAAG,SAAS,CAAC,CAAA;AAEpE,8CAA8C;AAC9C,MAAM,MAAM,cAAc,GAAG,cAAc,GAAG,oBAAoB,CAAA;AAElE;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,QAAQ,CAAC;IACtC,aAAa,EAAE,CAAC,CAAA;IAChB,MAAM,EAAE,cAAc,CAAA;IACtB,sFAAsF;IACtF,UAAU,EAAE,MAAM,CAAA;IAClB,6CAA6C;IAC7C,GAAG,EAAE,MAAM,CAAA;CACZ,CAAC,CAAA;AAEF;;;;;;GAMG;AACH,wBAAgB,wBAAwB,IAAI,iBAAiB,GAAG,SAAS,CAMxE;AAED,uDAAuD;AACvD,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,cAAc,EACtB,GAAG,GAAE,IAAiB,GACrB,gBAAgB,CAElB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,iBAAiB,EAC1B,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,cAAc,EACtB,GAAG,GAAE,MAAM,IAAuB,GACjC,IAAI,CAMN;AAED,+EAA+E;AAC/E,MAAM,MAAM,qBAAqB,GAAG;IAClC,uCAAuC;IACvC,GAAG,EAAE,MAAM,CAAA;IACX;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,mEAAmE;IACnE,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,CAAA;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,aAAa,GACrB;IAAE,MAAM,EAAE,QAAQ,CAAA;CAAE,GACpB;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAC5E;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,cAAc,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAC9D;IAAE,MAAM,EAAE,aAAa,CAAA;CAAE,CAAA;AAE7B,mDAAmD;AACnD,wBAAgB,cAAc,CAAC,MAAM,EAAE,qBAAqB,GAAG,MAAM,CAEpE;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,iBAAiB,EAC1B,MAAM,EAAE,qBAAqB,EAC7B,GAAG,CAAC,EAAE,MAAM,IAAI,GACf,aAAa,CA6Bf;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM5F;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,iBAAiB,EAC1B,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,OAAO,GAChB,OAAO,CAOT"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
//#region src/lib/preference-store.ts
|
|
2
|
+
/**
|
|
3
|
+
* The preference store, or nothing.
|
|
4
|
+
*
|
|
5
|
+
* The `typeof` guard sits inside the `try` for a reason: where storage is blocked,
|
|
6
|
+
* merely *reading* the `localStorage` global throws, so a guard in front of the `try`
|
|
7
|
+
* is itself the crash.
|
|
8
|
+
*/
|
|
9
|
+
function defaultPreferenceStorage() {
|
|
10
|
+
try {
|
|
11
|
+
return typeof localStorage === "undefined" ? void 0 : localStorage;
|
|
12
|
+
} catch {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/** The envelope for a document, stamped with `now`. */
|
|
17
|
+
function recoveryEnvelope(raw, reason, now = /* @__PURE__ */ new Date()) {
|
|
18
|
+
return {
|
|
19
|
+
schemaVersion: 1,
|
|
20
|
+
reason,
|
|
21
|
+
capturedAt: now.toISOString(),
|
|
22
|
+
raw
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Quarantine an unread document into `recoveryKey`.
|
|
27
|
+
*
|
|
28
|
+
* Idempotent, so re-reading the same unread value refreshes the capture without
|
|
29
|
+
* losing it. Best effort: a store that cannot be written leaves the caller to fail
|
|
30
|
+
* closed on the defaults, which is the same outcome as not having a recovery key at
|
|
31
|
+
* all. Never throws.
|
|
32
|
+
*
|
|
33
|
+
* Exported so a host quarantines its *other* documents (a theme library, say) with
|
|
34
|
+
* the same envelope. Give each document its own `recoveryKey`: two documents that
|
|
35
|
+
* share one overwrite each other's capture.
|
|
36
|
+
*/
|
|
37
|
+
function quarantineRecord(storage, recoveryKey, raw, reason, now = () => /* @__PURE__ */ new Date()) {
|
|
38
|
+
try {
|
|
39
|
+
storage.setItem(recoveryKey, JSON.stringify(recoveryEnvelope(raw, reason, now())));
|
|
40
|
+
} catch {}
|
|
41
|
+
}
|
|
42
|
+
/** The default recovery key for a document key. */
|
|
43
|
+
function recoveryKeyFor(schema) {
|
|
44
|
+
return schema.recoveryKey ?? `${schema.key}.recovery`;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Read the document under `schema.key`.
|
|
48
|
+
*
|
|
49
|
+
* The classification is the contract: a value that is not valid JSON, or is JSON but
|
|
50
|
+
* not an object (`null`, a number, a string), is `corrupt_json`; an object (or array)
|
|
51
|
+
* that is not a record of the supported version is `unsupported_record`. Both are
|
|
52
|
+
* quarantined before this returns. An empty string is a stored value, so it is
|
|
53
|
+
* corrupt rather than absent.
|
|
54
|
+
*/
|
|
55
|
+
function readVersionedRecord(storage, schema, now) {
|
|
56
|
+
let raw;
|
|
57
|
+
try {
|
|
58
|
+
raw = storage.getItem(schema.key);
|
|
59
|
+
} catch {
|
|
60
|
+
return { status: "unavailable" };
|
|
61
|
+
}
|
|
62
|
+
if (raw === null) return { status: "absent" };
|
|
63
|
+
const recoveryKey = recoveryKeyFor(schema);
|
|
64
|
+
let parsed;
|
|
65
|
+
try {
|
|
66
|
+
parsed = JSON.parse(raw);
|
|
67
|
+
} catch {
|
|
68
|
+
quarantineRecord(storage, recoveryKey, raw, "corrupt_json", now);
|
|
69
|
+
return {
|
|
70
|
+
status: "quarantined",
|
|
71
|
+
reason: "corrupt_json",
|
|
72
|
+
raw
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
76
|
+
quarantineRecord(storage, recoveryKey, raw, "corrupt_json", now);
|
|
77
|
+
return {
|
|
78
|
+
status: "quarantined",
|
|
79
|
+
reason: "corrupt_json",
|
|
80
|
+
raw
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
if (Array.isArray(parsed) || schema.version !== void 0 && parsed.version !== schema.version) {
|
|
84
|
+
quarantineRecord(storage, recoveryKey, raw, "unsupported_record", now);
|
|
85
|
+
return {
|
|
86
|
+
status: "quarantined",
|
|
87
|
+
reason: "unsupported_record",
|
|
88
|
+
raw
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
status: "record",
|
|
93
|
+
record: parsed,
|
|
94
|
+
raw
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* The legacy single-key value, or `null`.
|
|
99
|
+
*
|
|
100
|
+
* Only the caller decides whether to look: the engine's rule is to ask when the main
|
|
101
|
+
* key is `absent`, not when it was quarantined.
|
|
102
|
+
*/
|
|
103
|
+
function readLegacyValue(storage, legacyKey) {
|
|
104
|
+
try {
|
|
105
|
+
return storage.getItem(legacyKey);
|
|
106
|
+
} catch {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Write `document` to `key` as JSON. Returns whether it was stored; a full or blocked
|
|
112
|
+
* store returns `false` and never throws, because losing a preference must not break
|
|
113
|
+
* the switch that tried to save it.
|
|
114
|
+
*/
|
|
115
|
+
function writeVersionedRecord(storage, key, document) {
|
|
116
|
+
try {
|
|
117
|
+
storage.setItem(key, JSON.stringify(document));
|
|
118
|
+
return true;
|
|
119
|
+
} catch {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
//#endregion
|
|
124
|
+
export { defaultPreferenceStorage, quarantineRecord, readLegacyValue, readVersionedRecord, recoveryEnvelope, recoveryKeyFor, writeVersionedRecord };
|
|
125
|
+
|
|
126
|
+
//# sourceMappingURL=preference-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preference-store.js","names":[],"sources":["../../src/lib/preference-store.ts"],"sourcesContent":["/*\n * Copyright (c) 2026 Wing\n * Licensed under the MIT License.\n *\n * The recovery behaviour here (quarantine before any write, the envelope shape, the\n * legacy key that is read once and never deleted) is Adea's accepted port of Zeron's\n * preference store (https://github.com/zeronsh/zeron),\n * `packages/ui/src/components/appearance.ts`, moved here so the shared provider and\n * Adea run one implementation. See NOTICE.\n */\n\n/**\n * A versioned preference store: read one JSON document, refuse to guess about what\n * it cannot read, and keep what it refused.\n *\n * Nothing here knows what a preference *is*. A host hands the engine a key, a\n * version and (through `createVersionedPersistence`) a codec; the engine owns only\n * the part every versioned store gets wrong the same way, which is what happens to\n * a document that is not the one the code expects:\n *\n * - A document that is not JSON, or not a record, or carries a version the code\n * does not know (usually a newer build's) is **quarantined, byte for byte, into a\n * recovery key** before anything else happens. The store then answers as if\n * nothing were stored. Because the quarantine runs at read time, ahead of any\n * write, saving valid preferences later cannot destroy the unread original.\n * - A legacy key is consulted only when the main key is *absent*, never when it is\n * unreadable: a corrupt main document is not \"no preference\", and falling back to\n * an older, staler key would quietly restore a choice the user has since changed.\n * The legacy key is never deleted.\n * - Blocked storage (a sandboxed iframe, a privacy mode) throws on the mere access\n * of the `localStorage` global. Every path here treats that as \"nothing stored\".\n *\n * The reads and writes are against a {@link PreferenceStorage}, so the rules run in a\n * test without a DOM.\n */\n\n/** The part of `Storage` the engine uses. */\nexport type PreferenceStorage = Pick<Storage, 'getItem' | 'setItem'>\n\n/** Why an unread document was quarantined. */\nexport type RecoveryReason = 'corrupt_json' | 'unsupported_record'\n\n/**\n * What is written to the recovery key.\n *\n * The shape is a compatibility surface: Adea's stored envelopes already have it, and\n * a tool that inspects a user's storage reads it. Do not rename a field.\n */\nexport type RecoveryEnvelope = Readonly<{\n schemaVersion: 1\n reason: RecoveryReason\n /** An ISO-8601 timestamp of the capture; refreshed by each read of the same bytes. */\n capturedAt: string\n /** The document exactly as it was stored. */\n raw: string\n}>\n\n/**\n * The preference store, or nothing.\n *\n * The `typeof` guard sits inside the `try` for a reason: where storage is blocked,\n * merely *reading* the `localStorage` global throws, so a guard in front of the `try`\n * is itself the crash.\n */\nexport function defaultPreferenceStorage(): PreferenceStorage | undefined {\n try {\n return typeof localStorage === 'undefined' ? undefined : localStorage\n } catch {\n return undefined\n }\n}\n\n/** The envelope for a document, stamped with `now`. */\nexport function recoveryEnvelope(\n raw: string,\n reason: RecoveryReason,\n now: Date = new Date()\n): RecoveryEnvelope {\n return { schemaVersion: 1, reason, capturedAt: now.toISOString(), raw }\n}\n\n/**\n * Quarantine an unread document into `recoveryKey`.\n *\n * Idempotent, so re-reading the same unread value refreshes the capture without\n * losing it. Best effort: a store that cannot be written leaves the caller to fail\n * closed on the defaults, which is the same outcome as not having a recovery key at\n * all. Never throws.\n *\n * Exported so a host quarantines its *other* documents (a theme library, say) with\n * the same envelope. Give each document its own `recoveryKey`: two documents that\n * share one overwrite each other's capture.\n */\nexport function quarantineRecord(\n storage: PreferenceStorage,\n recoveryKey: string,\n raw: string,\n reason: RecoveryReason,\n now: () => Date = () => new Date()\n): void {\n try {\n storage.setItem(recoveryKey, JSON.stringify(recoveryEnvelope(raw, reason, now())))\n } catch {\n // Quarantine is best effort; the active preference still fails closed.\n }\n}\n\n/** Where a versioned document lives and which version the code understands. */\nexport type VersionedRecordSchema = {\n /** The storage key of the document. */\n key: string\n /**\n * The only `version` the code reads. Absent, the document is unversioned and any\n * JSON object is accepted; present, a record whose top-level `version` is not\n * exactly this number is unsupported (newer or older), not \"close enough\".\n */\n version?: number\n /** Where an unread document is kept. Default `${key}.recovery`. */\n recoveryKey?: string\n}\n\n/**\n * What reading the document found.\n *\n * - `absent`: nothing is stored under the key.\n * - `record`: a JSON object this code can read; `raw` is the stored text.\n * - `quarantined`: something was stored and cannot be read. It has already been\n * written to the recovery key.\n * - `unavailable`: the store itself could not be read.\n */\nexport type VersionedRead =\n | { status: 'absent' }\n | { status: 'record'; record: Readonly<Record<string, unknown>>; raw: string }\n | { status: 'quarantined'; reason: RecoveryReason; raw: string }\n | { status: 'unavailable' }\n\n/** The default recovery key for a document key. */\nexport function recoveryKeyFor(schema: VersionedRecordSchema): string {\n return schema.recoveryKey ?? `${schema.key}.recovery`\n}\n\n/**\n * Read the document under `schema.key`.\n *\n * The classification is the contract: a value that is not valid JSON, or is JSON but\n * not an object (`null`, a number, a string), is `corrupt_json`; an object (or array)\n * that is not a record of the supported version is `unsupported_record`. Both are\n * quarantined before this returns. An empty string is a stored value, so it is\n * corrupt rather than absent.\n */\nexport function readVersionedRecord(\n storage: PreferenceStorage,\n schema: VersionedRecordSchema,\n now?: () => Date\n): VersionedRead {\n let raw: string | null\n try {\n raw = storage.getItem(schema.key)\n } catch {\n return { status: 'unavailable' }\n }\n if (raw === null) return { status: 'absent' }\n\n const recoveryKey = recoveryKeyFor(schema)\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch {\n quarantineRecord(storage, recoveryKey, raw, 'corrupt_json', now)\n return { status: 'quarantined', reason: 'corrupt_json', raw }\n }\n if (typeof parsed !== 'object' || parsed === null) {\n quarantineRecord(storage, recoveryKey, raw, 'corrupt_json', now)\n return { status: 'quarantined', reason: 'corrupt_json', raw }\n }\n if (\n Array.isArray(parsed) ||\n (schema.version !== undefined && (parsed as { version?: unknown }).version !== schema.version)\n ) {\n quarantineRecord(storage, recoveryKey, raw, 'unsupported_record', now)\n return { status: 'quarantined', reason: 'unsupported_record', raw }\n }\n return { status: 'record', record: parsed as Record<string, unknown>, raw }\n}\n\n/**\n * The legacy single-key value, or `null`.\n *\n * Only the caller decides whether to look: the engine's rule is to ask when the main\n * key is `absent`, not when it was quarantined.\n */\nexport function readLegacyValue(storage: PreferenceStorage, legacyKey: string): string | null {\n try {\n return storage.getItem(legacyKey)\n } catch {\n return null\n }\n}\n\n/**\n * Write `document` to `key` as JSON. Returns whether it was stored; a full or blocked\n * store returns `false` and never throws, because losing a preference must not break\n * the switch that tried to save it.\n */\nexport function writeVersionedRecord(\n storage: PreferenceStorage,\n key: string,\n document: unknown\n): boolean {\n try {\n storage.setItem(key, JSON.stringify(document))\n return true\n } catch {\n return false\n }\n}\n"],"mappings":";;;;;;;;AAgEA,SAAgB,2BAA0D;CACxE,IAAI;EACF,OAAO,OAAO,iBAAiB,cAAc,KAAA,IAAY;CAC3D,QAAQ;EACN;CACF;AACF;;AAGA,SAAgB,iBACd,KACA,QACA,sBAAY,IAAI,KAAK,GACH;CAClB,OAAO;EAAE,eAAe;EAAG;EAAQ,YAAY,IAAI,YAAY;EAAG;CAAI;AACxE;;;;;;;;;;;;;AAcA,SAAgB,iBACd,SACA,aACA,KACA,QACA,4BAAwB,IAAI,KAAK,GAC3B;CACN,IAAI;EACF,QAAQ,QAAQ,aAAa,KAAK,UAAU,iBAAiB,KAAK,QAAQ,IAAI,CAAC,CAAC,CAAC;CACnF,QAAQ,CAER;AACF;;AAgCA,SAAgB,eAAe,QAAuC;CACpE,OAAO,OAAO,eAAe,GAAG,OAAO,IAAI;AAC7C;;;;;;;;;;AAWA,SAAgB,oBACd,SACA,QACA,KACe;CACf,IAAI;CACJ,IAAI;EACF,MAAM,QAAQ,QAAQ,OAAO,GAAG;CAClC,QAAQ;EACN,OAAO,EAAE,QAAQ,cAAc;CACjC;CACA,IAAI,QAAQ,MAAM,OAAO,EAAE,QAAQ,SAAS;CAE5C,MAAM,cAAc,eAAe,MAAM;CACzC,IAAI;CACJ,IAAI;EACF,SAAS,KAAK,MAAM,GAAG;CACzB,QAAQ;EACN,iBAAiB,SAAS,aAAa,KAAK,gBAAgB,GAAG;EAC/D,OAAO;GAAE,QAAQ;GAAe,QAAQ;GAAgB;EAAI;CAC9D;CACA,IAAI,OAAO,WAAW,YAAY,WAAW,MAAM;EACjD,iBAAiB,SAAS,aAAa,KAAK,gBAAgB,GAAG;EAC/D,OAAO;GAAE,QAAQ;GAAe,QAAQ;GAAgB;EAAI;CAC9D;CACA,IACE,MAAM,QAAQ,MAAM,KACnB,OAAO,YAAY,KAAA,KAAc,OAAiC,YAAY,OAAO,SACtF;EACA,iBAAiB,SAAS,aAAa,KAAK,sBAAsB,GAAG;EACrE,OAAO;GAAE,QAAQ;GAAe,QAAQ;GAAsB;EAAI;CACpE;CACA,OAAO;EAAE,QAAQ;EAAU,QAAQ;EAAmC;CAAI;AAC5E;;;;;;;AAQA,SAAgB,gBAAgB,SAA4B,WAAkC;CAC5F,IAAI;EACF,OAAO,QAAQ,QAAQ,SAAS;CAClC,QAAQ;EACN,OAAO;CACT;AACF;;;;;;AAOA,SAAgB,qBACd,SACA,KACA,UACS;CACT,IAAI;EACF,QAAQ,QAAQ,KAAK,KAAK,UAAU,QAAQ,CAAC;EAC7C,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The surface axis, and the policy that decides what it paints.
|
|
3
|
+
*
|
|
4
|
+
* A surface is how opaque the window's elevated panels are: `opaque` is the plain
|
|
5
|
+
* canvas, `frosted` is a tokenized translucent fill over a blur (works everywhere,
|
|
6
|
+
* in CSS alone), and `translucent` asks for the host window's own vibrancy, which only
|
|
7
|
+
* a desktop shell can honour.
|
|
8
|
+
*
|
|
9
|
+
* Nothing here touches the document or a signal. The provider reads the operating
|
|
10
|
+
* system and the host's capability, calls {@link resolveSurface}, and
|
|
11
|
+
* `theme-document.ts` writes the answer; the rule itself is a function of its four
|
|
12
|
+
* inputs, which is what makes it testable.
|
|
13
|
+
*/
|
|
14
|
+
export type ThemeSurface = 'opaque' | 'frosted' | 'translucent';
|
|
15
|
+
/** Every surface, in the order a picker offers them. */
|
|
16
|
+
export declare const themeSurfaces: readonly ThemeSurface[];
|
|
17
|
+
/** The media query the operating system's Reduce Transparency setting answers. */
|
|
18
|
+
export declare const REDUCED_TRANSPARENCY_QUERY = "(prefers-reduced-transparency: reduce)";
|
|
19
|
+
/**
|
|
20
|
+
* What the world says about transparency, as opposed to what the user prefers.
|
|
21
|
+
*/
|
|
22
|
+
export type SurfaceEnvironment = Readonly<{
|
|
23
|
+
/** The operating system asks for less transparency. */
|
|
24
|
+
osReducedTransparency: boolean;
|
|
25
|
+
/** The user turned reduced transparency on inside the application. */
|
|
26
|
+
userReducedTransparency: boolean;
|
|
27
|
+
/** The host window can render OS translucency (vibrancy, acrylic). */
|
|
28
|
+
nativeTranslucency: boolean;
|
|
29
|
+
}>;
|
|
30
|
+
/**
|
|
31
|
+
* A stored value as a surface, or nothing.
|
|
32
|
+
*
|
|
33
|
+
* `undefined` rather than `opaque` for an unreadable value: the selection field is
|
|
34
|
+
* optional, and an absent field is how a store that never chose a surface stays
|
|
35
|
+
* byte-identical to what it was before the axis existed.
|
|
36
|
+
*/
|
|
37
|
+
export declare function normalizeSurface(value: unknown): ThemeSurface | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* The surface to paint: the preference, after the accessibility override and the
|
|
40
|
+
* host's capability.
|
|
41
|
+
*
|
|
42
|
+
* The OS setting or the user's own flag forces an accessible opaque fallback. A
|
|
43
|
+
* translucent request on a host without native translucency renders the tokenized
|
|
44
|
+
* frosted surface instead of pretending to OS vibrancy.
|
|
45
|
+
*/
|
|
46
|
+
export declare function resolveSurface(preference: ThemeSurface, environment: SurfaceEnvironment): ThemeSurface;
|
|
47
|
+
/**
|
|
48
|
+
* The fill opacity each surface paints, written as `--surface-alpha`.
|
|
49
|
+
*
|
|
50
|
+
* These are the values `--surface-frost` and `--surface-translucent` declare in
|
|
51
|
+
* `theme.css` (the stylesheet is the readable copy; `tests/theme-surface.test.ts`
|
|
52
|
+
* fails when the two disagree). They are numbers rather than `var()` references so
|
|
53
|
+
* the property reads the same to a script, a test and the stylesheet, whichever
|
|
54
|
+
* wrote it.
|
|
55
|
+
*/
|
|
56
|
+
export declare const surfaceAlpha: Readonly<Record<ThemeSurface, string>>;
|
|
57
|
+
/** What a selection paints for the surface axis. */
|
|
58
|
+
export type SurfaceState = Readonly<{
|
|
59
|
+
/** The surface after the accessibility override and capability gate. */
|
|
60
|
+
surface: ThemeSurface;
|
|
61
|
+
/** True while the OS or the user forces opaque surfaces. */
|
|
62
|
+
reduceTransparencyActive: boolean;
|
|
63
|
+
}>;
|
|
64
|
+
/**
|
|
65
|
+
* The surface state for a preference and an environment.
|
|
66
|
+
*
|
|
67
|
+
* A preference of `undefined` is `opaque`: the axis is optional on a selection, and
|
|
68
|
+
* a host that never set it gets the plain canvas.
|
|
69
|
+
*/
|
|
70
|
+
export declare function resolveSurfaceState(preference: ThemeSurface | undefined, environment: SurfaceEnvironment): SurfaceState;
|
|
71
|
+
//# sourceMappingURL=theme-surface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"theme-surface.d.ts","sourceRoot":"","sources":["../../src/lib/theme-surface.ts"],"names":[],"mappings":"AAUA;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,SAAS,GAAG,aAAa,CAAA;AAE/D,wDAAwD;AACxD,eAAO,MAAM,aAAa,EAAE,SAAS,YAAY,EAAyC,CAAA;AAE1F,kFAAkF;AAClF,eAAO,MAAM,0BAA0B,2CAA2C,CAAA;AAElF;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,QAAQ,CAAC;IACxC,uDAAuD;IACvD,qBAAqB,EAAE,OAAO,CAAA;IAC9B,sEAAsE;IACtE,uBAAuB,EAAE,OAAO,CAAA;IAChC,sEAAsE;IACtE,kBAAkB,EAAE,OAAO,CAAA;CAC5B,CAAC,CAAA;AAEF;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY,GAAG,SAAS,CAEzE;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,UAAU,EAAE,YAAY,EACxB,WAAW,EAAE,kBAAkB,GAC9B,YAAY,CAId;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAI/D,CAAA;AAED,oDAAoD;AACpD,MAAM,MAAM,YAAY,GAAG,QAAQ,CAAC;IAClC,wEAAwE;IACxE,OAAO,EAAE,YAAY,CAAA;IACrB,4DAA4D;IAC5D,wBAAwB,EAAE,OAAO,CAAA;CAClC,CAAC,CAAA;AAEF;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,YAAY,GAAG,SAAS,EACpC,WAAW,EAAE,kBAAkB,GAC9B,YAAY,CAMd"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
//#region src/lib/theme-surface.ts
|
|
2
|
+
/** Every surface, in the order a picker offers them. */
|
|
3
|
+
var themeSurfaces = [
|
|
4
|
+
"opaque",
|
|
5
|
+
"frosted",
|
|
6
|
+
"translucent"
|
|
7
|
+
];
|
|
8
|
+
/** The media query the operating system's Reduce Transparency setting answers. */
|
|
9
|
+
var REDUCED_TRANSPARENCY_QUERY = "(prefers-reduced-transparency: reduce)";
|
|
10
|
+
/**
|
|
11
|
+
* A stored value as a surface, or nothing.
|
|
12
|
+
*
|
|
13
|
+
* `undefined` rather than `opaque` for an unreadable value: the selection field is
|
|
14
|
+
* optional, and an absent field is how a store that never chose a surface stays
|
|
15
|
+
* byte-identical to what it was before the axis existed.
|
|
16
|
+
*/
|
|
17
|
+
function normalizeSurface(value) {
|
|
18
|
+
return value === "opaque" || value === "frosted" || value === "translucent" ? value : void 0;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The surface to paint: the preference, after the accessibility override and the
|
|
22
|
+
* host's capability.
|
|
23
|
+
*
|
|
24
|
+
* The OS setting or the user's own flag forces an accessible opaque fallback. A
|
|
25
|
+
* translucent request on a host without native translucency renders the tokenized
|
|
26
|
+
* frosted surface instead of pretending to OS vibrancy.
|
|
27
|
+
*/
|
|
28
|
+
function resolveSurface(preference, environment) {
|
|
29
|
+
if (environment.osReducedTransparency || environment.userReducedTransparency) return "opaque";
|
|
30
|
+
if (preference === "translucent" && !environment.nativeTranslucency) return "frosted";
|
|
31
|
+
return preference;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The fill opacity each surface paints, written as `--surface-alpha`.
|
|
35
|
+
*
|
|
36
|
+
* These are the values `--surface-frost` and `--surface-translucent` declare in
|
|
37
|
+
* `theme.css` (the stylesheet is the readable copy; `tests/theme-surface.test.ts`
|
|
38
|
+
* fails when the two disagree). They are numbers rather than `var()` references so
|
|
39
|
+
* the property reads the same to a script, a test and the stylesheet, whichever
|
|
40
|
+
* wrote it.
|
|
41
|
+
*/
|
|
42
|
+
var surfaceAlpha = {
|
|
43
|
+
opaque: "1",
|
|
44
|
+
frosted: "0.92",
|
|
45
|
+
translucent: "0.8"
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* The surface state for a preference and an environment.
|
|
49
|
+
*
|
|
50
|
+
* A preference of `undefined` is `opaque`: the axis is optional on a selection, and
|
|
51
|
+
* a host that never set it gets the plain canvas.
|
|
52
|
+
*/
|
|
53
|
+
function resolveSurfaceState(preference, environment) {
|
|
54
|
+
return {
|
|
55
|
+
surface: resolveSurface(preference ?? "opaque", environment),
|
|
56
|
+
reduceTransparencyActive: environment.osReducedTransparency || environment.userReducedTransparency
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
export { REDUCED_TRANSPARENCY_QUERY, normalizeSurface, resolveSurface, resolveSurfaceState, surfaceAlpha, themeSurfaces };
|
|
61
|
+
|
|
62
|
+
//# sourceMappingURL=theme-surface.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"theme-surface.js","names":[],"sources":["../../src/lib/theme-surface.ts"],"sourcesContent":["/*\n * Copyright (c) 2026 Wing\n * Licensed under the MIT License.\n *\n * `resolveSurface` is Zeron's `SurfacePreference.resolve` (https://github.com/zeronsh/zeron),\n * extended by Adea's accepted port of it (`packages/ui/src/components/appearance.ts`)\n * with the translucent capability gate and the reduced-transparency policy. Moved\n * here so the shared provider and Adea run one implementation. See NOTICE.\n */\n\n/**\n * The surface axis, and the policy that decides what it paints.\n *\n * A surface is how opaque the window's elevated panels are: `opaque` is the plain\n * canvas, `frosted` is a tokenized translucent fill over a blur (works everywhere,\n * in CSS alone), and `translucent` asks for the host window's own vibrancy, which only\n * a desktop shell can honour.\n *\n * Nothing here touches the document or a signal. The provider reads the operating\n * system and the host's capability, calls {@link resolveSurface}, and\n * `theme-document.ts` writes the answer; the rule itself is a function of its four\n * inputs, which is what makes it testable.\n */\nexport type ThemeSurface = 'opaque' | 'frosted' | 'translucent'\n\n/** Every surface, in the order a picker offers them. */\nexport const themeSurfaces: readonly ThemeSurface[] = ['opaque', 'frosted', 'translucent']\n\n/** The media query the operating system's Reduce Transparency setting answers. */\nexport const REDUCED_TRANSPARENCY_QUERY = '(prefers-reduced-transparency: reduce)'\n\n/**\n * What the world says about transparency, as opposed to what the user prefers.\n */\nexport type SurfaceEnvironment = Readonly<{\n /** The operating system asks for less transparency. */\n osReducedTransparency: boolean\n /** The user turned reduced transparency on inside the application. */\n userReducedTransparency: boolean\n /** The host window can render OS translucency (vibrancy, acrylic). */\n nativeTranslucency: boolean\n}>\n\n/**\n * A stored value as a surface, or nothing.\n *\n * `undefined` rather than `opaque` for an unreadable value: the selection field is\n * optional, and an absent field is how a store that never chose a surface stays\n * byte-identical to what it was before the axis existed.\n */\nexport function normalizeSurface(value: unknown): ThemeSurface | undefined {\n return value === 'opaque' || value === 'frosted' || value === 'translucent' ? value : undefined\n}\n\n/**\n * The surface to paint: the preference, after the accessibility override and the\n * host's capability.\n *\n * The OS setting or the user's own flag forces an accessible opaque fallback. A\n * translucent request on a host without native translucency renders the tokenized\n * frosted surface instead of pretending to OS vibrancy.\n */\nexport function resolveSurface(\n preference: ThemeSurface,\n environment: SurfaceEnvironment\n): ThemeSurface {\n if (environment.osReducedTransparency || environment.userReducedTransparency) return 'opaque'\n if (preference === 'translucent' && !environment.nativeTranslucency) return 'frosted'\n return preference\n}\n\n/**\n * The fill opacity each surface paints, written as `--surface-alpha`.\n *\n * These are the values `--surface-frost` and `--surface-translucent` declare in\n * `theme.css` (the stylesheet is the readable copy; `tests/theme-surface.test.ts`\n * fails when the two disagree). They are numbers rather than `var()` references so\n * the property reads the same to a script, a test and the stylesheet, whichever\n * wrote it.\n */\nexport const surfaceAlpha: Readonly<Record<ThemeSurface, string>> = {\n opaque: '1',\n frosted: '0.92',\n translucent: '0.8',\n}\n\n/** What a selection paints for the surface axis. */\nexport type SurfaceState = Readonly<{\n /** The surface after the accessibility override and capability gate. */\n surface: ThemeSurface\n /** True while the OS or the user forces opaque surfaces. */\n reduceTransparencyActive: boolean\n}>\n\n/**\n * The surface state for a preference and an environment.\n *\n * A preference of `undefined` is `opaque`: the axis is optional on a selection, and\n * a host that never set it gets the plain canvas.\n */\nexport function resolveSurfaceState(\n preference: ThemeSurface | undefined,\n environment: SurfaceEnvironment\n): SurfaceState {\n return {\n surface: resolveSurface(preference ?? 'opaque', environment),\n reduceTransparencyActive:\n environment.osReducedTransparency || environment.userReducedTransparency,\n }\n}\n"],"mappings":";;AA0BA,IAAa,gBAAyC;CAAC;CAAU;CAAW;AAAa;;AAGzF,IAAa,6BAA6B;;;;;;;;AAqB1C,SAAgB,iBAAiB,OAA0C;CACzE,OAAO,UAAU,YAAY,UAAU,aAAa,UAAU,gBAAgB,QAAQ,KAAA;AACxF;;;;;;;;;AAUA,SAAgB,eACd,YACA,aACc;CACd,IAAI,YAAY,yBAAyB,YAAY,yBAAyB,OAAO;CACrF,IAAI,eAAe,iBAAiB,CAAC,YAAY,oBAAoB,OAAO;CAC5E,OAAO;AACT;;;;;;;;;;AAWA,IAAa,eAAuD;CAClE,QAAQ;CACR,SAAS;CACT,aAAa;AACf;;;;;;;AAgBA,SAAgB,oBACd,YACA,aACc;CACd,OAAO;EACL,SAAS,eAAe,cAAc,UAAU,WAAW;EAC3D,0BACE,YAAY,yBAAyB,YAAY;CACrD;AACF"}
|
package/dist/lib/themes.d.ts
CHANGED
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
* `surfaceHover`, which is what shadcn components actually use it for.
|
|
34
34
|
*/
|
|
35
35
|
import { type AccentPreset, type AdeaTheme, type AdeaThemeRecord } from '@adea-ai/themes';
|
|
36
|
+
import { type AccentRoles } from './accent-roles';
|
|
36
37
|
export { isThemeAccentId } from './theme-accents';
|
|
37
38
|
export type ThemeAppearance = 'light' | 'dark';
|
|
38
39
|
/**
|
|
@@ -289,7 +290,28 @@ export declare function appearancePreviewThemes(selection: {
|
|
|
289
290
|
export declare function accentVariables(accentId: string, appearance: ThemeAppearance, pair?: {
|
|
290
291
|
lightThemeId: string;
|
|
291
292
|
darkThemeId: string;
|
|
292
|
-
}): Record<string, string> | undefined;
|
|
293
|
+
}, options?: AccentVariableOptions): Record<string, string> | undefined;
|
|
294
|
+
/** What an accent is held to when it is resolved against a surface. */
|
|
295
|
+
export type AccentVariableOptions = {
|
|
296
|
+
/** The surface the accent must read on: the variant's `colors.background`. */
|
|
297
|
+
background?: string;
|
|
298
|
+
/**
|
|
299
|
+
* The contrast floor against `background`. Absent, a preset is painted verbatim
|
|
300
|
+
* and only a custom `#rrggbb` is held to the default floor.
|
|
301
|
+
*/
|
|
302
|
+
floor?: number;
|
|
303
|
+
/** The variant the accent is applied to, so the tint takes its measured strength. */
|
|
304
|
+
variantId?: string;
|
|
305
|
+
};
|
|
306
|
+
/**
|
|
307
|
+
* An accent selection resolved to the full {@link AccentRoles}, against a theme
|
|
308
|
+
* pair. {@link accentVariables} is the document half of this; an element-scoped
|
|
309
|
+
* paint (`paintAccentRoles`) wants all of it, including the tint.
|
|
310
|
+
*/
|
|
311
|
+
export declare function accentRolesForSelection(accentId: string, appearance: ThemeAppearance, pair?: {
|
|
312
|
+
lightThemeId: string;
|
|
313
|
+
darkThemeId: string;
|
|
314
|
+
}, options?: AccentVariableOptions): AccentRoles | undefined;
|
|
293
315
|
/**
|
|
294
316
|
* The roles a variant sets, as CSS custom properties.
|
|
295
317
|
*
|
|
@@ -299,6 +321,15 @@ export declare function accentVariables(accentId: string, appearance: ThemeAppea
|
|
|
299
321
|
* than separate roles, which is why they are built here and not in the catalogue.
|
|
300
322
|
*/
|
|
301
323
|
export declare function themeCssVariables(theme: ThemeVariant): Record<string, string>;
|
|
324
|
+
/**
|
|
325
|
+
* The `--terminal-*` properties of a variant: the palette a terminal paints.
|
|
326
|
+
*
|
|
327
|
+
* What a pinned terminal theme writes over the interface variant's own. Taken from
|
|
328
|
+
* {@link themeCssVariables} rather than rebuilt, so the names, and a custom
|
|
329
|
+
* variant's verbatim `tokens`, can never drift from what the interface variant
|
|
330
|
+
* would have written for the same palette.
|
|
331
|
+
*/
|
|
332
|
+
export declare function terminalCssVariables(theme: ThemeVariant): Record<string, string>;
|
|
302
333
|
/**
|
|
303
334
|
* Token values with every `oklch()` colour written as `#rrggbb`.
|
|
304
335
|
*
|
package/dist/lib/themes.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"themes.d.ts","sourceRoot":"","sources":["../../src/lib/themes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"themes.d.ts","sourceRoot":"","sources":["../../src/lib/themes.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/themes.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { accentOverrideVariables, resolveAccentRoles } from "./accent-roles.js";
|
|
1
2
|
import { isThemeAccentId } from "./theme-accents.js";
|
|
2
|
-
import { accentForeground,
|
|
3
|
+
import { accentForeground, chartSeries, contrastRatio as contrastRatio$1, getAccent, getTheme, hasTheme, primaryHover, primarySubtleCss, shadcnDestructiveProjection, statusForeground, syntaxRoles, themeAccentPresets, themes, themesByAppearance, tint, validateCatalogue } from "@adea-ai/themes";
|
|
3
4
|
import { shadcnVariables } from "@adea-ai/themes/adapters/shadcn";
|
|
4
5
|
import { oklchToHex, parseColor } from "@adea-ai/themes/oklch";
|
|
5
6
|
//#region src/lib/themes.ts
|
|
@@ -364,18 +365,27 @@ function appearancePreviewThemes(selection) {
|
|
|
364
365
|
function accentVariables(accentId, appearance, pair = {
|
|
365
366
|
lightThemeId: defaultLightThemeId,
|
|
366
367
|
darkThemeId: defaultDarkThemeId
|
|
367
|
-
}) {
|
|
368
|
+
}, options = {}) {
|
|
369
|
+
const roles = accentRolesForSelection(accentId, appearance, pair, options);
|
|
370
|
+
return roles ? accentOverrideVariables(roles) : void 0;
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* An accent selection resolved to the full {@link AccentRoles}, against a theme
|
|
374
|
+
* pair. {@link accentVariables} is the document half of this; an element-scoped
|
|
375
|
+
* paint (`paintAccentRoles`) wants all of it, including the tint.
|
|
376
|
+
*/
|
|
377
|
+
function accentRolesForSelection(accentId, appearance, pair = {
|
|
378
|
+
lightThemeId: defaultLightThemeId,
|
|
379
|
+
darkThemeId: defaultDarkThemeId
|
|
380
|
+
}, options = {}) {
|
|
368
381
|
if (accentId === "theme") return void 0;
|
|
369
|
-
const
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
"--primary-hover": roles.primaryHover,
|
|
377
|
-
"--ring": roles.ring
|
|
378
|
-
};
|
|
382
|
+
const record = options.variantId ? getTheme(options.variantId) : void 0;
|
|
383
|
+
return resolveAccentRoles(accentId, {
|
|
384
|
+
target: record ?? appearance,
|
|
385
|
+
background: options.background,
|
|
386
|
+
floor: options.floor,
|
|
387
|
+
themeAccents: isThemeAccentId(accentId) ? themeAccentsFor(pair.lightThemeId, pair.darkThemeId) : void 0
|
|
388
|
+
});
|
|
379
389
|
}
|
|
380
390
|
/**
|
|
381
391
|
* The roles a variant sets, as CSS custom properties.
|
|
@@ -484,6 +494,17 @@ function themeCssVariables(theme) {
|
|
|
484
494
|
} : derived;
|
|
485
495
|
}
|
|
486
496
|
/**
|
|
497
|
+
* The `--terminal-*` properties of a variant: the palette a terminal paints.
|
|
498
|
+
*
|
|
499
|
+
* What a pinned terminal theme writes over the interface variant's own. Taken from
|
|
500
|
+
* {@link themeCssVariables} rather than rebuilt, so the names, and a custom
|
|
501
|
+
* variant's verbatim `tokens`, can never drift from what the interface variant
|
|
502
|
+
* would have written for the same palette.
|
|
503
|
+
*/
|
|
504
|
+
function terminalCssVariables(theme) {
|
|
505
|
+
return Object.fromEntries(Object.entries(themeCssVariables(theme)).filter(([name]) => name.startsWith("--terminal-")));
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
487
508
|
* Token values with every `oklch()` colour written as `#rrggbb`.
|
|
488
509
|
*
|
|
489
510
|
* For a host whose stylesheet, tests or native shell speak hex: the catalogue's
|
|
@@ -531,6 +552,6 @@ function validateThemeRegistry(themes$1 = builtinThemes) {
|
|
|
531
552
|
var defaultLightThemeId = "adea-light";
|
|
532
553
|
var defaultDarkThemeId = "adea-dark";
|
|
533
554
|
//#endregion
|
|
534
|
-
export { accentVariables, appearancePreviewThemes, builtinThemes, contrastRatio, defaultDarkThemeId, defaultLightThemeId, isThemeAccentId, primarySubtleFor, resolveAccentPreset, resolveVariant, themeAccentsFor, themeById, themeCssVariables, themeFamilies, themeRecordById, themeRecords, themesForAppearance, tokensAsHex, validateTheme, validateThemeRegistry, variantFromRecord, withAccent };
|
|
555
|
+
export { accentRolesForSelection, accentVariables, appearancePreviewThemes, builtinThemes, contrastRatio$1 as contrastRatio, defaultDarkThemeId, defaultLightThemeId, isThemeAccentId, primarySubtleFor, resolveAccentPreset, resolveVariant, terminalCssVariables, themeAccentsFor, themeById, themeCssVariables, themeFamilies, themeRecordById, themeRecords, themesForAppearance, tokensAsHex, validateTheme, validateThemeRegistry, variantFromRecord, withAccent };
|
|
535
556
|
|
|
536
557
|
//# sourceMappingURL=themes.js.map
|