@loomcli/core 0.2.0 → 0.4.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.
Files changed (51) hide show
  1. package/dist/application.d.ts +59 -34
  2. package/dist/application.js +162 -59
  3. package/dist/chain.d.ts +25 -6
  4. package/dist/chain.js +99 -15
  5. package/dist/command.d.ts +146 -42
  6. package/dist/command.js +642 -78
  7. package/dist/environment.d.ts +22 -0
  8. package/dist/environment.js +1 -0
  9. package/dist/errors.d.ts +31 -59
  10. package/dist/errors.js +49 -106
  11. package/dist/extension.d.ts +80 -20
  12. package/dist/extension.js +115 -30
  13. package/dist/globals.d.ts +14 -25
  14. package/dist/globals.js +10 -61
  15. package/dist/glyphs.generated.d.ts +464 -0
  16. package/dist/glyphs.generated.js +491 -0
  17. package/dist/host.js +2 -1
  18. package/dist/index.d.ts +15 -6
  19. package/dist/index.js +5 -2
  20. package/dist/inspect.d.ts +38 -4
  21. package/dist/inspect.js +69 -6
  22. package/dist/lanes.d.ts +26 -0
  23. package/dist/lanes.js +45 -0
  24. package/dist/output.d.ts +93 -15
  25. package/dist/output.js +307 -34
  26. package/dist/plugin.d.ts +32 -16
  27. package/dist/plugin.js +46 -18
  28. package/dist/rendering.d.ts +21 -0
  29. package/dist/rendering.js +72 -0
  30. package/dist/sequence.d.ts +41 -0
  31. package/dist/sequence.js +225 -0
  32. package/dist/style-ansi.d.ts +13 -0
  33. package/dist/style-ansi.js +306 -0
  34. package/dist/style-layout.d.ts +29 -0
  35. package/dist/style-layout.js +228 -0
  36. package/dist/style-resolve.d.ts +6 -0
  37. package/dist/style-resolve.js +26 -0
  38. package/dist/style-state.d.ts +14 -0
  39. package/dist/style-state.js +179 -0
  40. package/dist/style-wire.d.ts +31 -0
  41. package/dist/style-wire.js +201 -0
  42. package/dist/style.d.ts +86 -0
  43. package/dist/style.js +201 -0
  44. package/dist/theme.d.ts +3 -0
  45. package/dist/theme.js +22 -0
  46. package/dist/types.d.ts +174 -21
  47. package/dist/validation.d.ts +8 -1
  48. package/dist/validation.js +17 -2
  49. package/dist/view.d.ts +180 -0
  50. package/dist/view.js +307 -0
  51. package/package.json +2 -1
