@promptctl/rich-js 0.26.0 → 0.28.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 (132) hide show
  1. package/README.md +1 -0
  2. package/THIRD-PARTY-NOTICES +42 -0
  3. package/dist/core/animation.d.ts +52 -15
  4. package/dist/core/animation.js +82 -37
  5. package/dist/core/ansi.d.ts +15 -8
  6. package/dist/core/ansi.js +106 -28
  7. package/dist/core/bidi-data.d.ts +14 -0
  8. package/dist/core/bidi-data.js +385 -0
  9. package/dist/core/bidi.d.ts +89 -0
  10. package/dist/core/bidi.js +615 -0
  11. package/dist/core/box.d.ts +9 -1
  12. package/dist/core/box.js +11 -0
  13. package/dist/core/cell-widths.d.ts +3 -2
  14. package/dist/core/cell-widths.js +93 -93
  15. package/dist/core/cells.d.ts +20 -0
  16. package/dist/core/cells.js +93 -5
  17. package/dist/core/color.d.ts +26 -18
  18. package/dist/core/color.js +37 -34
  19. package/dist/core/console.d.ts +26 -1
  20. package/dist/core/console.js +26 -6
  21. package/dist/core/control-codes.d.ts +61 -0
  22. package/dist/core/control-codes.js +157 -0
  23. package/dist/core/easing.js +6 -1
  24. package/dist/core/env.d.ts +6 -0
  25. package/dist/core/env.js +9 -1
  26. package/dist/core/export-html.js +4 -3
  27. package/dist/core/export-lines.d.ts +2 -0
  28. package/dist/core/export-lines.js +7 -3
  29. package/dist/core/export-svg.js +6 -5
  30. package/dist/core/json.js +9 -2
  31. package/dist/core/markup.js +4 -3
  32. package/dist/core/oklch.d.ts +2 -0
  33. package/dist/core/oklch.js +3 -3
  34. package/dist/core/orient.d.ts +68 -0
  35. package/dist/core/orient.js +224 -0
  36. package/dist/core/osc8.d.ts +12 -5
  37. package/dist/core/osc8.js +14 -7
  38. package/dist/core/pretty.js +22 -18
  39. package/dist/core/protocol.d.ts +53 -22
  40. package/dist/core/protocol.js +14 -6
  41. package/dist/core/render.d.ts +8 -3
  42. package/dist/core/render.js +19 -5
  43. package/dist/core/seal.d.ts +26 -0
  44. package/dist/core/seal.js +75 -0
  45. package/dist/core/segment.d.ts +9 -1
  46. package/dist/core/segment.js +11 -1
  47. package/dist/core/style.d.ts +29 -1
  48. package/dist/core/style.js +41 -5
  49. package/dist/core/text-order.d.ts +51 -0
  50. package/dist/core/text-order.js +50 -0
  51. package/dist/core/text.d.ts +10 -0
  52. package/dist/core/text.js +133 -87
  53. package/dist/core/timeline.js +6 -6
  54. package/dist/core/view.d.ts +23 -9
  55. package/dist/core/view.js +37 -12
  56. package/dist/core/wrap.d.ts +24 -6
  57. package/dist/core/wrap.js +82 -16
  58. package/dist/host/app.d.ts +48 -7
  59. package/dist/host/app.js +116 -11
  60. package/dist/host/index.d.ts +3 -2
  61. package/dist/host/index.js +1 -0
  62. package/dist/host/input.d.ts +35 -2
  63. package/dist/host/input.js +88 -4
  64. package/dist/host/terminal-colors.d.ts +18 -0
  65. package/dist/host/terminal-colors.js +46 -0
  66. package/dist/host/terminal-host.d.ts +2 -1
  67. package/dist/index.d.ts +10 -8
  68. package/dist/index.js +2 -2
  69. package/dist/renderables/change.d.ts +1 -3
  70. package/dist/renderables/change.js +4 -6
  71. package/dist/renderables/effect.d.ts +61 -60
  72. package/dist/renderables/effect.js +91 -81
  73. package/dist/renderables/effects.d.ts +37 -8
  74. package/dist/renderables/effects.js +193 -55
  75. package/dist/renderables/frames.d.ts +1 -10
  76. package/dist/renderables/frames.js +1 -16
  77. package/dist/renderables/glyph-effect.d.ts +12 -13
  78. package/dist/renderables/glyph-effect.js +34 -32
  79. package/dist/renderables/glyph-effects.d.ts +27 -8
  80. package/dist/renderables/glyph-effects.js +75 -7
  81. package/dist/renderables/lifecycle.d.ts +40 -32
  82. package/dist/renderables/lifecycle.js +87 -48
  83. package/dist/renderables/live.d.ts +3 -3
  84. package/dist/renderables/live.js +4 -4
  85. package/dist/renderables/markdown.js +4 -2
  86. package/dist/renderables/panel.js +6 -4
  87. package/dist/renderables/progress.d.ts +11 -4
  88. package/dist/renderables/progress.js +29 -17
  89. package/dist/renderables/progressBar.d.ts +3 -3
  90. package/dist/renderables/progressBar.js +16 -13
  91. package/dist/renderables/syntax.js +7 -2
  92. package/dist/renderables/table.js +2 -1
  93. package/dist/template-bindings/animation-funcs.d.ts +49 -18
  94. package/dist/template-bindings/animation-funcs.js +115 -32
  95. package/dist/template-bindings/index.d.ts +7 -5
  96. package/dist/template-bindings/index.js +8 -5
  97. package/dist/template-bindings/palette-funcs.d.ts +1 -1
  98. package/dist/template-bindings/palette-funcs.js +1 -1
  99. package/dist/themes/buildPalette.d.ts +15 -39
  100. package/dist/themes/buildPalette.js +196 -67
  101. package/dist/themes/colorMath.d.ts +10 -4
  102. package/dist/themes/colorMath.js +33 -1
  103. package/dist/themes/data/index.js +1 -4
  104. package/dist/themes/data/textual-ansi.js +0 -143
  105. package/dist/themes/data/types.d.ts +3 -1
  106. package/dist/themes/palette.d.ts +22 -6
  107. package/dist/themes/palette.js +13 -21
  108. package/dist/themes/ramp.d.ts +1 -1
  109. package/dist/themes/ramp.js +1 -1
  110. package/dist/themes/registry.d.ts +7 -10
  111. package/dist/themes/registry.js +9 -30
  112. package/dist/themes/terminalThemes.d.ts +4 -10
  113. package/dist/themes/terminalThemes.js +31 -46
  114. package/dist/themes/transpose.d.ts +5 -10
  115. package/dist/themes/transpose.js +13 -24
  116. package/dist/widgets/button.d.ts +1 -1
  117. package/dist/widgets/button.js +6 -6
  118. package/dist/widgets/checkbox.js +4 -4
  119. package/dist/widgets/dropdown.d.ts +1 -1
  120. package/dist/widgets/dropdown.js +7 -5
  121. package/dist/widgets/event-router.js +2 -2
  122. package/dist/widgets/ink.d.ts +5 -4
  123. package/dist/widgets/ink.js +6 -9
  124. package/dist/widgets/text-input.d.ts +1 -1
  125. package/dist/widgets/text-input.js +41 -32
  126. package/dist/widgets/themed-widget.js +3 -3
  127. package/dist/widgets/toggle.d.ts +1 -1
  128. package/dist/widgets/toggle.js +5 -5
  129. package/dist/widgets/types.d.ts +2 -2
  130. package/dist/widgets/widget-app.d.ts +3 -3
  131. package/dist/widgets/widget-app.js +5 -5
  132. package/package.json +5 -4
