@animus-ui/system 0.1.1 → 0.1.3
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 +125 -4
- package/dist/Animus.d.ts +2 -3
- package/dist/Animus.d.ts.map +1 -1
- package/dist/AnimusExtended.d.ts +2 -3
- package/dist/AnimusExtended.d.ts.map +1 -1
- package/dist/SystemBuilder.d.ts +55 -8
- package/dist/SystemBuilder.d.ts.map +1 -1
- package/dist/appearance/index.d.ts +75 -0
- package/dist/appearance/index.d.ts.map +1 -0
- package/dist/appearance/index.js +179 -0
- package/dist/bootstrap/createAppearanceBootstrap.d.ts +71 -0
- package/dist/bootstrap/createAppearanceBootstrap.d.ts.map +1 -0
- package/dist/bootstrap/index.d.ts +2 -0
- package/dist/bootstrap/index.d.ts.map +1 -0
- package/dist/bootstrap/index.js +98 -0
- package/dist/composeWithContext.d.ts.map +1 -1
- package/dist/composeWithContext.js +43 -22
- package/dist/conditions.d.ts +163 -0
- package/dist/conditions.d.ts.map +1 -0
- package/dist/{createComposedFamily-DSE2ZuRE.js → createComposedFamily-DJ2aqfqN.js} +141 -67
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +648 -25
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.d.ts.map +1 -1
- package/dist/runtime/resolveClasses.d.ts.map +1 -1
- package/dist/runtime-entry.js +1 -1
- package/dist/selectors.d.ts +22 -0
- package/dist/selectors.d.ts.map +1 -1
- package/dist/theme/createTheme.d.ts +47 -4
- package/dist/theme/createTheme.d.ts.map +1 -1
- package/dist/types/component.d.ts +27 -10
- package/dist/types/component.d.ts.map +1 -1
- package/dist/types/config.d.ts +124 -11
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/props.d.ts +0 -4
- package/dist/types/props.d.ts.map +1 -1
- package/dist/types/theme.d.ts +132 -0
- package/dist/types/theme.d.ts.map +1 -1
- package/dist/utils/deepMerge.d.ts +0 -3
- package/dist/utils/deepMerge.d.ts.map +1 -1
- package/package.json +12 -4
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The slice of a built theme the generator reads: the declared mode names.
|
|
3
|
+
* Structural on purpose — the generator reads mode KEYS only and never touches
|
|
4
|
+
* the theme's pipeline wire (guardrail G5).
|
|
5
|
+
*/
|
|
6
|
+
export interface AppearanceBootstrapTheme {
|
|
7
|
+
manifest: {
|
|
8
|
+
/** Mode name → resolved tokens. Only the KEYS are read. */
|
|
9
|
+
modes?: Record<string, unknown>;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export interface AppearanceBootstrapOptions {
|
|
13
|
+
/** Overrides the record key. Defaults to `animus:appearance`. */
|
|
14
|
+
storageKey?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface AppearanceBootstrapArtifact {
|
|
17
|
+
/** Dependency-free inline IIFE, safe to embed in a document head. */
|
|
18
|
+
code: string;
|
|
19
|
+
/**
|
|
20
|
+
* `sha256-<base64>` — the CSP hash of `code`, ready for a `script-src`
|
|
21
|
+
* source list.
|
|
22
|
+
*
|
|
23
|
+
* The source expression MUST be SINGLE-QUOTED in the header, exactly as
|
|
24
|
+
* returned:
|
|
25
|
+
*
|
|
26
|
+
* ```
|
|
27
|
+
* Content-Security-Policy: script-src 'sha256-<the artifact hash>'
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* Unquoted, it is parsed as a host source and silently fails to authorize
|
|
31
|
+
* the script.
|
|
32
|
+
*
|
|
33
|
+
* Always derive the header from this field at build time — never hand-copy
|
|
34
|
+
* it. Any theme edit that changes the declared mode names (or a changed
|
|
35
|
+
* `storageKey`) changes `code` and therefore this hash; a stale literal in a
|
|
36
|
+
* config file turns into a blocked script and a flash of the wrong mode.
|
|
37
|
+
*/
|
|
38
|
+
cspHash: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Builds the inline appearance-restoration script for a built theme.
|
|
42
|
+
*
|
|
43
|
+
* The generated snippet, before first paint:
|
|
44
|
+
* 1. reads the appearance record from `storageKey`, falling back to the legacy
|
|
45
|
+
* `color-mode` key ONLY when the record is absent (missing key, or an empty
|
|
46
|
+
* string, which is not a record);
|
|
47
|
+
* 2. accepts the record only at version `1` — a future or absent version is
|
|
48
|
+
* treated as NO record, because an older snippet cannot interpret a newer
|
|
49
|
+
* record and absence is the neutral fail-safe;
|
|
50
|
+
* 3. sets `data-color-mode` when the resulting name is a declared mode;
|
|
51
|
+
* 4. removes the attribute for `system`, no record, an unreadable store, or an
|
|
52
|
+
* unknown mode — absence is how "follow the OS" is represented, so later OS
|
|
53
|
+
* changes apply with no script running.
|
|
54
|
+
*
|
|
55
|
+
* The failure semantics are deliberately asymmetric:
|
|
56
|
+
* - storage that THROWS on read (private browsing, blocked storage) means "no
|
|
57
|
+
* knowledge", so the attribute is REMOVED and the media-query fallback takes
|
|
58
|
+
* over; freezing a server-rendered mode would defeat that fallback;
|
|
59
|
+
* - a record that is PRESENT but unparseable means "storage is telling us
|
|
60
|
+
* something we cannot read", so the snippet exits leaving the server-rendered
|
|
61
|
+
* markup exactly as it was.
|
|
62
|
+
*
|
|
63
|
+
* It never writes storage, never calls `matchMedia`, and ignores the record's
|
|
64
|
+
* `theme` axis. Identical inputs always yield byte-identical output.
|
|
65
|
+
*
|
|
66
|
+
* @throws if the theme declares no color modes (a bootstrap without a mode
|
|
67
|
+
* allowlist could not validate anything), declares the reserved name
|
|
68
|
+
* `system`, or declares a blank mode name.
|
|
69
|
+
*/
|
|
70
|
+
export declare function createAppearanceBootstrap(theme: AppearanceBootstrapTheme, options?: AppearanceBootstrapOptions): AppearanceBootstrapArtifact;
|
|
71
|
+
//# sourceMappingURL=createAppearanceBootstrap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createAppearanceBootstrap.d.ts","sourceRoot":"","sources":["../../src/bootstrap/createAppearanceBootstrap.ts"],"names":[],"mappings":"AAgCA;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE;QACR,2DAA2D;QAC3D,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACjC,CAAC;CACH;AAED,MAAM,WAAW,0BAA0B;IACzC,iEAAiE;IACjE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,2BAA2B;IAC1C,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;;;;;;;;;;;;;OAkBG;IACH,OAAO,EAAE,MAAM,CAAC;CACjB;AA8BD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,wBAAwB,EAC/B,OAAO,GAAE,0BAA+B,GACvC,2BAA2B,CAsE7B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/bootstrap/index.ts"],"names":[],"mappings":"AASA,OAAO,EACL,KAAK,2BAA2B,EAChC,KAAK,0BAA0B,EAC/B,KAAK,wBAAwB,EAC7B,yBAAyB,GAC1B,MAAM,gCAAgC,CAAC"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
//#region src/bootstrap/createAppearanceBootstrap.ts
|
|
3
|
+
/**
|
|
4
|
+
* Appearance bootstrap generator — build-tooling surface ONLY.
|
|
5
|
+
*
|
|
6
|
+
* This module is reachable exclusively from the `@animus-ui/system/bootstrap`
|
|
7
|
+
* subpath. It is never imported by the component/runtime entries, so no part of
|
|
8
|
+
* it (including the storage keys below) can reach an extracted application
|
|
9
|
+
* bundle.
|
|
10
|
+
*/
|
|
11
|
+
/** The single versioned appearance record key. */
|
|
12
|
+
const DEFAULT_STORAGE_KEY = "animus:appearance";
|
|
13
|
+
/**
|
|
14
|
+
* Pre-record key read once, only when the record is absent. Never written —
|
|
15
|
+
* migration is read-only by contract.
|
|
16
|
+
*/
|
|
17
|
+
const LEGACY_STORAGE_KEY = "color-mode";
|
|
18
|
+
/** The mode axis attribute. Its ABSENCE means "follow the OS" (D4). */
|
|
19
|
+
const MODE_ATTRIBUTE = "data-color-mode";
|
|
20
|
+
/**
|
|
21
|
+
* Reserved mode name. `system` is modeled as attribute absence and can never be
|
|
22
|
+
* a declared mode, so it must never enter the generated allowlist.
|
|
23
|
+
*/
|
|
24
|
+
const RESERVED_MODE_NAME = "system";
|
|
25
|
+
/** The only appearance-record version this generation of the snippet reads. */
|
|
26
|
+
const RECORD_VERSION = 1;
|
|
27
|
+
/**
|
|
28
|
+
* Renders a string as a JS literal that is also safe inside an inline
|
|
29
|
+
* `<script>` and inside a consumer's own string context:
|
|
30
|
+
* - `<` is escaped, so an embedded value can never open a closing tag or an
|
|
31
|
+
* HTML comment;
|
|
32
|
+
* - U+2028/U+2029 are escaped, so `code` survives being nested in string
|
|
33
|
+
* contexts that still treat them as line terminators.
|
|
34
|
+
*
|
|
35
|
+
* Character-wise on purpose: this file stays pure ASCII, so no invisible
|
|
36
|
+
* separator can hide in it.
|
|
37
|
+
*/
|
|
38
|
+
function inlineLiteral(value) {
|
|
39
|
+
let out = "";
|
|
40
|
+
for (const char of JSON.stringify(value)) {
|
|
41
|
+
if (char === "<") {
|
|
42
|
+
out += "\\u003c";
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const code = char.codePointAt(0);
|
|
46
|
+
if (code === 8232 || code === 8233) {
|
|
47
|
+
out += `\\u${code.toString(16)}`;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
out += char;
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Builds the inline appearance-restoration script for a built theme.
|
|
56
|
+
*
|
|
57
|
+
* The generated snippet, before first paint:
|
|
58
|
+
* 1. reads the appearance record from `storageKey`, falling back to the legacy
|
|
59
|
+
* `color-mode` key ONLY when the record is absent (missing key, or an empty
|
|
60
|
+
* string, which is not a record);
|
|
61
|
+
* 2. accepts the record only at version `1` — a future or absent version is
|
|
62
|
+
* treated as NO record, because an older snippet cannot interpret a newer
|
|
63
|
+
* record and absence is the neutral fail-safe;
|
|
64
|
+
* 3. sets `data-color-mode` when the resulting name is a declared mode;
|
|
65
|
+
* 4. removes the attribute for `system`, no record, an unreadable store, or an
|
|
66
|
+
* unknown mode — absence is how "follow the OS" is represented, so later OS
|
|
67
|
+
* changes apply with no script running.
|
|
68
|
+
*
|
|
69
|
+
* The failure semantics are deliberately asymmetric:
|
|
70
|
+
* - storage that THROWS on read (private browsing, blocked storage) means "no
|
|
71
|
+
* knowledge", so the attribute is REMOVED and the media-query fallback takes
|
|
72
|
+
* over; freezing a server-rendered mode would defeat that fallback;
|
|
73
|
+
* - a record that is PRESENT but unparseable means "storage is telling us
|
|
74
|
+
* something we cannot read", so the snippet exits leaving the server-rendered
|
|
75
|
+
* markup exactly as it was.
|
|
76
|
+
*
|
|
77
|
+
* It never writes storage, never calls `matchMedia`, and ignores the record's
|
|
78
|
+
* `theme` axis. Identical inputs always yield byte-identical output.
|
|
79
|
+
*
|
|
80
|
+
* @throws if the theme declares no color modes (a bootstrap without a mode
|
|
81
|
+
* allowlist could not validate anything), declares the reserved name
|
|
82
|
+
* `system`, or declares a blank mode name.
|
|
83
|
+
*/
|
|
84
|
+
function createAppearanceBootstrap(theme, options = {}) {
|
|
85
|
+
const { storageKey = DEFAULT_STORAGE_KEY } = options;
|
|
86
|
+
if (typeof storageKey !== "string" || storageKey === "") throw new Error("createAppearanceBootstrap: storageKey must be a non-empty string.");
|
|
87
|
+
const modeNames = Object.keys(theme?.manifest?.modes ?? {}).sort();
|
|
88
|
+
if (modeNames.length === 0) throw new Error("createAppearanceBootstrap: the theme declares no color modes — call addColorModes() before generating a bootstrap.");
|
|
89
|
+
if (modeNames.includes(RESERVED_MODE_NAME)) throw new Error(`createAppearanceBootstrap: '${RESERVED_MODE_NAME}' is a reserved mode name — the OS preference is represented by the absence of the ${MODE_ATTRIBUTE} attribute, never by a declared mode.`);
|
|
90
|
+
if (modeNames.some((name) => name.trim() === "")) throw new Error("createAppearanceBootstrap: a declared mode name is empty or whitespace-only — such a name matches no mode block and would suppress the system fallback.");
|
|
91
|
+
const code = `(function(){try{var m=${`[${modeNames.map(inlineLiteral).join(",")}]`};var r=document.documentElement;var v=null;try{v=localStorage.getItem(${inlineLiteral(storageKey)});}catch(e){v=null;}var n=null;if(typeof v==="string"&&v!==""){var p;try{p=JSON.parse(v);}catch(e){return;}n=p&&typeof p==="object"&&p.v===${RECORD_VERSION}?p.mode:null;}else{try{n=localStorage.getItem(${inlineLiteral(LEGACY_STORAGE_KEY)});}catch(e){n=null;}}if(typeof n==="string"&&m.indexOf(n)!==-1){r.setAttribute(${inlineLiteral(MODE_ATTRIBUTE)},n);}else{r.removeAttribute(${inlineLiteral(MODE_ATTRIBUTE)});}}catch(e){}})();`;
|
|
92
|
+
return {
|
|
93
|
+
code,
|
|
94
|
+
cspHash: `sha256-${createHash("sha256").update(code, "utf8").digest("base64")}`
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
//#endregion
|
|
98
|
+
export { createAppearanceBootstrap };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"composeWithContext.d.ts","sourceRoot":"","sources":["../src/composeWithContext.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,yBAAyB,EAI/B,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EACV,mBAAmB,EACnB,cAAc,EACd,YAAY,EACb,MAAM,mBAAmB,CAAC;AAE3B;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,EACjD,KAAK,CAAC,MAAM,SAAS,YAAY,CAAC,KAAK,CAAC,EAExC,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GACzC,cAAc,CAAC,KAAK,CAAC,
|
|
1
|
+
{"version":3,"file":"composeWithContext.d.ts","sourceRoot":"","sources":["../src/composeWithContext.ts"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,yBAAyB,EAI/B,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EACV,mBAAmB,EACnB,cAAc,EACd,YAAY,EACb,MAAM,mBAAmB,CAAC;AAE3B;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,EACjD,KAAK,CAAC,MAAM,SAAS,YAAY,CAAC,KAAK,CAAC,EAExC,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GACzC,cAAc,CAAC,KAAK,CAAC,CAmEvB;AAED;;;;;;;;GAQG;AACH,wBAAgB,+BAA+B,CAC7C,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,yBAAyB,CAAC,GAAG,CAAC,CAAC,EACrD,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAA;CAAE,GAC7C,MAAM,CAAC,MAAM,EAAE,yBAAyB,CAAC,GAAG,CAAC,CAAC,CA+DhD"}
|
|
@@ -18,18 +18,28 @@ function composeWithContext(slots, options) {
|
|
|
18
18
|
const result = {};
|
|
19
19
|
for (const [name, SourceComponent] of Object.entries(slots)) {
|
|
20
20
|
let Wrapper;
|
|
21
|
-
if (name === "Root")
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
21
|
+
if (name === "Root") {
|
|
22
|
+
const rootDefaults = SourceComponent.variantDefaults;
|
|
23
|
+
Wrapper = forwardRef((props, ref) => {
|
|
24
|
+
const shared = {};
|
|
25
|
+
for (const key of sharedKeySet) {
|
|
26
|
+
const value = props[key] ?? rootDefaults?.[key];
|
|
27
|
+
if (value != null) shared[key] = value;
|
|
28
|
+
}
|
|
29
|
+
return createElement(SourceComponent, {
|
|
30
|
+
...props,
|
|
31
|
+
ref
|
|
32
|
+
}, createElement(FamilyCtx.Provider, { value: shared }, props.children));
|
|
33
|
+
});
|
|
34
|
+
} else Wrapper = forwardRef((props, ref) => {
|
|
35
|
+
const inherited = useContext(FamilyCtx);
|
|
36
|
+
const merged = {
|
|
37
|
+
...inherited,
|
|
38
|
+
...props
|
|
39
|
+
};
|
|
40
|
+
for (const key of sharedKeySet) if (props[key] == null && key in inherited) merged[key] = inherited[key];
|
|
30
41
|
return createElement(SourceComponent, {
|
|
31
|
-
...
|
|
32
|
-
...props,
|
|
42
|
+
...merged,
|
|
33
43
|
ref
|
|
34
44
|
});
|
|
35
45
|
});
|
|
@@ -55,24 +65,35 @@ function createComposedFamilyWithContext(slots, config) {
|
|
|
55
65
|
const result = {};
|
|
56
66
|
for (const [slotName, SourceComponent] of Object.entries(slots)) {
|
|
57
67
|
let Wrapper;
|
|
58
|
-
if (slotName === "Root")
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
68
|
+
if (slotName === "Root") {
|
|
69
|
+
const rootDefaults = SourceComponent.variantDefaults;
|
|
70
|
+
Wrapper = forwardRef((props, ref) => {
|
|
71
|
+
const shared = {};
|
|
72
|
+
for (const key of keySet) {
|
|
73
|
+
const value = props[key] ?? rootDefaults?.[key];
|
|
74
|
+
if (value != null) shared[key] = value;
|
|
75
|
+
}
|
|
76
|
+
return createElement(SourceComponent, {
|
|
77
|
+
...props,
|
|
78
|
+
ref
|
|
79
|
+
}, createElement(Ctx.Provider, { value: shared }, props.children));
|
|
80
|
+
});
|
|
81
|
+
} else Wrapper = forwardRef((props, ref) => {
|
|
82
|
+
const inherited = useContext(Ctx);
|
|
83
|
+
const merged = {
|
|
84
|
+
...inherited,
|
|
85
|
+
...props
|
|
86
|
+
};
|
|
87
|
+
for (const key of keySet) if (props[key] == null && key in inherited) merged[key] = inherited[key];
|
|
67
88
|
return createElement(SourceComponent, {
|
|
68
|
-
...
|
|
69
|
-
...props,
|
|
89
|
+
...merged,
|
|
70
90
|
ref
|
|
71
91
|
});
|
|
72
92
|
});
|
|
73
93
|
Wrapper.displayName = `${name}.${slotName}`;
|
|
74
94
|
result[slotName] = Wrapper;
|
|
75
95
|
}
|
|
96
|
+
if (!("Root" in result)) throw new Error("createComposedFamilyWithContext(): No \"Root\" slot found. The root slot key must be exactly \"Root\" (PascalCase).");
|
|
76
97
|
return result;
|
|
77
98
|
}
|
|
78
99
|
//#endregion
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Condition alias registry — maps `_`-prefixed alias keys to at-rule condition
|
|
3
|
+
* strings (`@media` / `@container` / `@supports`). Parallel in shape to the
|
|
4
|
+
* selector alias registry (`selectors.ts`): alias → `{ value, order, kind }`.
|
|
5
|
+
*
|
|
6
|
+
* `order` determines cascade precedence within a layer (aliased conditions emit
|
|
7
|
+
* in registry order, per design D4). `kind` is inferred from the at-rule prefix
|
|
8
|
+
* of `value` (design D3) and drives which at-rule the extractor wraps the block
|
|
9
|
+
* in. Serialized into the manifest as the NEW `conditionAliases` field, leaving
|
|
10
|
+
* `selectorAliases` byte-for-byte unchanged.
|
|
11
|
+
*
|
|
12
|
+
* This module hosts BOTH the runtime registry and the authoring TYPE surface
|
|
13
|
+
* (inc 04): the augmentable `Conditions`/`Selectors` publication, the
|
|
14
|
+
* `ConditionsOf`/`SelectorsOf` extractors, the branded rejection types
|
|
15
|
+
* (`UnknownConditionAlias`/`UnknownAtRule`), and the shallow `RawAtRuleKey`
|
|
16
|
+
* shapes consumed by `ThemedCSSProps`' kind-dispatched arms. `addConditions()`
|
|
17
|
+
* constrains values to `@media`/`@container`/`@supports`-prefixed strings at
|
|
18
|
+
* the type level; `inferConditionKind` remains the runtime fail-loud check.
|
|
19
|
+
*/
|
|
20
|
+
/** The three condition kinds, inferred from the at-rule prefix of the value. */
|
|
21
|
+
export type ConditionKind = 'media' | 'container' | 'supports';
|
|
22
|
+
/**
|
|
23
|
+
* Augmentable registry of registered condition alias keys. Empty by default;
|
|
24
|
+
* a consumer publishes its registrations with:
|
|
25
|
+
*
|
|
26
|
+
* ```ts
|
|
27
|
+
* declare module '@animus-ui/system' {
|
|
28
|
+
* interface Conditions extends Record<ConditionsOf<typeof ds>, true> {}
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* When this interface is empty (no publication), `ThemedCSSProps` keeps
|
|
33
|
+
* `_`-prefixed block keys permissive — the same graceful degradation the
|
|
34
|
+
* augmentable `Theme` uses (empty theme ⇒ raw CSS values). Once populated, the
|
|
35
|
+
* validating arms engage: unknown `_` keys resolve to `UnknownConditionAlias`.
|
|
36
|
+
*
|
|
37
|
+
* JOINT NAMESPACE (inc-04 F8/F9): condition and selector aliases share the one
|
|
38
|
+
* `_` block-key namespace, so publishing EITHER `Conditions` OR `Selectors`
|
|
39
|
+
* flips BOTH namespaces from permissive to validating (see `KnownUnderscoreKey`
|
|
40
|
+
* in `types/config.ts`, whose gate reads `keyof Conditions | keyof Selectors`).
|
|
41
|
+
*
|
|
42
|
+
* Built-in condition aliases (`BUILT_IN_CONDITIONS`, design D8) are NOT members
|
|
43
|
+
* of this interface — they live in the static `BuiltInConditionAlias` union
|
|
44
|
+
* (`types/config.ts`), mirroring `BuiltInSelectorAlias`. Defaulting members here
|
|
45
|
+
* would make publication permanently non-empty and destroy the degradation
|
|
46
|
+
* contract above (a non-augmenting consumer with custom aliases would flip from
|
|
47
|
+
* permissive to branded-rejection the day built-ins shipped).
|
|
48
|
+
*/
|
|
49
|
+
export interface Conditions {
|
|
50
|
+
}
|
|
51
|
+
declare const CONDS_BRAND: unique symbol;
|
|
52
|
+
declare const SELS_BRAND: unique symbol;
|
|
53
|
+
/**
|
|
54
|
+
* Phantom brand carried on `build()`'s system output, threading the accumulated
|
|
55
|
+
* registered condition (`C`) and custom selector (`S`) alias-key unions to the
|
|
56
|
+
* consumer's augmentation site. Type-only — never present at runtime.
|
|
57
|
+
*/
|
|
58
|
+
export interface RegistryBrand<C extends string = never, S extends string = never> {
|
|
59
|
+
readonly [CONDS_BRAND]?: C;
|
|
60
|
+
readonly [SELS_BRAND]?: S;
|
|
61
|
+
}
|
|
62
|
+
/** Extract the registered condition alias keys from a built system. */
|
|
63
|
+
export type ConditionsOf<Sys> = Sys extends RegistryBrand<infer C, string> ? C : never;
|
|
64
|
+
/** Extract the registered custom selector alias keys from a built system. */
|
|
65
|
+
export type SelectorsOf<Sys> = Sys extends RegistryBrand<string, infer S> ? S : never;
|
|
66
|
+
/**
|
|
67
|
+
* Branded rejection for a `_`-prefixed block key that is neither a registered
|
|
68
|
+
* condition alias, a registered selector alias, a built-in selector alias,
|
|
69
|
+
* nor a built-in condition alias.
|
|
70
|
+
* The type name carries the offending key; the `hint` states the remedy. Never
|
|
71
|
+
* a bare `never` arm — `never` produces a misleading "not assignable to
|
|
72
|
+
* 'undefined'" (design D9, measured).
|
|
73
|
+
*/
|
|
74
|
+
export interface UnknownConditionAlias<K extends string> {
|
|
75
|
+
readonly __unknownConditionAlias: K;
|
|
76
|
+
readonly hint: `"${K}" is not a registered condition or selector alias. Register it with .addConditions() / .addSelectors(), or use a raw "&…" selector or "@media|@container|@supports (…)" key.`;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Branded rejection for an `@`-prefixed block key that does not match an
|
|
80
|
+
* accepted at-rule shape (`@media …` / `@container …` / `@supports …`). The
|
|
81
|
+
* type name carries the offending key; the `hint` states the remedy.
|
|
82
|
+
*/
|
|
83
|
+
export interface UnknownAtRule<K extends string> {
|
|
84
|
+
readonly __unknownAtRule: K;
|
|
85
|
+
readonly hint: `"${K}" is not a valid at-rule block key. At-rule keys must begin with "@media ", "@container ", or "@supports " followed by a parenthesized/feature tail.`;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* SHALLOW at-rule key shapes (design D9): prefix + tail only. Deep query-grammar
|
|
89
|
+
* template literals are the repo's one known TS2589 zone (string-embedded
|
|
90
|
+
* unions) and stay out — Rust validates queries for real; the type layer stops
|
|
91
|
+
* at the prefix. The trailing space after each at-rule name is load-bearing: it
|
|
92
|
+
* rejects prefix typos (`@containr …`, `@medi …`) without a closed grammar.
|
|
93
|
+
*/
|
|
94
|
+
export type RawAtRuleKey = `@media ${string}` | `@container ${string}` | `@supports ${string}`;
|
|
95
|
+
export interface ConditionAlias {
|
|
96
|
+
/** Full at-rule condition string, e.g. `@media (prefers-reduced-motion: reduce)`. */
|
|
97
|
+
value: string;
|
|
98
|
+
/** Sort index for cascade ordering within a layer. */
|
|
99
|
+
order: number;
|
|
100
|
+
/** Condition kind, inferred from the at-rule prefix of `value`. */
|
|
101
|
+
kind: ConditionKind;
|
|
102
|
+
}
|
|
103
|
+
export type ConditionAliasMap = Record<string, ConditionAlias>;
|
|
104
|
+
/**
|
|
105
|
+
* Built-in condition aliases (design D8, increment 06).
|
|
106
|
+
*
|
|
107
|
+
* The Panda-compatible media-feature set: motion, print, orientation, contrast,
|
|
108
|
+
* and OS color-scheme preferences. Every alias is a pure `@media` feature query
|
|
109
|
+
* (`kind: 'media'`). Color-mode aliases (`_dark` / `_light`) are DELIBERATELY
|
|
110
|
+
* excluded — they are selector-kind (`[data-color-mode]`) surface owned by the
|
|
111
|
+
* `system-color-scheme` change, not media features.
|
|
112
|
+
*
|
|
113
|
+
* ORDER BAND (inc-03 full-pass): built-ins occupy a reserved band BELOW the user
|
|
114
|
+
* band — one slot of 10 per alias in table row order, 300–380. The user band
|
|
115
|
+
* starts at 500 (`mergeConditions` floors allocation at 490 → first user alias
|
|
116
|
+
* is 500), so built-ins and user registrations never interleave on collision.
|
|
117
|
+
* This mirrors the selector registry's real layout (built-ins 10–440, users
|
|
118
|
+
* 500+).
|
|
119
|
+
*
|
|
120
|
+
* The static type-level mirror is `BuiltInConditionAlias` in `types/config.ts`
|
|
121
|
+
* (added to `KnownUnderscoreKey`'s VALIDATING branch, exactly like
|
|
122
|
+
* `BuiltInSelectorAlias`). Built-ins are NOT members of the augmentable
|
|
123
|
+
* `Conditions` interface: interface members would make publication permanently
|
|
124
|
+
* non-empty, killing inc-04's graceful-degradation contract. The
|
|
125
|
+
* `types.test-d.tsx` drift positives keep the two in sync.
|
|
126
|
+
*/
|
|
127
|
+
export declare const BUILT_IN_CONDITIONS: ConditionAliasMap;
|
|
128
|
+
/**
|
|
129
|
+
* Infer the condition kind from an at-rule value's prefix (design D3).
|
|
130
|
+
* Throws (fail-loud, build time) when the value does not begin with a
|
|
131
|
+
* supported at-rule name. The compile-time complement is `addConditions()`'s
|
|
132
|
+
* value constraint (`@media`/`@container`/`@supports`-prefixed template
|
|
133
|
+
* literals); this runtime throw remains the backstop for untyped callers.
|
|
134
|
+
*/
|
|
135
|
+
export declare function inferConditionKind(value: string): ConditionKind;
|
|
136
|
+
/**
|
|
137
|
+
* Merge user-provided condition aliases with a base map.
|
|
138
|
+
*
|
|
139
|
+
* - User aliases override built-ins of the same name, preserving the existing
|
|
140
|
+
* order (mirrors `mergeSelectors`).
|
|
141
|
+
* - New aliases allocate orders CONTINUING from the highest existing order
|
|
142
|
+
* (floored at 490, so the first user alias lands at 500) rather than
|
|
143
|
+
* restarting at 500 each call — chained `.addConditions()` calls must not
|
|
144
|
+
* collide on order 500.
|
|
145
|
+
* - Fails loud when a condition alias name is already reserved by the SELECTOR
|
|
146
|
+
* alias registry (`reservedSelectorNames`): the two registries share the `_`
|
|
147
|
+
* namespace at the call site and their names MUST be disjoint, else a block
|
|
148
|
+
* key would be ambiguous. The error names the offending alias and both
|
|
149
|
+
* registries.
|
|
150
|
+
*/
|
|
151
|
+
export declare function mergeConditions(base: ConditionAliasMap, custom: Record<string, string>, reservedSelectorNames?: ReadonlySet<string>): ConditionAliasMap;
|
|
152
|
+
/** Get the sorted alias keys for deterministic cascade ordering. */
|
|
153
|
+
export declare function getSortedConditionKeys(map: ConditionAliasMap): string[];
|
|
154
|
+
/**
|
|
155
|
+
* Serialize the condition map for the extraction pipeline.
|
|
156
|
+
* Emits `alias → { value, order, kind }` in registry (cascade) order so the
|
|
157
|
+
* JSON is deterministic. Distinct in SHAPE from the selector map (which
|
|
158
|
+
* flattens to `alias → selector`) — the object entry is forward-compatible
|
|
159
|
+
* with a future multi-valued `values: string[]` property (design D3).
|
|
160
|
+
*/
|
|
161
|
+
export declare function serializeConditionMap(map: ConditionAliasMap): Record<string, ConditionAlias>;
|
|
162
|
+
export {};
|
|
163
|
+
//# sourceMappingURL=conditions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conditions.d.ts","sourceRoot":"","sources":["../src/conditions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,gFAAgF;AAChF,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,WAAW,GAAG,UAAU,CAAC;AAY/D;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,MAAM,WAAW,UAAU;CAAG;AAE9B,OAAO,CAAC,MAAM,WAAW,EAAE,OAAO,MAAM,CAAC;AACzC,OAAO,CAAC,MAAM,UAAU,EAAE,OAAO,MAAM,CAAC;AAExC;;;;GAIG;AACH,MAAM,WAAW,aAAa,CAC5B,CAAC,SAAS,MAAM,GAAG,KAAK,EACxB,CAAC,SAAS,MAAM,GAAG,KAAK;IAExB,QAAQ,CAAC,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC;IAC3B,QAAQ,CAAC,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC;CAC3B;AAED,uEAAuE;AACvE,MAAM,MAAM,YAAY,CAAC,GAAG,IAC1B,GAAG,SAAS,aAAa,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;AAEzD,6EAA6E;AAC7E,MAAM,MAAM,WAAW,CAAC,GAAG,IACzB,GAAG,SAAS,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;AAEzD;;;;;;;GAOG;AACH,MAAM,WAAW,qBAAqB,CAAC,CAAC,SAAS,MAAM;IACrD,QAAQ,CAAC,uBAAuB,EAAE,CAAC,CAAC;IACpC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,8KAA8K,CAAC;CACpM;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa,CAAC,CAAC,SAAS,MAAM;IAC7C,QAAQ,CAAC,eAAe,EAAE,CAAC,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,sJAAsJ,CAAC;CAC5K;AAED;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,GACpB,UAAU,MAAM,EAAE,GAClB,cAAc,MAAM,EAAE,GACtB,aAAa,MAAM,EAAE,CAAC;AAE1B,MAAM,WAAW,cAAc;IAC7B,qFAAqF;IACrF,KAAK,EAAE,MAAM,CAAC;IACd,sDAAsD;IACtD,KAAK,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,IAAI,EAAE,aAAa,CAAC;CACrB;AAED,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;AAE/D;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,mBAAmB,EAAE,iBA0CjC,CAAC;AAUF;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,aAAa,CAS/D;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,iBAAiB,EACvB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9B,qBAAqB,GAAE,WAAW,CAAC,MAAM,CAAqB,GAC7D,iBAAiB,CA0BnB;AAED,oEAAoE;AACpE,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,iBAAiB,GAAG,MAAM,EAAE,CAEvE;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,iBAAiB,GACrB,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAMhC"}
|