@valbuild/shared 0.128.0 → 0.130.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/CHANGELOG.md CHANGED
@@ -1,5 +1,76 @@
1
1
  # @valbuild/shared
2
2
 
3
+ ## 0.130.0
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [[`8425378`](https://github.com/valbuild/val/commit/8425378c315ea46b5d822f1130b633e0449ff1b0), [`473a185`](https://github.com/valbuild/val/commit/473a185f70351b44388f3bc1852649e2c1dbe001), [`64f0de3`](https://github.com/valbuild/val/commit/64f0de339b8621cb5a6c422dfe55cae5b2bbe2a0)]:
8
+ - @valbuild/core@0.130.0
9
+
10
+ ## 0.129.0
11
+
12
+ ### Minor Changes
13
+
14
+ - [#655](https://github.com/valbuild/val/pull/655) [`7d34ecc`](https://github.com/valbuild/val/commit/7d34ecce787a0709025ae7b4764cb6c3ad1f766b) Thanks [@freekh](https://github.com/freekh)! - A project can now make Val Studio look like its own.
15
+
16
+ `s.settings()` has a new `theme` section, edited under **Settings → Appearance**
17
+ in the Studio. It is content like everything else: edited as a draft, shown in
18
+ the publish diff, and the same for everyone working on the project.
19
+
20
+ ```ts
21
+ export default c.define("/settings.val.ts", s.settings(), {
22
+ theme: {
23
+ accent: "#2563eb",
24
+ radius: "tight",
25
+ mode: "light",
26
+ },
27
+ });
28
+ ```
29
+
30
+ **`accent` is one hex, and it restyles the whole chrome** — Publish, the active
31
+ rail item, focus rings, switches, the caret in a rich text field, and the
32
+ outlines the canvas draws around editable elements on your own page. Any colour
33
+ is allowed, not a list of approved ones, because what the accent replaces is a
34
+ ten-step ramp that is _generated_ from it: each step keeps the lightness of the
35
+ step it replaces and changes only the hue. WCAG contrast is almost entirely a
36
+ function of lightness, so every foreground/background pair the chrome renders
37
+ stays at AA — which is asserted across the hue circle, pure black and a
38
+ saturated yellow included, rather than argued for. One value drives both light
39
+ and dark mode, since the semantic tokens pick different steps of the ramp in
40
+ each.
41
+
42
+ **`radius`** is `square`, `tight`, `default` or `soft`, and moves every corner
43
+ in the Studio.
44
+
45
+ The accent also moves the outlines the canvas draws around editable elements on
46
+ your own page. Those are drawn inside your document, which has none of Val's
47
+ stylesheet, so the colour is sent to the page over the canvas protocol — which
48
+ means a project on an older `@valbuild/next` or `@valbuild/tanstack` than its
49
+ Studio keeps Val's green there until it upgrades, rather than breaking.
50
+
51
+ **`mode`** is the mode the Studio opens in for an editor who has never picked
52
+ one. It never overrides an editor who has — that choice stays theirs, per
53
+ person and per browser, behind the account button.
54
+
55
+ Every field is optional, and unset means Val's own look, so an existing
56
+ settings module needs no change.
57
+
58
+ Two smaller fixes that came with it:
59
+
60
+ - **A settings change is its own entry in the publish diff.** Editing the
61
+ assistant's tone used to collapse the entire settings module into one change
62
+ card labelled "Settings", because the patch that writes a settings field is
63
+ an `add`, and `PatchSets` had no case for a settings section — so it gave up
64
+ and grouped the whole module. Two unrelated settings edits now show as two
65
+ changes, each under the name the panel gives it.
66
+ - **The Val mark keeps its green.** It named a step of the brand ramp, so it
67
+ would have recoloured along with a project's accent. It has its own token now.
68
+
69
+ ### Patch Changes
70
+
71
+ - Updated dependencies [[`7d34ecc`](https://github.com/valbuild/val/commit/7d34ecce787a0709025ae7b4764cb6c3ad1f766b), [`0c351c4`](https://github.com/valbuild/val/commit/0c351c4f97ae7f09772eab0e856ae69821b803b7)]:
72
+ - @valbuild/core@0.129.0
73
+
3
74
  ## 0.128.0
4
75
 
5
76
  ### Patch Changes
@@ -2,11 +2,13 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var getSitemapTree = require('../../dist/getSitemapTree-d1977d3e.cjs.dev.js');
5
+ var getSitemapTree = require('../../dist/getSitemapTree-99c97c62.cjs.dev.js');
6
6
  require('@valbuild/core');
7
7
 
8
8
 
9
9
 
10
+ exports.DEFAULT_CANVAS_SELECTION = getSitemapTree.DEFAULT_CANVAS_SELECTION;
11
+ exports.DEFAULT_CANVAS_SELECTION_SOFT = getSitemapTree.DEFAULT_CANVAS_SELECTION_SOFT;
10
12
  exports.VAL_AI_SESSION_STORAGE_KEY = getSitemapTree.VAL_AI_SESSION_STORAGE_KEY;
11
13
  exports.VAL_CANVAS_MESSAGE = getSitemapTree.VAL_CANVAS_MESSAGE;
12
14
  exports.VAL_CANVAS_PARAM = getSitemapTree.VAL_CANVAS_PARAM;
@@ -2,11 +2,13 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var getSitemapTree = require('../../dist/getSitemapTree-b320d033.cjs.prod.js');
5
+ var getSitemapTree = require('../../dist/getSitemapTree-99839b02.cjs.prod.js');
6
6
  require('@valbuild/core');
7
7
 
8
8
 
9
9
 
10
+ exports.DEFAULT_CANVAS_SELECTION = getSitemapTree.DEFAULT_CANVAS_SELECTION;
11
+ exports.DEFAULT_CANVAS_SELECTION_SOFT = getSitemapTree.DEFAULT_CANVAS_SELECTION_SOFT;
10
12
  exports.VAL_AI_SESSION_STORAGE_KEY = getSitemapTree.VAL_AI_SESSION_STORAGE_KEY;
11
13
  exports.VAL_CANVAS_MESSAGE = getSitemapTree.VAL_CANVAS_MESSAGE;
12
14
  exports.VAL_CANVAS_PARAM = getSitemapTree.VAL_CANVAS_PARAM;
@@ -1,2 +1,2 @@
1
- export { b as VAL_AI_SESSION_STORAGE_KEY, d as VAL_CANVAS_MESSAGE, c as VAL_CANVAS_PARAM, V as VAL_CONFIG_SESSION_STORAGE_KEY, a as VAL_THEME_SESSION_STORAGE_KEY, g as getNextAppRouterSourceFolder, l as getPageRouterSitemapTree, j as getPageRouterSourceFolder, m as getPatternFromModuleFilePath, h as getTanStackRouterSourceFolder, k as isTanStackRoutesFolder, f as isValCanvasFrame, i as isValCanvasPageMessage, e as isValCanvasStudioMessage, p as parseRoutePattern, w as withValCanvasParam } from '../../dist/getSitemapTree-6777bdfa.esm.js';
1
+ export { D as DEFAULT_CANVAS_SELECTION, e as DEFAULT_CANVAS_SELECTION_SOFT, b as VAL_AI_SESSION_STORAGE_KEY, d as VAL_CANVAS_MESSAGE, c as VAL_CANVAS_PARAM, V as VAL_CONFIG_SESSION_STORAGE_KEY, a as VAL_THEME_SESSION_STORAGE_KEY, h as getNextAppRouterSourceFolder, m as getPageRouterSitemapTree, k as getPageRouterSourceFolder, n as getPatternFromModuleFilePath, j as getTanStackRouterSourceFolder, l as isTanStackRoutesFolder, g as isValCanvasFrame, i as isValCanvasPageMessage, f as isValCanvasStudioMessage, p as parseRoutePattern, w as withValCanvasParam } from '../../dist/getSitemapTree-878d3888.esm.js';
2
2
  import '@valbuild/core';
@@ -1,4 +1,5 @@
1
- import { SerializedSchema, Json } from "@valbuild/core";
1
+ import { SerializedSchema } from "@valbuild/core";
2
+ import { JSONValue } from "@valbuild/core/patch";
2
3
  /**
3
4
  * What `emptyOf` cannot read off a serialized schema.
4
5
  *
@@ -22,4 +23,16 @@ export type EmptyOfContext = {
22
23
  */
23
24
  selectedLocale?: string;
24
25
  };
25
- export declare function emptyOf(schema: SerializedSchema, context?: EmptyOfContext): Json;
26
+ /**
27
+ * The empty value for a schema — the thing a "create this" affordance writes.
28
+ *
29
+ * Returns `JSONValue`, the MUTABLE shape, rather than `Json`. Every value here
30
+ * is built fresh on the way out — `Object.fromEntries`, a new array, a
31
+ * primitive — and none of it aliases anything a caller could then mutate out
32
+ * from under someone. Saying `Json` instead made the type readonly, which is
33
+ * a claim about sharing that is not true of anything this returns, and left
34
+ * every caller asserting `as JSONValue` at the point of writing a patch. A
35
+ * `JSONValue` is still assignable to `Json`, so a reader of the value loses
36
+ * nothing.
37
+ */
38
+ export declare function emptyOf(schema: SerializedSchema, context?: EmptyOfContext): JSONValue;
@@ -31,3 +31,4 @@ export * from "./search/searchIndex.js";
31
31
  export * from "./search/traverseSchemaSource.js";
32
32
  export * from "./search/getFilenameFromRef.js";
33
33
  export * from "./search/sourcePath.js";
34
+ export * from "./theme/accentRamp.js";
@@ -0,0 +1,120 @@
1
+ /**
2
+ * The Studio's chrome, generated from one colour.
3
+ *
4
+ * `s.settings()`'s `theme.accent` is a single hex, and what it has to replace is
5
+ * a TEN-STEP RAMP: `index.css` declares `--colors-brand-green-100` through
6
+ * `-1000` and every brand token points into it, picking different steps in light
7
+ * and in dark (a tinted surface is step 200 in light and 800 in dark; the text
8
+ * on it is 1000 and 100). So the accent is not a colour to place, it is a ramp
9
+ * to build — which is also why one accent drives both modes with nothing to keep
10
+ * in sync.
11
+ *
12
+ * ## Why an arbitrary hex is safe
13
+ *
14
+ * The obvious worry is `contrast.test.ts`, which holds every foreground /
15
+ * background pair the chrome renders to WCAG AA. A hex typed by an editor never
16
+ * goes near that test.
17
+ *
18
+ * It holds anyway, because of how the ramp is built: **green's lightness at each
19
+ * step is reused unchanged, and only the hue moves.** WCAG relative luminance is
20
+ * almost entirely a function of lightness, so a ramp that keeps green's L
21
+ * profile keeps green's contrast relationships. Chroma is scaled to the accent's
22
+ * own saturation and then reduced — never L — until each step fits sRGB.
23
+ *
24
+ * "Almost entirely" is doing real work in that sentence: at equal OKLCH
25
+ * lightness a saturated yellow has a higher relative luminance than a blue, so
26
+ * the ratios do move a little. {@link BRAND_CONTRAST_PAIRS} is the list of pairs
27
+ * that have to survive it, and `accentRamp.test.ts` runs them over the hue
28
+ * circle rather than trusting the argument.
29
+ *
30
+ * ## What is NOT generated
31
+ *
32
+ * With no accent set, the hand-tuned ramp in `index.css` is used as it is. This
33
+ * module reproduces it to within a step or two, and re-deriving a value somebody
34
+ * tuned by eye is a silent regression rather than a simplification.
35
+ */
36
+ /** The steps `index.css` declares, smallest (lightest) first. */
37
+ export declare const RAMP_STEPS: readonly [100, 200, 300, 400, 500, 600, 700, 800, 900, 1000];
38
+ export type RampStep = (typeof RAMP_STEPS)[number];
39
+ export type AccentRamp = Record<RampStep, string>;
40
+ /**
41
+ * Val's own brand ramp, and the lightness profile every generated ramp borrows.
42
+ *
43
+ * A copy of what `index.css` declares, because this package cannot read the UI
44
+ * package's stylesheet — so the two are held together by a test instead:
45
+ * `contrast.test.ts` resolves the ramp out of the real CSS and asserts it equals
46
+ * this. If that test fails, the stylesheet moved and this did not.
47
+ */
48
+ export declare const VAL_GREEN_RAMP: AccentRamp;
49
+ /**
50
+ * The ten-step ramp for one accent, or `null` if it is not a six-digit hex.
51
+ *
52
+ * `null` rather than a fallback: the caller knows whether it is holding a value
53
+ * an editor picked (in which case the schema already validated it) or one that
54
+ * came out of a hand-edited settings file, and only the caller can decide
55
+ * whether "ignore it" or "report it" is right.
56
+ */
57
+ export declare function accentRamp(accent: string): AccentRamp | null;
58
+ /**
59
+ * The custom properties that restyle the chrome, ready to put on a `style`
60
+ * attribute.
61
+ *
62
+ * Custom properties on the element rather than a generated stylesheet: nothing
63
+ * has to be escaped, an inline custom property beats `@layer base` without a
64
+ * specificity fight, and a subtree that should keep the untouched palette
65
+ * simply is not given the object. See `useValThemeStyle` for where they go —
66
+ * every place the Studio stamps `data-mode`, because the theme travels with it.
67
+ *
68
+ * Empty when nothing is set, which is what makes "no theme" cost nothing.
69
+ */
70
+ export declare function themeCustomProperties(theme: {
71
+ accent?: string | null;
72
+ /** A `--radius` length. The caller maps the named step; see `THEME_RADIUS_LENGTHS`. */
73
+ radius?: string | null;
74
+ }): Record<string, string>;
75
+ /**
76
+ * WCAG relative luminance of a six-digit hex.
77
+ *
78
+ * Exported so there is one implementation of the maths that decides whether a
79
+ * theme is legible; `contrast.test.ts` had its own copy, and two copies of a
80
+ * threshold is how a guarantee quietly stops being one.
81
+ */
82
+ export declare function relativeLuminance(hex: string): number;
83
+ /** WCAG contrast ratio between two six-digit hex colours, 1 to 21. */
84
+ export declare function contrastRatio(a: string, b: string): number;
85
+ /** WCAG AA for body text. */
86
+ export declare const AA_TEXT = 4.5;
87
+ /** WCAG AA for large text and for UI components such as a focus ring. */
88
+ export declare const AA_LARGE = 3;
89
+ /**
90
+ * Where a brand pair's background comes from.
91
+ *
92
+ * A `RampStep` is a step of the brand ramp, so it moves with the accent. A hex
93
+ * is a neutral surface that does not — the canvas and the floating panels are
94
+ * grey in every theme, and a ring has to be legible against them.
95
+ */
96
+ export type PairGround = RampStep | string;
97
+ /**
98
+ * Every foreground / background pair the chrome renders with BRAND colours.
99
+ *
100
+ * The list is data rather than a test body because two different things have to
101
+ * be held to it, and one table is the only way they can agree:
102
+ *
103
+ * - `accentRamp.test.ts` runs it over ramps generated across the hue circle,
104
+ * which is what makes an arbitrary accent safe.
105
+ * - `contrast.test.ts` runs it over the ramp resolved out of the real
106
+ * `index.css`, which is what stops someone nudging Val's own green past AA.
107
+ *
108
+ * The neutral pairs — muted text on a panel, a hairline on the canvas — stay in
109
+ * `contrast.test.ts`. They have nothing to do with the accent, and a theme
110
+ * cannot move them.
111
+ */
112
+ export declare const BRAND_CONTRAST_PAIRS: readonly {
113
+ what: string;
114
+ mode: "light" | "dark";
115
+ fg: PairGround;
116
+ bg: PairGround;
117
+ min: number;
118
+ }[];
119
+ /** Resolve one side of a {@link BRAND_CONTRAST_PAIRS} entry against a ramp. */
120
+ export declare function resolvePairGround(ground: PairGround, ramp: AccentRamp): string;
@@ -54,6 +54,27 @@ export type ValCanvasElement = {
54
54
  height: number;
55
55
  };
56
56
  };
57
+ /**
58
+ * The colours Val outlines the page's content in, when nobody has said
59
+ * otherwise.
60
+ *
61
+ * They mirror `--bg-page-selection` and `--bg-page-selection-soft` in
62
+ * `packages/ui/spa/index.css`, and they are literals here because the page the
63
+ * bridge runs in has none of Val's stylesheet — no `:host`, no tokens, nothing
64
+ * to inherit. A CSS custom property genuinely cannot reach across that
65
+ * boundary, which is why the colours are SENT (see the `theme` message below)
66
+ * rather than derived.
67
+ *
68
+ * They live beside the protocol rather than with the theme code on purpose:
69
+ * `@valbuild/shared/client` is what the bridge imports, and it exists to keep
70
+ * production bundles small (see the docblock on that entrypoint). Two strings
71
+ * cost nothing; the accent generator that produces the non-default values is
72
+ * ~350 lines of colour maths that no visitor to a Val site should download.
73
+ *
74
+ * `canvasSelectionDefaults.test.ts` holds them equal to the stylesheet.
75
+ */
76
+ export declare const DEFAULT_CANVAS_SELECTION = "#079455";
77
+ export declare const DEFAULT_CANVAS_SELECTION_SOFT = "rgba(7, 148, 85, 0.4)";
57
78
  /** What the page tells the studio. */
58
79
  export type ValCanvasPageMessage = {
59
80
  val: typeof VAL_CANVAS_MESSAGE;
@@ -200,6 +221,29 @@ export type ValCanvasStudioMessage = {
200
221
  */
201
222
  val: typeof VAL_CANVAS_MESSAGE;
202
223
  type: "sourcesSynced";
224
+ } | {
225
+ /**
226
+ * What colour to outline the page's content in.
227
+ *
228
+ * Sent because it cannot be inherited: the bridge runs in the customer's
229
+ * document, which has no Val stylesheet, so the studio's custom
230
+ * properties are not merely overridden there — they do not exist. A
231
+ * project that sets `theme.accent` in `s.settings()` moves the studio's
232
+ * chrome by overriding tokens, and this message is the only way the same
233
+ * decision reaches the outlines drawn on the page itself.
234
+ *
235
+ * Both halves degrade on their own: a bridge that predates this message
236
+ * ignores it and stays Val green, and a bridge that never receives one
237
+ * falls back to {@link DEFAULT_CANVAS_SELECTION}. So an app on an older
238
+ * `@valbuild/next` than its studio, or the reverse, keeps working and
239
+ * simply does not follow the accent.
240
+ */
241
+ val: typeof VAL_CANVAS_MESSAGE;
242
+ type: "theme";
243
+ /** The outline on the element under the pointer, and on a highlight. */
244
+ selection: string;
245
+ /** The resting outline on every field the canvas found. */
246
+ selectionSoft: string;
203
247
  } | {
204
248
  val: typeof VAL_CANVAS_MESSAGE;
205
249
  type: "setPicking";
@@ -51,6 +51,28 @@ var VAL_CANVAS_MESSAGE = "val-canvas";
51
51
 
52
52
  /** One element on the page that Val has a path for. */
53
53
 
54
+ /**
55
+ * The colours Val outlines the page's content in, when nobody has said
56
+ * otherwise.
57
+ *
58
+ * They mirror `--bg-page-selection` and `--bg-page-selection-soft` in
59
+ * `packages/ui/spa/index.css`, and they are literals here because the page the
60
+ * bridge runs in has none of Val's stylesheet — no `:host`, no tokens, nothing
61
+ * to inherit. A CSS custom property genuinely cannot reach across that
62
+ * boundary, which is why the colours are SENT (see the `theme` message below)
63
+ * rather than derived.
64
+ *
65
+ * They live beside the protocol rather than with the theme code on purpose:
66
+ * `@valbuild/shared/client` is what the bridge imports, and it exists to keep
67
+ * production bundles small (see the docblock on that entrypoint). Two strings
68
+ * cost nothing; the accent generator that produces the non-default values is
69
+ * ~350 lines of colour maths that no visitor to a Val site should download.
70
+ *
71
+ * `canvasSelectionDefaults.test.ts` holds them equal to the stylesheet.
72
+ */
73
+ var DEFAULT_CANVAS_SELECTION = "#079455";
74
+ var DEFAULT_CANVAS_SELECTION_SOFT = "rgba(7, 148, 85, 0.4)";
75
+
54
76
  /** What the page tells the studio. */
55
77
 
56
78
  /** What the studio tells the page. */
@@ -63,11 +85,36 @@ function isValCanvasPageMessage(data) {
63
85
  return message.type === "ready" || message.type === "elements" || message.type === "clicked" || message.type === "pinch" || message.type === "zoom" || message.type === "refreshing";
64
86
  }
65
87
 
88
+ /**
89
+ * Whether a value is a colour a `theme` message may carry.
90
+ *
91
+ * A whitelist rather than an escape, and the reason is where these two strings
92
+ * end up: the bridge writes them into a `<style>` element in the CUSTOMER's
93
+ * document with `dangerouslySetInnerHTML`, so a value containing `</style>` is
94
+ * a script tag away from running in someone else's page. A page can be
95
+ * `postMessage`d by any window holding a handle on it, and this guard is the
96
+ * only thing between such a message and that sink.
97
+ *
98
+ * The two shapes are exactly what `themeCustomProperties` produces — a hex, and
99
+ * the `rgba()` it derives the soft outline as. Narrowing the contract is easier
100
+ * to keep true than sanitising an open one, and a forged message that passes
101
+ * this can do no more than pick a different colour.
102
+ */
103
+ var CANVAS_SELECTION_COLOR = /^(?:#[0-9a-f]{3,8}|rgba?\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*(?:,\s*(?:0|1|0?\.\d+)\s*)?\))$/i;
104
+ function isCanvasSelectionColor(value) {
105
+ return typeof value === "string" && CANVAS_SELECTION_COLOR.test(value);
106
+ }
107
+
66
108
  /** Narrow an unknown `event.data` to a message from the studio. */
67
109
  function isValCanvasStudioMessage(data) {
68
110
  if (_typeof(data) !== "object" || data === null) return false;
69
111
  var message = data;
70
112
  if (message.val !== VAL_CANVAS_MESSAGE) return false;
113
+ if (message.type === "theme") {
114
+ // Checked here and not in the bridge: a rejected message is never applied
115
+ // at all, and there is one guard rather than one per framework binding.
116
+ return isCanvasSelectionColor(message.selection) && isCanvasSelectionColor(message.selectionSoft);
117
+ }
71
118
  return message.type === "rescan" || message.type === "highlight" || message.type === "setPicking" || message.type === "sourceUpdate" || message.type === "sourcesSynced";
72
119
  }
73
120
 
@@ -428,4 +475,4 @@ function getPatternFromModuleFilePath(moduleFilePath, srcFolder) {
428
475
  .replace("/page.val.ts", "").replace("/page.val.js", "");
429
476
  }
430
477
 
431
- export { VAL_CONFIG_SESSION_STORAGE_KEY as V, _createForOfIteratorHelper as _, VAL_THEME_SESSION_STORAGE_KEY as a, VAL_AI_SESSION_STORAGE_KEY as b, VAL_CANVAS_PARAM as c, VAL_CANVAS_MESSAGE as d, isValCanvasStudioMessage as e, isValCanvasFrame as f, getNextAppRouterSourceFolder as g, getTanStackRouterSourceFolder as h, isValCanvasPageMessage as i, getPageRouterSourceFolder as j, isTanStackRoutesFolder as k, getPageRouterSitemapTree as l, getPatternFromModuleFilePath as m, _objectSpread2 as n, _arrayLikeToArray as o, parseRoutePattern as p, _unsupportedIterableToArray as q, _typeof as r, _defineProperty as s, withValCanvasParam as w };
478
+ export { DEFAULT_CANVAS_SELECTION as D, VAL_CONFIG_SESSION_STORAGE_KEY as V, _createForOfIteratorHelper as _, VAL_THEME_SESSION_STORAGE_KEY as a, VAL_AI_SESSION_STORAGE_KEY as b, VAL_CANVAS_PARAM as c, VAL_CANVAS_MESSAGE as d, DEFAULT_CANVAS_SELECTION_SOFT as e, isValCanvasStudioMessage as f, isValCanvasFrame as g, getNextAppRouterSourceFolder as h, isValCanvasPageMessage as i, getTanStackRouterSourceFolder as j, getPageRouterSourceFolder as k, isTanStackRoutesFolder as l, getPageRouterSitemapTree as m, getPatternFromModuleFilePath as n, _objectSpread2 as o, parseRoutePattern as p, _arrayLikeToArray as q, _unsupportedIterableToArray as r, _typeof as s, _defineProperty as t, withValCanvasParam as w };
@@ -53,6 +53,28 @@ var VAL_CANVAS_MESSAGE = "val-canvas";
53
53
 
54
54
  /** One element on the page that Val has a path for. */
55
55
 
56
+ /**
57
+ * The colours Val outlines the page's content in, when nobody has said
58
+ * otherwise.
59
+ *
60
+ * They mirror `--bg-page-selection` and `--bg-page-selection-soft` in
61
+ * `packages/ui/spa/index.css`, and they are literals here because the page the
62
+ * bridge runs in has none of Val's stylesheet — no `:host`, no tokens, nothing
63
+ * to inherit. A CSS custom property genuinely cannot reach across that
64
+ * boundary, which is why the colours are SENT (see the `theme` message below)
65
+ * rather than derived.
66
+ *
67
+ * They live beside the protocol rather than with the theme code on purpose:
68
+ * `@valbuild/shared/client` is what the bridge imports, and it exists to keep
69
+ * production bundles small (see the docblock on that entrypoint). Two strings
70
+ * cost nothing; the accent generator that produces the non-default values is
71
+ * ~350 lines of colour maths that no visitor to a Val site should download.
72
+ *
73
+ * `canvasSelectionDefaults.test.ts` holds them equal to the stylesheet.
74
+ */
75
+ var DEFAULT_CANVAS_SELECTION = "#079455";
76
+ var DEFAULT_CANVAS_SELECTION_SOFT = "rgba(7, 148, 85, 0.4)";
77
+
56
78
  /** What the page tells the studio. */
57
79
 
58
80
  /** What the studio tells the page. */
@@ -65,11 +87,36 @@ function isValCanvasPageMessage(data) {
65
87
  return message.type === "ready" || message.type === "elements" || message.type === "clicked" || message.type === "pinch" || message.type === "zoom" || message.type === "refreshing";
66
88
  }
67
89
 
90
+ /**
91
+ * Whether a value is a colour a `theme` message may carry.
92
+ *
93
+ * A whitelist rather than an escape, and the reason is where these two strings
94
+ * end up: the bridge writes them into a `<style>` element in the CUSTOMER's
95
+ * document with `dangerouslySetInnerHTML`, so a value containing `</style>` is
96
+ * a script tag away from running in someone else's page. A page can be
97
+ * `postMessage`d by any window holding a handle on it, and this guard is the
98
+ * only thing between such a message and that sink.
99
+ *
100
+ * The two shapes are exactly what `themeCustomProperties` produces — a hex, and
101
+ * the `rgba()` it derives the soft outline as. Narrowing the contract is easier
102
+ * to keep true than sanitising an open one, and a forged message that passes
103
+ * this can do no more than pick a different colour.
104
+ */
105
+ var CANVAS_SELECTION_COLOR = /^(?:#[0-9a-f]{3,8}|rgba?\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*(?:,\s*(?:0|1|0?\.\d+)\s*)?\))$/i;
106
+ function isCanvasSelectionColor(value) {
107
+ return typeof value === "string" && CANVAS_SELECTION_COLOR.test(value);
108
+ }
109
+
68
110
  /** Narrow an unknown `event.data` to a message from the studio. */
69
111
  function isValCanvasStudioMessage(data) {
70
112
  if (_typeof(data) !== "object" || data === null) return false;
71
113
  var message = data;
72
114
  if (message.val !== VAL_CANVAS_MESSAGE) return false;
115
+ if (message.type === "theme") {
116
+ // Checked here and not in the bridge: a rejected message is never applied
117
+ // at all, and there is one guard rather than one per framework binding.
118
+ return isCanvasSelectionColor(message.selection) && isCanvasSelectionColor(message.selectionSoft);
119
+ }
73
120
  return message.type === "rescan" || message.type === "highlight" || message.type === "setPicking" || message.type === "sourceUpdate" || message.type === "sourcesSynced";
74
121
  }
75
122
 
@@ -430,6 +477,8 @@ function getPatternFromModuleFilePath(moduleFilePath, srcFolder) {
430
477
  .replace("/page.val.ts", "").replace("/page.val.js", "");
431
478
  }
432
479
 
480
+ exports.DEFAULT_CANVAS_SELECTION = DEFAULT_CANVAS_SELECTION;
481
+ exports.DEFAULT_CANVAS_SELECTION_SOFT = DEFAULT_CANVAS_SELECTION_SOFT;
433
482
  exports.VAL_AI_SESSION_STORAGE_KEY = VAL_AI_SESSION_STORAGE_KEY;
434
483
  exports.VAL_CANVAS_MESSAGE = VAL_CANVAS_MESSAGE;
435
484
  exports.VAL_CANVAS_PARAM = VAL_CANVAS_PARAM;
@@ -53,6 +53,28 @@ var VAL_CANVAS_MESSAGE = "val-canvas";
53
53
 
54
54
  /** One element on the page that Val has a path for. */
55
55
 
56
+ /**
57
+ * The colours Val outlines the page's content in, when nobody has said
58
+ * otherwise.
59
+ *
60
+ * They mirror `--bg-page-selection` and `--bg-page-selection-soft` in
61
+ * `packages/ui/spa/index.css`, and they are literals here because the page the
62
+ * bridge runs in has none of Val's stylesheet — no `:host`, no tokens, nothing
63
+ * to inherit. A CSS custom property genuinely cannot reach across that
64
+ * boundary, which is why the colours are SENT (see the `theme` message below)
65
+ * rather than derived.
66
+ *
67
+ * They live beside the protocol rather than with the theme code on purpose:
68
+ * `@valbuild/shared/client` is what the bridge imports, and it exists to keep
69
+ * production bundles small (see the docblock on that entrypoint). Two strings
70
+ * cost nothing; the accent generator that produces the non-default values is
71
+ * ~350 lines of colour maths that no visitor to a Val site should download.
72
+ *
73
+ * `canvasSelectionDefaults.test.ts` holds them equal to the stylesheet.
74
+ */
75
+ var DEFAULT_CANVAS_SELECTION = "#079455";
76
+ var DEFAULT_CANVAS_SELECTION_SOFT = "rgba(7, 148, 85, 0.4)";
77
+
56
78
  /** What the page tells the studio. */
57
79
 
58
80
  /** What the studio tells the page. */
@@ -65,11 +87,36 @@ function isValCanvasPageMessage(data) {
65
87
  return message.type === "ready" || message.type === "elements" || message.type === "clicked" || message.type === "pinch" || message.type === "zoom" || message.type === "refreshing";
66
88
  }
67
89
 
90
+ /**
91
+ * Whether a value is a colour a `theme` message may carry.
92
+ *
93
+ * A whitelist rather than an escape, and the reason is where these two strings
94
+ * end up: the bridge writes them into a `<style>` element in the CUSTOMER's
95
+ * document with `dangerouslySetInnerHTML`, so a value containing `</style>` is
96
+ * a script tag away from running in someone else's page. A page can be
97
+ * `postMessage`d by any window holding a handle on it, and this guard is the
98
+ * only thing between such a message and that sink.
99
+ *
100
+ * The two shapes are exactly what `themeCustomProperties` produces — a hex, and
101
+ * the `rgba()` it derives the soft outline as. Narrowing the contract is easier
102
+ * to keep true than sanitising an open one, and a forged message that passes
103
+ * this can do no more than pick a different colour.
104
+ */
105
+ var CANVAS_SELECTION_COLOR = /^(?:#[0-9a-f]{3,8}|rgba?\(\s*\d{1,3}\s*,\s*\d{1,3}\s*,\s*\d{1,3}\s*(?:,\s*(?:0|1|0?\.\d+)\s*)?\))$/i;
106
+ function isCanvasSelectionColor(value) {
107
+ return typeof value === "string" && CANVAS_SELECTION_COLOR.test(value);
108
+ }
109
+
68
110
  /** Narrow an unknown `event.data` to a message from the studio. */
69
111
  function isValCanvasStudioMessage(data) {
70
112
  if (_typeof(data) !== "object" || data === null) return false;
71
113
  var message = data;
72
114
  if (message.val !== VAL_CANVAS_MESSAGE) return false;
115
+ if (message.type === "theme") {
116
+ // Checked here and not in the bridge: a rejected message is never applied
117
+ // at all, and there is one guard rather than one per framework binding.
118
+ return isCanvasSelectionColor(message.selection) && isCanvasSelectionColor(message.selectionSoft);
119
+ }
73
120
  return message.type === "rescan" || message.type === "highlight" || message.type === "setPicking" || message.type === "sourceUpdate" || message.type === "sourcesSynced";
74
121
  }
75
122
 
@@ -430,6 +477,8 @@ function getPatternFromModuleFilePath(moduleFilePath, srcFolder) {
430
477
  .replace("/page.val.ts", "").replace("/page.val.js", "");
431
478
  }
432
479
 
480
+ exports.DEFAULT_CANVAS_SELECTION = DEFAULT_CANVAS_SELECTION;
481
+ exports.DEFAULT_CANVAS_SELECTION_SOFT = DEFAULT_CANVAS_SELECTION_SOFT;
433
482
  exports.VAL_AI_SESSION_STORAGE_KEY = VAL_AI_SESSION_STORAGE_KEY;
434
483
  exports.VAL_CANVAS_MESSAGE = VAL_CANVAS_MESSAGE;
435
484
  exports.VAL_CANVAS_PARAM = VAL_CANVAS_PARAM;