@@ -20,7 +20,7 @@
20
20
  * number the time is turned into first: a frame's stamp says when it changes
21
21
  * on the host's clock, which only `Timeline.run` can carry it back onto —
22
22
  * paused, never; backwards, at the hold it is about to drop under.
23
- * [LAW:single-enforcer] `drawing` is the one place the time, the timeline
23
+ * [LAW:single-enforcer] `drawn` is the one place the time, the timeline
24
24
  * and the reader's preference for motion are read, for every function.
25
25
  *
26
26
  * `frames <seconds> <frame> <seconds> <frame> …` is a `FrameSequence`
@@ -57,27 +57,62 @@
57
57
  * exact arity of typed slots, so a slot of the wrong kind fails at the gate.
58
58
  *
59
59
  * Every function says when what it drew changes, so a host paints a
60
- * template while it moves and not after. `frames` and `glyphs` stamp the
61
- * fragment they draw (`textFrameAt`), covering its own glyphs. A number or a
62
- * colour carries no cells to stamp, so `tween`, `loop` and `stepped` tell
63
- * their hold to the frame instead (`TemplateFrame.onHold`), as a placed
64
- * animation of no cells does (`RenderOptions.onHold`). Nothing here claims
65
- * a moment it does not know.
60
+ * template while it moves and not after — and says it only for what the
61
+ * output depends on. A number or a colour has no cells to carry a hold, and
62
+ * a fragment's cells lose theirs the moment it is flattened (`printf "%s"`),
63
+ * so no function stamps cells or tells the frame itself. Each marks its
64
+ * return with the hold its value is in (`marked`, the engine's evaluation
65
+ * provenance), and the engine carries the mark beside the value into every
66
+ * variable, dot and function the value passes through, reporting it where
67
+ * the output depends on it. `emitted` is where a template's cells take a
68
+ * hold: every fragment the template emits, stamped with the holds of the
69
+ * marked values its action read. `decided` is the route for a hold no cell
70
+ * can carry, told to the frame (`TemplateFrame.onHold`): a mark an
71
+ * `if`/`with`/`range` consumed, whichever arm it took, because the frame may
72
+ * show cells for it later and must say when without a cell to say it on, as
73
+ * a placed animation of no cells does (`placed`) — and, by the same rule, a
74
+ * mark a writing function or a throwing action read; `EngineConfig.decided`
75
+ * is the authority on when it is called. A mark bound somewhere nobody reads
76
+ * is reported nowhere, so a value a template computes and never draws or
77
+ * decides on tells the frame nothing (rich-animation-27tq.0gd). Early, never
78
+ * late: an emission may carry a hold it did not strictly need, and a hold
79
+ * the output depends on is never dropped. [LAW:single-enforcer]
80
+ *
81
+ * The functions and the two hooks go into the host's engine config together,
82
+ * by `withAnimation`: the functions wired without the hooks would compile,
83
+ * draw and never say they move. [LAW:no-silent-failure]
66
84
  */