@@ -0,0 +1,201 @@
1
+ import { isPlainObject } from './facts.js';
2
+ import { glyphForms } from './glyphs.generated.js';
3
+ import { applyOperations, emptyAttributes } from './style-state.js';
4
+ import { close, colorFallbacks, escape, headerEnd, isColorName, modifiers, open, tokens, } from './style.js';
5
+ function isByte(value) {
6
+ return typeof value === 'number' && Number.isInteger(value) && value >= 0 && value <= 255;
7
+ }
8
+ function color(value) {
9
+ if (isColorName(value)) {
10
+ return value;
11
+ }
12
+ if (!Array.isArray(value)) {
13
+ return undefined;
14
+ }
15
+ if ((value.length === 2 || value.length === 3) && value[0] === 'ansi256' && isByte(value[1])) {
16
+ if (value.length === 2) {
17
+ return ['ansi256', value[1]];
18
+ }
19
+ if (!isPlainObject(value[2])) {
20
+ return undefined;
21
+ }
22
+ try {
23
+ return ['ansi256', value[1], colorFallbacks(value[2], false)];
24
+ }
25
+ catch {
26
+ return undefined;
27
+ }
28
+ }
29
+ if ((value.length === 4 || value.length === 5) &&
30
+ value[0] === 'rgb' &&
31
+ isByte(value[1]) &&
32
+ isByte(value[2]) &&
33
+ isByte(value[3])) {
34
+ if (value.length === 4) {
35
+ return ['rgb', value[1], value[2], value[3]];
36
+ }
37
+ if (!isPlainObject(value[4])) {
38
+ return undefined;
39
+ }
40
+ try {
41
+ return ['rgb', value[1], value[2], value[3], colorFallbacks(value[4], true)];
42
+ }
43
+ catch {
44
+ return undefined;
45
+ }
46
+ }
47
+ return undefined;
48
+ }
49
+ function operation(value, names) {
50
+ if (!Array.isArray(value)) {
51
+ return undefined;
52
+ }
53
+ if (value.length === 1 &&
54
+ (value[0] === 'reset' || value[0] === 'resetForeground' || value[0] === 'resetBackground')) {
55
+ return [value[0]];
56
+ }
57
+ if (value.length !== 2) {
58
+ return undefined;
59
+ }
60
+ if (value[0] === 'token' && typeof value[1] === 'string' && names.has(value[1])) {
61
+ return ['token', value[1]];
62
+ }
63
+ if (value[0] === 'modifier') {
64
+ const modifier = modifiers.find((name) => name === value[1]);
65
+ return modifier === undefined ? undefined : ['modifier', modifier];
66
+ }
67
+ if (value[0] === 'foreground' || value[0] === 'background') {
68
+ const parsed = color(value[1]);
69
+ return parsed === undefined ? undefined : [value[0], parsed];
70
+ }
71
+ return undefined;
72
+ }
73
+ function glyphName(value) {
74
+ return typeof value === 'string' && Object.hasOwn(glyphForms, value);
75
+ }
76
+ function parseHeader(text, names) {
77
+ let value = undefined;
78
+ try {
79
+ value = JSON.parse(text);
80
+ }
81
+ catch {
82
+ return undefined;
83
+ }
84
+ if (!Array.isArray(value)) {
85
+ return undefined;
86
+ }
87
+ if (value.length === 2 && value[0] === 'glyph' && glyphName(value[1])) {
88
+ return { kind: 'glyph', name: value[1] };
89
+ }
90
+ if (value.length === 3 &&
91
+ value[0] === 'pad' &&
92
+ typeof value[1] === 'number' &&
93
+ Number.isSafeInteger(value[1]) &&
94
+ value[1] >= 0 &&
95
+ (value[2] === 'left' || value[2] === 'right' || value[2] === 'center')) {
96
+ return { kind: 'pad', padding: { align: value[2], width: value[1] } };
97
+ }
98
+ if (value.length !== 2 || value[0] !== 'style' || !Array.isArray(value[1])) {
99
+ return undefined;
100
+ }
101
+ const operations = [];
102
+ for (const item of value[1]) {
103
+ const parsed = operation(item, names);
104
+ if (parsed === undefined) {
105
+ return undefined;
106
+ }
107
+ operations.push(parsed);
108
+ }
109
+ return { kind: 'style', operations };
110
+ }
111
+ /** Decodes framing once. Rejected headers are opaque, and nesting uses an explicit stack. */
112
+ function parseText(input, palette, mainGlyphs) {
113
+ const pieces = [];
114
+ const scopes = [];
115
+ const boundaries = new Map();
116
+ const stack = [];
117
+ const names = new Set([...tokens, ...palette.keys()]);
118
+ const root = { attributes: emptyAttributes, operations: [], parent: undefined };
119
+ let active = root;
120
+ function append(text) {
121
+ pieces.push(text);
122
+ // Positions are UTF-16 offsets, including both units of surrogate pairs.
123
+ // oxlint-disable-next-line typescript/prefer-for-of
124
+ for (let index = 0; index < text.length; index += 1) {
125
+ scopes.push(active);
126
+ }
127
+ }
128
+ function boundary(value) {
129
+ const at = scopes.length;
130
+ const current = boundaries.get(at);
131
+ if (current) {
132
+ current.push(value);
133
+ }
134
+ else {
135
+ boundaries.set(at, [value]);
136
+ }
137
+ }
138
+ function endFrame() {
139
+ const ended = stack.pop();
140
+ if (ended?.kind === 'style') {
141
+ active = ended.previous;
142
+ }
143
+ else if (ended?.kind === 'pad') {
144
+ boundary({ kind: 'close' });
145
+ }
146
+ }
147
+ for (let cursor = 0; cursor < input.length;) {
148
+ const character = input[cursor];
149
+ if (character === open) {
150
+ const end = input.indexOf(headerEnd, cursor + 1);
151
+ const candidate = end === -1 ? undefined : parseHeader(input.slice(cursor + 1, end), names);
152
+ if (candidate === undefined || (candidate.kind === 'glyph' && input[end + 1] !== close)) {
153
+ const until = end === -1 ? input.length : end + 1;
154
+ append(input.slice(cursor, until));
155
+ cursor = until;
156
+ continue;
157
+ }
158
+ cursor = end + 1;
159
+ if (candidate.kind === 'glyph') {
160
+ append(glyphForms[candidate.name][mainGlyphs ? 0 : 1]);
161
+ cursor += 1;
162
+ }
163
+ else if (candidate.kind === 'style') {
164
+ stack.push({ kind: 'style', previous: active });
165
+ active = {
166
+ attributes: applyOperations(active.attributes, candidate.operations, palette),
167
+ operations: candidate.operations,
168
+ parent: active,
169
+ };
170
+ }
171
+ else {
172
+ stack.push({ kind: 'pad' });
173
+ boundary({
174
+ attributes: active.attributes,
175
+ kind: 'open',
176
+ padding: candidate.padding,
177
+ scope: active,
178
+ });
179
+ }
180
+ continue;
181
+ }
182
+ if (character === close && stack.length > 0) {
183
+ endFrame();
184
+ cursor += 1;
185
+ continue;
186
+ }
187
+ if (character === escape && /^E00[0-3]$/u.test(input.slice(cursor + 1, cursor + 5))) {
188
+ const digits = input.slice(cursor + 1, cursor + 5);
189
+ append(String.fromCharCode(Number.parseInt(digits, 16)));
190
+ cursor += 5;
191
+ continue;
192
+ }
193
+ append(character ?? '');
194
+ cursor += 1;
195
+ }
196
+ while (stack.length > 0) {
197
+ endFrame();
198
+ }
199
+ return { boundaries, palette, root, scopes, text: pieces.join('') };
200
+ }
201
+ export { parseText };
@@ -0,0 +1,86 @@
1
+ import type { ApplicationEnvironment, RegisteredEnvironment } from './environment.js';
2
+ import type { Plugin, ThemeOf } from './plugin.js';
3
+ import type { Alignment } from './style-wire.js';
4
+ /** Concrete terminal foregrounds; backgrounds use the same palette indices. */
5
+ declare const colors: {
6
+ black: number;
7
+ blue: number;
8
+ brightBlack: number;
9
+ brightBlue: number;
10
+ brightCyan: number;
11
+ brightGreen: number;
12
+ brightMagenta: number;
13
+ brightRed: number;
14
+ brightWhite: number;
15
+ brightYellow: number;
16
+ cyan: number;
17
+ green: number;
18
+ magenta: number;
19
+ red: number;
20
+ white: number;
21
+ yellow: number;
22
+ };
23
+ type Ansi16Color = keyof typeof colors;
24
+ type ColorName = Ansi16Color;
25
+ interface ColorFallbacks {
26
+ readonly ansi256?: number | undefined;
27
+ readonly ansi16?: Ansi16Color | undefined;
28
+ }
29
+ type Ansi256Fallbacks = Pick<ColorFallbacks, 'ansi16'>;
30
+ type RgbArguments = [
31
+ red: number,
32
+ green: number,
33
+ blue: number,
34
+ fallbacks?: ColorFallbacks | undefined
35
+ ];
36
+ type Color = ColorName | readonly ['rgb', number, number, number, ColorFallbacks?] | readonly ['ansi256', number, Ansi256Fallbacks?];
37
+ declare const modifiers: readonly ['bold', 'faint', 'italic', 'underline', 'inverse', 'hidden', 'strikethrough', 'overline'];
38
+ type Modifier = (typeof modifiers)[number];
39
+ declare const tokens: readonly ['dim', 'primary', 'highlight', 'success', 'warning', 'error', 'info'];
40
+ type CoreToken = (typeof tokens)[number];
41
+ type Reset = 'reset' | 'resetForeground' | 'resetBackground';
42
+ type Operation = readonly ['foreground' | 'background', Color] | readonly ['modifier', Modifier] | readonly [Reset] | readonly ['token', string];
43
+ declare const semanticStyle: unique symbol;
44
+ type Style<Names extends string = CoreToken, Semantic extends boolean = false> = {
45
+ (text: string): string;
46
+ readonly [semanticStyle]: Semantic;
47
+ readonly escape: (text: string) => string;
48
+ readonly hex: (color: string, fallbacks?: ColorFallbacks) => Style<Names, Semantic>;
49
+ readonly bgHex: (color: string, fallbacks?: ColorFallbacks) => Style<Names, Semantic>;
50
+ readonly rgb: (...args: RgbArguments) => Style<Names, Semantic>;
51
+ readonly bgRgb: (...args: RgbArguments) => Style<Names, Semantic>;
52
+ readonly ansi256: (index: number, fallbacks?: Ansi256Fallbacks) => Style<Names, Semantic>;
53
+ readonly bgAnsi256: (index: number, fallbacks?: Ansi256Fallbacks) => Style<Names, Semantic>;
54
+ } & {
55
+ readonly [Name in ColorName | `bg${Capitalize<ColorName>}` | Modifier | Reset]: Style<Names, Semantic>;
56
+ } & {
57
+ readonly [Name in Names]: Style<Names, true>;
58
+ };
59
+ type ThemeNames<Contributor> = Contributor extends Plugin ? keyof ThemeOf<Contributor> & string : never;
60
+ type EnvironmentStyle<Environment extends ApplicationEnvironment<unknown, readonly Plugin[]>> = Style<CoreToken | ThemeNames<Environment['plugins'][number]>>;
61
+ type ContextualStyle = EnvironmentStyle<RegisteredEnvironment>;
62
+ type ConcreteStyle = Style;
63
+ type ThemeMapping = Readonly<Record<string, ConcreteStyle | undefined>>;
64
+ type ThemeConstraint<Mapping> = {
65
+ readonly [Key in keyof Mapping]: Key extends Exclude<keyof Style, CoreToken> | (typeof reservedCallableNames)[number] ? never : Mapping[Key];
66
+ };
67
+ declare const open = "\uE000";
68
+ declare const headerEnd = "\uE001";
69
+ declare const close = "\uE002";
70
+ declare const escape = "\uE003";
71
+ declare const chains: WeakMap<object, readonly Operation[]>;
72
+ declare const reservedCallableNames: readonly ['apply', 'arguments', 'bind', 'call', 'caller', 'constructor', 'length', 'name', 'prototype', 'toString', 'toLocaleString', 'valueOf', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', '__proto__', '__defineGetter__', '__defineSetter__', '__lookupGetter__', '__lookupSetter__', 'then'];
73
+ declare const reservedStyleNames: Set<string>;
74
+ declare function frame(header: readonly unknown[], body: string): string;
75
+ declare function escapeText(text: string): string;
76
+ /** Copies options at the helper boundary; the wire parser uses the same value rules. */
77
+ declare function colorFallbacks(value: unknown, allow256: boolean): ColorFallbacks;
78
+ /** Properties are generated from the same catalogs the wire parser validates. */
79
+ declare function createStyle(names?: ReadonlySet<string>): ContextualStyle;
80
+ declare function isColorName(value: unknown): value is ColorName;
81
+ declare function pad(text: string, minimumWidth: number, options?: {
82
+ align?: Alignment;
83
+ }): string;
84
+ declare const style: Style;
85
+ export type { Ansi16Color, Ansi256Fallbacks, ColorFallbacks, ContextualStyle, Color, ColorName, ConcreteStyle, CoreToken, Modifier, Operation, Style, ThemeConstraint, ThemeMapping, };
86
+ export { colorFallbacks, chains, close, colors, createStyle, escape, escapeText, frame, headerEnd, isColorName, modifiers, open, pad, reservedStyleNames, style, tokens, };
package/dist/style.js ADDED
@@ -0,0 +1,201 @@
1
+ import { isPlainObject } from './facts.js';
2
+ /** Concrete terminal foregrounds; backgrounds use the same palette indices. */
3
+ const colors = {
4
+ black: 0,
5
+ blue: 4,
6
+ brightBlack: 8,
7
+ brightBlue: 12,
8
+ brightCyan: 14,
9
+ brightGreen: 10,
10
+ brightMagenta: 13,
11
+ brightRed: 9,
12
+ brightWhite: 15,
13
+ brightYellow: 11,
14
+ cyan: 6,
15
+ green: 2,
16
+ magenta: 5,
17
+ red: 1,
18
+ white: 7,
19
+ yellow: 3,
20
+ };
21
+ const modifiers = [
22
+ 'bold',
23
+ 'faint',
24
+ 'italic',
25
+ 'underline',
26
+ 'inverse',
27
+ 'hidden',
28
+ 'strikethrough',
29
+ 'overline',
30
+ ];
31
+ const tokens = ['dim', 'primary', 'highlight', 'success', 'warning', 'error', 'info'];
32
+ const open = '\uE000';
33
+ const headerEnd = '\uE001';
34
+ const close = '\uE002';
35
+ const escape = '\uE003';
36
+ const chains = new WeakMap();
37
+ const reservedCallableNames = [
38
+ 'apply',
39
+ 'arguments',
40
+ 'bind',
41
+ 'call',
42
+ 'caller',
43
+ 'constructor',
44
+ 'length',
45
+ 'name',
46
+ 'prototype',
47
+ 'toString',
48
+ 'toLocaleString',
49
+ 'valueOf',
50
+ 'hasOwnProperty',
51
+ 'isPrototypeOf',
52
+ 'propertyIsEnumerable',
53
+ '__proto__',
54
+ '__defineGetter__',
55
+ '__defineSetter__',
56
+ '__lookupGetter__',
57
+ '__lookupSetter__',
58
+ 'then',
59
+ ];
60
+ const reservedStyleNames = new Set([
61
+ ...Object.keys(colors),
62
+ ...Object.keys(colors).map((name) => `bg${name.charAt(0).toUpperCase()}${name.slice(1)}`),
63
+ ...modifiers,
64
+ 'reset',
65
+ 'resetForeground',
66
+ 'resetBackground',
67
+ 'hex',
68
+ 'bgHex',
69
+ 'rgb',
70
+ 'bgRgb',
71
+ 'ansi256',
72
+ 'bgAnsi256',
73
+ 'escape',
74
+ ...reservedCallableNames,
75
+ ]);
76
+ function textOnly(text) {
77
+ if (typeof text !== 'string') {
78
+ throw new TypeError('Style helpers require a string.');
79
+ }
80
+ return text;
81
+ }
82
+ function frame(header, body) {
83
+ const encoded = JSON.stringify(header).replace(/[\uE000-\uE003]/gu, (character) => `\\u${character.charCodeAt(0).toString(16)}`);
84
+ return `${open}${encoded}${headerEnd}${body}${close}`;
85
+ }
86
+ function escapeText(text) {
87
+ return textOnly(text).replace(/[\uE000-\uE003]/gu, (character) => `${escape}${character.charCodeAt(0).toString(16).toUpperCase()}`);
88
+ }
89
+ function byte(value) {
90
+ if (!Number.isInteger(value) || value < 0 || value > 255) {
91
+ throw new RangeError('Color channels and palette indices must be integers from 0 through 255.');
92
+ }
93
+ return value;
94
+ }
95
+ /** Copies options at the helper boundary; the wire parser uses the same value rules. */
96
+ function colorFallbacks(value, allow256) {
97
+ if (value === undefined) {
98
+ return {};
99
+ }
100
+ if (!isPlainObject(value) ||
101
+ Reflect.ownKeys(value).some((key) => key !== 'ansi16' && !(allow256 && key === 'ansi256'))) {
102
+ throw new TypeError('Color fallbacks must be a plain object with supported depth names.');
103
+ }
104
+ const ansi16 = value.ansi16;
105
+ const ansi256 = value.ansi256;
106
+ if (ansi16 !== undefined && !isColorName(ansi16)) {
107
+ throw new TypeError('The ansi16 fallback must be a terminal foreground color name.');
108
+ }
109
+ if (ansi256 !== undefined &&
110
+ (typeof ansi256 !== 'number' || !Number.isInteger(ansi256) || ansi256 < 0 || ansi256 > 255)) {
111
+ throw new RangeError('The ansi256 fallback must be an integer from 0 through 255.');
112
+ }
113
+ return {
114
+ ...(ansi16 === undefined ? {} : { ansi16 }),
115
+ ...(ansi256 === undefined ? {} : { ansi256 }),
116
+ };
117
+ }
118
+ function rgbColor([red, green, blue, fallbacks]) {
119
+ const rgb = ['rgb', byte(red), byte(green), byte(blue)];
120
+ const copied = colorFallbacks(fallbacks, true);
121
+ return Object.keys(copied).length === 0 ? rgb : [...rgb, copied];
122
+ }
123
+ function indexedColor(index, fallbacks) {
124
+ const color = ['ansi256', byte(index)];
125
+ const copied = colorFallbacks(fallbacks, false);
126
+ return Object.keys(copied).length === 0 ? color : [...color, copied];
127
+ }
128
+ function hexColor(value, fallbacks) {
129
+ if (typeof value !== 'string' || !/^#(?:[\da-f]{3}|[\da-f]{6})$/iu.test(value)) {
130
+ throw new TypeError('Hex colors must use #RGB or #RRGGBB.');
131
+ }
132
+ const full = value.length === 4
133
+ ? value.slice(1).replace(/[\da-f]/giu, (digit) => digit + digit)
134
+ : value.slice(1);
135
+ return rgbColor([
136
+ Number.parseInt(full.slice(0, 2), 16),
137
+ Number.parseInt(full.slice(2, 4), 16),
138
+ Number.parseInt(full.slice(4, 6), 16),
139
+ fallbacks,
140
+ ]);
141
+ }
142
+ /** Properties are generated from the same catalogs the wire parser validates. */
143
+ function createStyle(names = new Set(tokens)) {
144
+ function chain(operations) {
145
+ const callable = (text) => frame(['style', operations], textOnly(text));
146
+ const next = (operation) => chain([...operations, operation]);
147
+ const helpers = {
148
+ ansi256: (index, fallbacks) => next(['foreground', indexedColor(index, fallbacks)]),
149
+ bgAnsi256: (index, fallbacks) => next(['background', indexedColor(index, fallbacks)]),
150
+ bgHex: (value, fallbacks) => next(['background', hexColor(value, fallbacks)]),
151
+ bgRgb: (...args) => next(['background', rgbColor(args)]),
152
+ escape: escapeText,
153
+ hex: (value, fallbacks) => next(['foreground', hexColor(value, fallbacks)]),
154
+ rgb: (...args) => next(['foreground', rgbColor(args)]),
155
+ };
156
+ const properties = new Map(Object.entries(helpers).map(([name, helper]) => [name, () => helper]));
157
+ for (const name of Object.keys(colors)) {
158
+ if (isColorName(name)) {
159
+ properties.set(name, () => next(['foreground', name]));
160
+ properties.set(`bg${name.charAt(0).toUpperCase()}${name.slice(1)}`, () => next(['background', name]));
161
+ }
162
+ }
163
+ for (const name of modifiers) {
164
+ properties.set(name, () => next(['modifier', name]));
165
+ }
166
+ for (const name of ['reset', 'resetForeground', 'resetBackground']) {
167
+ properties.set(name, () => next([name]));
168
+ }
169
+ for (const name of names) {
170
+ properties.set(name, () => next(['token', name]));
171
+ }
172
+ const value = new Proxy(callable, {
173
+ get: (target, property, receiver) => typeof property === 'string' && properties.has(property)
174
+ ? properties.get(property)?.()
175
+ : Reflect.get(target, property, receiver),
176
+ });
177
+ chains.set(value, operations);
178
+ Object.freeze(value);
179
+ // Last resort: no typed path exists from runtime-generated callable properties to mapped keys.
180
+ // It holds because the catalogs above supply every declared helper and token; chains retain operations for validation.
181
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
182
+ return value;
183
+ }
184
+ return chain([]);
185
+ }
186
+ function isColorName(value) {
187
+ return typeof value === 'string' && Object.hasOwn(colors, value);
188
+ }
189
+ function pad(text, minimumWidth, options) {
190
+ textOnly(text);
191
+ if (!Number.isSafeInteger(minimumWidth) || minimumWidth < 0) {
192
+ throw new RangeError('Padding width must be a nonnegative safe integer.');
193
+ }
194
+ const align = options?.align ?? 'left';
195
+ if (align !== 'left' && align !== 'right' && align !== 'center') {
196
+ throw new TypeError('Padding alignment must be left, right, or center.');
197
+ }
198
+ return frame(['pad', minimumWidth, align], text);
199
+ }
200
+ const style = createStyle();
201
+ export { colorFallbacks, chains, close, colors, createStyle, escape, escapeText, frame, headerEnd, isColorName, modifiers, open, pad, reservedStyleNames, style, tokens, };
@@ -0,0 +1,3 @@
1
+ import type { Palette } from './style-state.js';
2
+ declare function buildTheme(value: unknown, identity: string): Palette;
3
+ export { buildTheme };
package/dist/theme.js ADDED
@@ -0,0 +1,22 @@
1
+ import { DeclarationError } from './errors.js';
2
+ import { isPlainObject } from './facts.js';
3
+ import { chains, reservedStyleNames } from './style.js';
4
+ function buildTheme(value, identity) {
5
+ if (!isPlainObject(value)) {
6
+ throw new DeclarationError(`Plugin "${identity}" must declare theme as a mapping of names to concrete style chains.`);
7
+ }
8
+ const palette = new Map();
9
+ for (const [name, chain] of Object.entries(value)) {
10
+ if (reservedStyleNames.has(name)) {
11
+ throw new DeclarationError(`Plugin "${identity}" theme name "${name}" shadows a built-in style member.`);
12
+ }
13
+ const operations = typeof chain === 'function' ? chains.get(chain) : undefined;
14
+ if (chain !== undefined &&
15
+ (operations === undefined || operations.some((entry) => entry[0] === 'token'))) {
16
+ throw new DeclarationError(`Plugin "${identity}" theme mapping "${name}" must be an unapplied concrete style chain without semantic tokens.`);
17
+ }
18
+ palette.set(name, operations ?? []);
19
+ }
20
+ return palette;
21
+ }
22
+ export { buildTheme };