67
- import { Animation, frameTimeOf, holdOf, shownAt } from "../core/animation.js";
85
+ import { marked } from "@promptctl/go-template-js";
86
+ import { Animation, frameTimeOf, holdOf, shownAt, stamp } from "../core/animation.js";
68
87
  import { Phase, beat, finiteTime, parseEase } from "../core/easing.js";
88
+ import { layerStyle } from "../core/style.js";
69
89
  import { Timeline } from "../core/timeline.js";
70
90
  import { tween } from "../core/tween.js";
71
91
  import { cellSeed } from "../renderables/effect.js";
72
92
  import { reglyphText } from "../renderables/glyph-effect.js";
73
93
  import { GLYPH_EFFECT_CATALOGUE } from "../renderables/glyph-effects.js";
74
- import { FrameSequence, textFrameAt } from "../renderables/frames.js";
94
+ import { FrameSequence } from "../renderables/frames.js";
75
95
  import { asColor } from "./color-funcs.js";
76
96
  import { asFragment } from "./helpers.js";
77
97
  /** The frame a renderable is drawn in, as the frame a template it evaluates there is drawn for. */
78
98
  export function templateFrame(options) {
79
99
  return { time: frameTimeOf(options), onHold: options.onHold, reducedMotion: options.reducedMotion, asciiOnly: options.asciiOnly };
80
100
  }
101
+ /**
102
+ * The mark a function puts on its return: the hold its value is in, and the
103
+ * frame it was drawn for, which is the one `decided` tells. Only the two
104
+ * hooks here read one; a consumer's own marks pass through them untouched,
105
+ * and a foreign token shaped like a `Hold` is not one.
106
+ * [LAW:types-are-the-program]
107
+ */
108
+ class HoldMark {
109
+ hold;
110
+ frame;
111
+ constructor(hold, frame) {
112
+ this.hold = hold;
113
+ this.frame = frame;
114
+ }
115
+ }
81
116
  /** The host's own clock as a timeline: host time 0 at animation time 0, at rate 1. */
82
117
  const HOST_CLOCK = Timeline.play(0);
83
118
  /**
@@ -127,36 +162,41 @@ function glyphEffect(name) {
127
162
  return GLYPH_EFFECTS[name];
128
163
  }
129
164
  /**
130
- * Register `frames`, `stepped`, `tween`, `loop` and `glyphs`, each drawn for
131
- * the frame `frame` reports when the template is evaluated — its time in
132
- * seconds, from whatever zero the host counts from — through
133
- * `options.timeline`, and each telling that frame when what it drew changes.
165
+ * `config` with `frames`, `stepped`, `tween`, `loop` and `glyphs` registered
166
+ * beside its own functions, each drawn for the frame `frame` reports when
167
+ * the template is evaluated — its time in seconds, from whatever zero the
168
+ * host counts from — through `options.timeline`, and each marking its
169
+ * return with the hold it is in there; and with the two hooks that carry
170
+ * those holds to the cells (`emitted`) and the frame (`decided`) installed
171
+ * over any `config` already has, which still hear everything they did. One
172
+ * call, so the functions and the hooks that report for them cannot be wired
173
+ * apart: the functions alone would compile, draw, and never say they move.
174
+ * [LAW:types-are-the-program] [LAW:no-silent-failure] [LAW:composability]
175
+ * A `config` that already has one of the five names is refused, which is
176
+ * also what a second `withAnimation` over its own result meets: its template
177
+ * calls would otherwise reach these with no word said.
178
+ *
134
179
  * The functions exist whatever `frame` returns, so a template compiles once
135
180
  * and is evaluated at every moment the host draws. In a frame that says
136
181
  * `reducedMotion` each draws its animation reduced (`Animation.reduced`): a
137
182
  * loop at its start, a one-shot cut from where it starts to where it arrives
138
183
  * when it starts.
139
184
  */
140
- export function animationFuncs(frame, options = {}) {
185
+ export function withAnimation(config, frame, options = {}) {
141
186
  const timeline = options.timeline ?? (() => HOST_CLOCK);
142
- // `animation` on this call's timeline, and the time of `at`, the frame it
143
- // is drawn in, read once a call so a value and its hold are one frame's.
144
- const drawing = (what, animation, at) => [Timeline.run(timeline(), animation), finiteTime(what, at.time)];
145
- // What a value animation shows now, under the reader's preference, its
146
- // hold told to the frame: a value has no cells to carry it.
147
- const now = (what, animation) => {
148
- const at = frame();
149
- const [drawn, t] = drawing(what, animation, at);
150
- const shown = shownAt(drawn, t, at);
151
- at.onHold?.(holdOf(shown, t));
152
- return shown.value;
187
+ // What `animation`, on this call's timeline, shows in the frame `at`,
188
+ // under the reader's preference, marked with how long for: the time is
189
+ // read once a call so a value and its hold are one frame's.
190
+ const drawn = (what, animation, at) => {
191
+ const t = finiteTime(what, at.time);
192
+ const shown = shownAt(Timeline.run(timeline(), animation), t, at);
193
+ return marked(shown.value, new HoldMark(holdOf(shown, t), at));
153
194
  };
154
195
  const startedAt = (start, animation) => Timeline.run(Timeline.play(start), animation);
155
196
  const framesFunc = {
156
197
  fn: ((...tail) => {
157
198
  const frames = pairs("frame", tail, asFragment).map(({ seconds, value }) => ({ seconds, text: value }));
158
- const at = frame();
159
- return textFrameAt(...drawing("frames", new FrameSequence(frames), at), at);
199
+ return drawn("frames", new FrameSequence(frames), frame());
160
200
  }),
161
201
  argTypes: ["float", "liftable"],
162
202
  arity: { kind: "alternating", minimum: 2 },
@@ -165,20 +205,20 @@ export function animationFuncs(frame, options = {}) {
165
205
  const steppedFunc = {
166
206
  fn: ((...tail) => {
167
207
  const steps = pairs("color", tail, (v) => asColor(v, "stepped"));
168
- return steps[now("stepped", beat(steps.map((step) => step.seconds)))].value.hex;
208
+ return drawn("stepped", Animation.map(beat(steps.map((s) => s.seconds)), (step) => steps[step].value.hex), frame());
169
209
  }),
170
210
  argTypes: ["float", "string"],
171
211
  arity: { kind: "alternating", minimum: 2 },
172
212
  returnType: "string",
173
213
  };
174
214
  const tweenFunc = {
175
- fn: ((seconds, ease, start, from, to) => now("tween", startedAt(start, tween(from, to, { seconds, ease: parseEase(ease) })))),
215
+ fn: ((seconds, ease, start, from, to) => drawn("tween", startedAt(start, tween(from, to, { seconds, ease: parseEase(ease) })), frame())),
176
216
  argTypes: ["float", "string", "float", "float", "float"],
177
217
  arity: { kind: "exact" },
178
218
  returnType: "float",
179
219
  };
180
220
  const loopFunc = {
181
- fn: ((seconds, start) => now("loop", startedAt(start, Animation.continuous(Phase.loop(seconds))))),
221
+ fn: ((seconds, start) => drawn("loop", startedAt(start, Animation.continuous(Phase.loop(seconds))), frame())),
182
222
  argTypes: ["float", "float"],
183
223
  arity: { kind: "exact" },
184
224
  returnType: "float",
@@ -191,11 +231,54 @@ export function animationFuncs(frame, options = {}) {
191
231
  const key = fragment.plain;
192
232
  const at = frame();
193
233
  const reglyphed = reglyphText(fragment, make({ z: cellSeed(key, 0, 0) }), { key, asciiOnly: at.asciiOnly === true });
194
- return textFrameAt(...drawing("glyphs", startedAt(start, reglyphed), at), at);
234
+ return drawn("glyphs", startedAt(start, reglyphed), at);
195
235
  }),
196
236
  argTypes: ["string", "float", "liftable"],
197
237
  arity: { kind: "exact" },
198
238
  returnType: "T",
199
239
  };
200
- return { frames: framesFunc, stepped: steppedFunc, tween: tweenFunc, loop: loopFunc, glyphs: glyphsFunc };
240
+ const funcs = { frames: framesFunc, stepped: steppedFunc, tween: tweenFunc, loop: loopFunc, glyphs: glyphsFunc };
241
+ const taken = Object.keys(funcs).filter((name) => Object.hasOwn(config.funcs ?? {}, name));
242
+ if (taken.length > 0) {
243
+ throw new RangeError(`withAnimation registers ${taken.join(", ")}, already in config.funcs`);
244
+ }
245
+ const holdMarks = (marks) => [...marks].filter((mark) => mark instanceof HoldMark);
246
+ // The holds among `marks` layered onto the fragment's base style, which an
247
+ // empty text still draws as its one piece of no width: `Style.add` keeps
248
+ // the later `lastChange` and the sooner `nextChange`, so cells that read
249
+ // two animated values are in the intersection of their holds, as two
250
+ // styles laid together are anywhere. A fragment no hold reaches is handed
251
+ // back as it came — the engine calls this for every emission — and one a
252
+ // hold reaches is a copy, since the engine may hand the one fragment a
253
+ // scope holds to more than one emission.
254
+ const held = (fragment, marks) => {
255
+ const holds = holdMarks(marks);
256
+ if (holds.length === 0)
257
+ return fragment;
258
+ const result = fragment.copy();
259
+ result.style = holds.reduce((style, mark) => layerStyle(style, stamp(mark.hold)), fragment.style);
260
+ return result;
261
+ };
262
+ const told = (marks) => {
263
+ for (const mark of holdMarks(marks))
264
+ mark.frame.onHold?.(mark.hold);
265
+ };
266
+ // The hooks `config` brought run first and hear everything they did; a
267
+ // consumer that stamps or listens on its own loses nothing to this binding.
268
+ const emitted = config.emitted ?? ((fragment) => fragment);
269
+ const decided = config.decided ?? (() => { });
270
+ return {
271
+ ...config,
272
+ funcs: { ...config.funcs, ...funcs },
273
+ emitted: (fragment, marks) => held(emitted(fragment, marks), marks),
274
+ // `finally`: a consumer hook that throws still leaves the frame told its holds.
275
+ decided: (marks) => {
276
+ try {
277
+ decided(marks);
278
+ }
279
+ finally {
280
+ told(marks);
281
+ }
282
+ },
283
+ };
201
284
  }
@@ -14,10 +14,12 @@
14
14
  * - `paletteFuncs(getPalette)` — `color` and `ramp`, the two functions that
15
15
  * resolve a palette name. This is the *only* registration that knows a
16
16
  * palette exists. [LAW:one-way-deps]
17
- * - `animationFuncs(frame)` — `frames`, `stepped`, `tween`, `loop` and
18
- * `glyphs`, drawn for a moment on a timeline. It is the only registration
19
- * that knows the template is evaluated at a time, and the time and the
20
- * timeline are the host's to hand in (`./animation-funcs.ts`).
17
+ * - `withAnimation(config, frame)` — the engine config with `frames`,
18
+ * `stepped`, `tween`, `loop` and `glyphs` registered, drawn for a moment on
19
+ * a timeline, and the two engine hooks their holds reach the frame through
20
+ * installed beside them. It is the only registration that knows the
21
+ * template is evaluated at a time, and the time and the timeline are the
22
+ * host's to hand in (`./animation-funcs.ts`).
21
23
  *
22
24
  * `createRichTextEngine()` wires up `richTextFuncs()` only — it cannot supply a
23
25
  * palette. Consumers that name theme colours call `paletteFuncs(getPalette)`
@@ -43,7 +45,7 @@ import type { RenderOptions } from "../core/protocol.js";
43
45
  import { type DrawnAt } from "./color-funcs.js";
44
46
  export { paletteFuncs } from "./palette-funcs.js";
45
47
  export { colorFuncs, readableOnFunc, type DrawnAt } from "./color-funcs.js";
46
- export { animationFuncs, templateFrame, type AnimationFuncsOptions, type TemplateFrame } from "./animation-funcs.js";
48
+ export { withAnimation, templateFrame, type AnimationOptions, type TemplateFrame } from "./animation-funcs.js";
47
49
  /**
48
50
  * Funcs registered by the rich-js binding — the colour sinks, the palette-free
49
51
  * colour math, text attributes, and the `link` cell-splitter. Nothing here
@@ -14,10 +14,12 @@
14
14
  * - `paletteFuncs(getPalette)` — `color` and `ramp`, the two functions that
15
15
  * resolve a palette name. This is the *only* registration that knows a
16
16
  * palette exists. [LAW:one-way-deps]
17
- * - `animationFuncs(frame)` — `frames`, `stepped`, `tween`, `loop` and
18
- * `glyphs`, drawn for a moment on a timeline. It is the only registration
19
- * that knows the template is evaluated at a time, and the time and the
20
- * timeline are the host's to hand in (`./animation-funcs.ts`).
17
+ * - `withAnimation(config, frame)` — the engine config with `frames`,
18
+ * `stepped`, `tween`, `loop` and `glyphs` registered, drawn for a moment on
19
+ * a timeline, and the two engine hooks their holds reach the frame through
20
+ * installed beside them. It is the only registration that knows the
21
+ * template is evaluated at a time, and the time and the timeline are the
22
+ * host's to hand in (`./animation-funcs.ts`).
21
23
  *
22
24
  * `createRichTextEngine()` wires up `richTextFuncs()` only — it cannot supply a
23
25
  * palette. Consumers that name theme colours call `paletteFuncs(getPalette)`
@@ -45,7 +47,7 @@ import { richTextStyleFuncs } from "./style-funcs.js";
45
47
  import { colorFuncs } from "./color-funcs.js";
46
48
  export { paletteFuncs } from "./palette-funcs.js";
47
49
  export { colorFuncs, readableOnFunc } from "./color-funcs.js";
48
- export { animationFuncs, templateFrame } from "./animation-funcs.js";
50
+ export { withAnimation, templateFrame } from "./animation-funcs.js";
49
51
  /**
50
52
  * Funcs registered by the rich-js binding — the colour sinks, the palette-free
51
53
  * colour math, text attributes, and the `link` cell-splitter. Nothing here
@@ -84,6 +86,7 @@ export function richTextFuncs(drawnAt) {
84
86
  export function createRichTextEngine() {
85
87
  return createEngine({
86
88
  fromString: (s) => new RichText(s),
89
+ isT: (v) => v instanceof RichText,
87
90
  toString: (rt) => rt.plain,
88
91
  funcs: richTextFuncs(),
89
92
  });
@@ -83,7 +83,7 @@ import type { Palette } from "../themes/palette.js";
83
83
  * @example
84
84
  * ```ts
85
85
  * const engine = createEngine({
86
- * fromString: (s) => new RichText(s),
86
+ * fromString: (s) => new RichText(s), isT: (v) => v instanceof RichText,
87
87
  * toString: (rt) => rt.plain,
88
88
  * funcs: {
89
89
  * ...richTextFuncs(),
@@ -111,7 +111,7 @@ function colorStops(tail, palette) {
111
111
  * @example
112
112
  * ```ts
113
113
  * const engine = createEngine({
114
- * fromString: (s) => new RichText(s),
114
+ * fromString: (s) => new RichText(s), isT: (v) => v instanceof RichText,
115
115
  * toString: (rt) => rt.plain,
116
116
  * funcs: {
117
117
  * ...richTextFuncs(),
@@ -1,5 +1,5 @@
1
1
  import { ColorRgba } from "../core/color.js";
2
- import { Palette } from "./palette.js";
2
+ import { Palette, type BaseRole, type PaletteRole, type PaletteRoles } from "./palette.js";
3
3
  /**
4
4
  * Base colors required to build a full semantic palette.
5
5
  * All are ColorRgba — callers construct from whatever source (hex, HSL, etc.).
@@ -14,44 +14,20 @@ export interface BaseColors {
14
14
  background: ColorRgba;
15
15
  foreground: ColorRgba;
16
16
  }
17
- type AccentKey = "primary" | "secondary" | "accent" | "success" | "warning" | "error";
18
- export declare const ACCENT_KEYS: readonly AccentKey[];
17
+ /** The eight roles every palette is given, and every role it may also state. */
18
+ export type StatedRoles = BaseColors & Partial<PaletteRoles>;
19
+ export declare const BASE_KEYS: readonly BaseRole[];
20
+ /** Every role of every palette, read off the rules. */
21
+ export declare const PALETTE_ROLES: readonly PaletteRole[];
19
22
  /**
20
- * Build a full semantic palette from base colors.
23
+ * Build a full semantic palette from its base colors and any other role it
24
+ * states: a stated role keeps its colour, and everything derived from it
25
+ * follows it, but for `text-*`, which is held to read on its `*-muted`. An
26
+ * authored theme is this with roles stated; see `RULES` for what a role
27
+ * not stated becomes.
21
28
  *
22
- * Derived entries follow Textual's formulas:
23
- * - `*-muted` = color blended 70% toward its opposite base role. Defined for
24
- * every accent AND for the two base roles themselves
25
- * (`foreground-muted` blends toward `background`,
26
- * `background-muted` blends toward `foreground`) — a caller
27
- * de-emphasizing body/structural text reaches for
28
- * `foreground-muted` the same way it reaches for
29
- * `primary-muted` to de-emphasize an accent.
30
- * - `text-*` = contrast text tinted 66% with the accent color, then held
31
- * to WCAG AA (4.5:1) on `*-muted` — the foreground for a
32
- * `*-muted` ground
33
- * - `on-*` = WCAG-correct contrast colour (black or white) for use as
34
- * foreground when the FULL accent is the background. Picked
35
- * by relative luminance — single source of truth so widgets
36
- * never need to invert / reverse fg/bg to get readable text.
37
- * - `surface` = background blended 5% toward foreground
38
- *
39
- * A translucent base colour is drawn onto `background` before anything is
40
- * derived from it, so `on-*` is chosen against the colour the accent is drawn
41
- * as and every derived entry is opaque.
42
- */
43
- export declare function buildPalette(name: string, dark: boolean, given: BaseColors): Palette;
44
- /**
45
- * A palette's `text-*`: its tint held to WCAG AA on `*-muted`, moved toward
46
- * the palette's own text side so it also reads on `background`, where widgets
47
- * draw it too. The muted floor is the promise: where no lightness on the text
48
- * side clears it, the colour that does is on the other side, and the
49
- * background pair is the one given up. Every bundled palette clears both
50
- * (test/themes/text-on-muted.test.ts).
51
- *
52
- * [LAW:single-enforcer] The one rule for the pair, for the palettes derived
53
- * here and the authored ones the registry hydrates: the tint is the theme's,
54
- * whether it reads is decided here.
29
+ * A translucent colour is drawn onto `background` before anything is derived
30
+ * from it, so `on-*` is chosen against the colour the accent is drawn as.
31
+ * `boost` alone stays a layer: it is what `panel` composites.
55
32
  */
56
- export declare function accentText(tint: ColorRgba, muted: ColorRgba, background: ColorRgba): ColorRgba;
57
- export {};
33
+ export declare function buildPalette(name: string, dark: boolean, given: StatedRoles): Palette;