@valbuild/shared 0.127.0 → 0.129.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.
@@ -1,6 +1,6 @@
1
1
  import { Internal, CODE_LANGUAGES, resolveSettingsModule, missingDeclaredKeys, localeOfValue, discriminatedUnionBranchOf, DEFAULT_COLOR_FORMAT, declaredKeySetOf } from '@valbuild/core';
2
- import { _ as _createForOfIteratorHelper, n as _objectSpread2, o as _arrayLikeToArray, q as _unsupportedIterableToArray, r as _defineProperty, s as _typeof } from '../../dist/getSitemapTree-a6cb37cb.esm.js';
3
- 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-a6cb37cb.esm.js';
2
+ import { _ as _createForOfIteratorHelper, o as _objectSpread2, q as _arrayLikeToArray, r as _unsupportedIterableToArray, s as _typeof, t as _defineProperty } from '../../dist/getSitemapTree-878d3888.esm.js';
3
+ 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';
4
4
  import { z } from 'zod';
5
5
  import { fromError } from 'zod-validation-error';
6
6
  import FlexSearch from 'flexsearch';
@@ -890,10 +890,34 @@ var PatchBlock = z.object({
890
890
  parentRef: ParentRef$1
891
891
  });
892
892
 
893
+ function _objectWithoutPropertiesLoose(r, e) {
894
+ if (null == r) return {};
895
+ var t = {};
896
+ for (var n in r) if ({}.hasOwnProperty.call(r, n)) {
897
+ if (-1 !== e.indexOf(n)) continue;
898
+ t[n] = r[n];
899
+ }
900
+ return t;
901
+ }
902
+
903
+ function _objectWithoutProperties(e, t) {
904
+ if (null == e) return {};
905
+ var o,
906
+ r,
907
+ i = _objectWithoutPropertiesLoose(e, t);
908
+ if (Object.getOwnPropertySymbols) {
909
+ var n = Object.getOwnPropertySymbols(e);
910
+ for (r = 0; r < n.length; r++) o = n[r], -1 === t.indexOf(o) && {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]);
911
+ }
912
+ return i;
913
+ }
914
+
893
915
  var SourcePath = z.string().transform(function (path) {
894
916
  return path;
895
917
  });
896
918
 
919
+ var _excluded = ["directory"];
920
+
897
921
  // A render is static config, so unlike a `preview` it travels WHOLE — this
898
922
  // is the field the editor reads the layout from. See `core/src/render.ts`.
899
923
  // Every field can carry `{ as: "inline" }`, and that is all a render says.
@@ -1001,11 +1025,35 @@ var ImageEncodeOption = z.union([z.literal(false), z.object({
1001
1025
  maxWidth: z.number().optional(),
1002
1026
  maxHeight: z.number().optional()
1003
1027
  })]);
1004
- var ImageOptions = z.object({
1005
- directory: z.string().optional(),
1028
+ /**
1029
+ * Accept the pre-rename `directory` key and read it as `dir`.
1030
+ *
1031
+ * Serialized schemas are not only computed from live code: a commit record
1032
+ * stores the schema each module was under (`StoredModuleVersion`), and the
1033
+ * history view parses those back. Commits made before `directory` became `dir`
1034
+ * therefore still carry the old key, and dropping it would show a historical
1035
+ * gallery with no directory at all — silently, since the rest of the schema
1036
+ * parses fine.
1037
+ *
1038
+ * Only fills `dir` when it is absent, so a schema carrying both (which should
1039
+ * not happen) keeps the current key.
1040
+ */
1041
+ var readLegacyDirectoryAsDir = function readLegacyDirectoryAsDir(value) {
1042
+ if (!value || _typeof(value) !== "object" || Array.isArray(value) || !("directory" in value) || "dir" in value) {
1043
+ return value;
1044
+ }
1045
+ var _ref = value,
1046
+ directory = _ref.directory,
1047
+ rest = _objectWithoutProperties(_ref, _excluded);
1048
+ return _objectSpread2(_objectSpread2({}, rest), {}, {
1049
+ dir: directory
1050
+ });
1051
+ };
1052
+ var ImageOptions = z.preprocess(readLegacyDirectoryAsDir, z.object({
1053
+ dir: z.string().optional(),
1006
1054
  accept: z.string().optional(),
1007
1055
  encode: ImageEncodeOption.optional()
1008
- });
1056
+ }));
1009
1057
  var SerializedImageSchema = z.object(_objectSpread2(_objectSpread2({}, commonSchemaFields), {}, {
1010
1058
  type: z.literal("image"),
1011
1059
  options: ImageOptions.optional(),
@@ -1044,7 +1092,7 @@ var SerializedRichTextSchema = z.object(_objectSpread2(_objectSpread2({}, common
1044
1092
  opt: z["boolean"]()
1045
1093
  }));
1046
1094
  var SerializedRecordSchema = z.lazy(function () {
1047
- return z.object(_objectSpread2(_objectSpread2({}, commonSchemaFields), {}, {
1095
+ return z.preprocess(readLegacyDirectoryAsDir, z.object(_objectSpread2(_objectSpread2({}, commonSchemaFields), {}, {
1048
1096
  type: z.literal("record"),
1049
1097
  item: SerializedSchema,
1050
1098
  opt: z["boolean"](),
@@ -1052,12 +1100,12 @@ var SerializedRecordSchema = z.lazy(function () {
1052
1100
  mediaType: z.union([z.literal("files"), z.literal("images")]).optional(),
1053
1101
  // Optional legacy gallery metadata
1054
1102
  accept: z.string().optional(),
1055
- directory: z.string().optional(),
1103
+ dir: z.string().optional(),
1056
1104
  remote: z["boolean"]().optional(),
1057
1105
  encode: ImageEncodeOption.optional(),
1058
1106
  alt: SerializedSchema.optional(),
1059
1107
  moduleMetadata: z.record(z.string(), z.record(z.string(), z.any())).optional()
1060
- })).passthrough();
1108
+ })).passthrough());
1061
1109
  });
1062
1110
  var SerializedKeyOfSchema = z.lazy(function () {
1063
1111
  return z.object(_objectSpread2(_objectSpread2({}, commonSchemaFields), {}, {
@@ -4697,7 +4745,7 @@ function getSourceAt(source, path) {
4697
4745
  * Walks `schema` along `path`, returning a structured result:
4698
4746
  * - `leaf`: walked the full path; resolved schema at the end.
4699
4747
  * - `richtext`: hit a richtext mid-walk; richtext is the resolved schema.
4700
- * - `gallery-traversed`: hit an `s.images()` record and walked past it (i.e. the
4748
+ * - `gallery-traversed`: hit an `s.imageset()` record and walked past it (i.e. the
4701
4749
  * path tries to address an entry inside the gallery — a clue the AI should
4702
4750
  * have used the gallery tool instead).
4703
4751
  * - `unresolved`: the path could not be walked.
@@ -4828,7 +4876,7 @@ function buildImageGalleryPatch(args, moduleSchema) {
4828
4876
  op: "add",
4829
4877
  path: [args.filePath],
4830
4878
  value: _objectSpread2(_objectSpread2({}, args.metadata), {}, {
4831
- // `alt` is a REQUIRED field of a gallery entry (`s.images()` gives it a
4879
+ // `alt` is a REQUIRED field of a gallery entry (`s.imageset()` gives it a
4832
4880
  // nullable string schema), so an entry written without one is invalid
4833
4881
  // the moment it lands — and the caller that had no alt to give is the
4834
4882
  // common case, not the odd one. Null is what the Studio's own upload
@@ -4891,10 +4939,10 @@ function buildRemoveImageGalleryEntryPatch(args, moduleSchema, moduleSource) {
4891
4939
  var GALLERY_REDIRECT = {
4892
4940
  kind: "wrong-tool",
4893
4941
  suggestedTool: "add_session_image_to_gallery",
4894
- reason: "Destination is a media gallery (s.images() / s.files()). Gallery entries are keyed by file path and carry a file on disk, so they cannot be created with a plain patch. Use add_session_image_to_gallery."
4942
+ reason: "Destination is a media gallery (s.imageset() / s.fileset()). Gallery entries are keyed by file path and carry a file on disk, so they cannot be created with a plain patch. Use add_session_image_to_gallery."
4895
4943
  };
4896
4944
 
4897
- /** An `s.images()` / `s.files()` record: a gallery, not an ordinary record. */
4945
+ /** An `s.imageset()` / `s.fileset()` record: a gallery, not an ordinary record. */
4898
4946
  function isGallery(schema) {
4899
4947
  return schema.type === "record" && schema.mediaType !== undefined;
4900
4948
  }
@@ -6064,4 +6112,497 @@ function performSearch(searchIndex, query) {
6064
6112
  };
6065
6113
  }
6066
6114
 
6067
- export { Api, BinaryFileRef, CodeOptions, ColorOptions, DEFAULT_ERROR_MESSAGE, DateOptions, ENCODE_DEFAULTS, ENCODE_MIME_TYPE_OF, FileOptions, HistoricalCommit, HistoricalModule, HistoricalPatchSet, HistoryError, ImageEncodeOption, ImageOptions, JSONValue, JSON_ENTRIES_BATCH_MAX, ModuleComparison, NEVER_ENCODED, ParentRef$1 as ParentRef, Patch, PatchBlock, PatchGroup, PatchId, PatchJSON, RemirrorBr, RemirrorBulletList, RemirrorHeading, RemirrorImage, RemirrorJSON, RemirrorLinkMark, RemirrorListItem, RemirrorOrderedList, RemirrorParagraph, RemirrorText, RemirrorTextMark, RestoreVerdict, RichTextOptions, SCHEMA_SOURCE_FIXES, SerializedArraySchema, SerializedBooleanSchema, SerializedCodeSchema, SerializedColorSchema, SerializedDateSchema, SerializedDateTimeSchema, SerializedDiscriminatedUnionSchema, SerializedEnumSchema, SerializedFileSchema, SerializedImageSchema, SerializedKeyOfSchema, SerializedLiteralSchema, SerializedLocaleSchema, SerializedNumberSchema, SerializedObjectSchema, SerializedRecordSchema, SerializedRichTextSchema, SerializedRouteSchema, SerializedSchema, SerializedSettingsSchema, SerializedStringSchema, SharedValConfig, VAL_ENABLE_COOKIE_NAME, VAL_SESSION_COOKIE, VAL_STATE_COOKIE, ValCommit, ValDeployment, buildDuplicatePatch, buildEmptyAtPathPatch, buildFileRefValue, buildImageGalleryPatch, buildRemoveImageGalleryEntryPatch, buildSearchIndex, checkCompatibility, chooseEncoded, concatModulePath, createRegExpFromPattern, createSearchIndex, createValClient, declaredLocales, describeContainerAtPath, emptyOf, explainIncompatible, filterBlockingValidationErrors, filterRoutesByPatterns, findSimilar, fitWithin, flattenRichText, getErrorMessageFromUnknownJson, getFilenameFromRef, getInlineImgInfo, getKeyOfRecordAt, getRefParts, getRoutesOf, getRoutesWithModulePaths, getSourceAt, indexModule, isMimeTypeAccepted, isRemoteSchema, isSchemaSourceFixError, isSkippedSource, keyOfRecordPath, levenshtein, localeAt, newestCommitSha, partitionValidationErrors, performSearch, remirrorToRichTextSource, removeModule, resolveEncodeSettings, resolveSchemaSourceFixForError, resolveSchemaSourceFixes, resolveSerializedSchemaAtPath, richTextToRemirror, safeParsePatch, sourcePathOfChild, toolNames, traverseSchemaSource, urlOf, validateRoutePatterns, withExtension };
6115
+ /**
6116
+ * The Studio's chrome, generated from one colour.
6117
+ *
6118
+ * `s.settings()`'s `theme.accent` is a single hex, and what it has to replace is
6119
+ * a TEN-STEP RAMP: `index.css` declares `--colors-brand-green-100` through
6120
+ * `-1000` and every brand token points into it, picking different steps in light
6121
+ * and in dark (a tinted surface is step 200 in light and 800 in dark; the text
6122
+ * on it is 1000 and 100). So the accent is not a colour to place, it is a ramp
6123
+ * to build — which is also why one accent drives both modes with nothing to keep
6124
+ * in sync.
6125
+ *
6126
+ * ## Why an arbitrary hex is safe
6127
+ *
6128
+ * The obvious worry is `contrast.test.ts`, which holds every foreground /
6129
+ * background pair the chrome renders to WCAG AA. A hex typed by an editor never
6130
+ * goes near that test.
6131
+ *
6132
+ * It holds anyway, because of how the ramp is built: **green's lightness at each
6133
+ * step is reused unchanged, and only the hue moves.** WCAG relative luminance is
6134
+ * almost entirely a function of lightness, so a ramp that keeps green's L
6135
+ * profile keeps green's contrast relationships. Chroma is scaled to the accent's
6136
+ * own saturation and then reduced — never L — until each step fits sRGB.
6137
+ *
6138
+ * "Almost entirely" is doing real work in that sentence: at equal OKLCH
6139
+ * lightness a saturated yellow has a higher relative luminance than a blue, so
6140
+ * the ratios do move a little. {@link BRAND_CONTRAST_PAIRS} is the list of pairs
6141
+ * that have to survive it, and `accentRamp.test.ts` runs them over the hue
6142
+ * circle rather than trusting the argument.
6143
+ *
6144
+ * ## What is NOT generated
6145
+ *
6146
+ * With no accent set, the hand-tuned ramp in `index.css` is used as it is. This
6147
+ * module reproduces it to within a step or two, and re-deriving a value somebody
6148
+ * tuned by eye is a silent regression rather than a simplification.
6149
+ */
6150
+
6151
+ /** The steps `index.css` declares, smallest (lightest) first. */
6152
+ var RAMP_STEPS = [100, 200, 300, 400, 500, 600, 700, 800, 900, 1000];
6153
+ /**
6154
+ * Val's own brand ramp, and the lightness profile every generated ramp borrows.
6155
+ *
6156
+ * A copy of what `index.css` declares, because this package cannot read the UI
6157
+ * package's stylesheet — so the two are held together by a test instead:
6158
+ * `contrast.test.ts` resolves the ramp out of the real CSS and asserts it equals
6159
+ * this. If that test fails, the stylesheet moved and this did not.
6160
+ */
6161
+ var VAL_GREEN_RAMP = {
6162
+ 100: "#dcfae6",
6163
+ 200: "#abefc6",
6164
+ 300: "#75e0a7",
6165
+ 400: "#47cd89",
6166
+ 500: "#17b26a",
6167
+ 600: "#079455",
6168
+ 700: "#067647",
6169
+ 800: "#085d3a",
6170
+ 900: "#074d31",
6171
+ 1000: "#053321"
6172
+ };
6173
+
6174
+ /**
6175
+ * The step the accent itself becomes, and therefore the step the whole ramp's
6176
+ * chroma is scaled against.
6177
+ *
6178
+ * 500 rather than 600: it is the most saturated step in the green ramp, so an
6179
+ * accent anchored there is reproduced about as faithfully as the ramp allows,
6180
+ * and the steps either side of it stay in the same relationship to it that
6181
+ * green's do.
6182
+ */
6183
+ var ANCHOR_STEP = 500;
6184
+
6185
+ /**
6186
+ * The step drawn on the CUSTOMER's page, as an outline around every editable
6187
+ * element.
6188
+ *
6189
+ * 600 is the step `index.css` pins the page-selection tokens to, for a reason
6190
+ * worth keeping: it clears 3:1 against both white and black, so the outlines
6191
+ * stay visible whatever the site behind them looks like. A generated ramp
6192
+ * inherits that because it inherits green-600's lightness — and
6193
+ * `accentRamp.test.ts` checks it on both grounds rather than assuming.
6194
+ */
6195
+ var PAGE_SELECTION_STEP = 600;
6196
+
6197
+ // ---------------------------------------------------------------------------
6198
+ // sRGB <-> OKLCH
6199
+ //
6200
+ // Written out rather than taken from a dependency: it is thirty lines, it runs
6201
+ // on every Studio render that has an accent, and `s.color()` already carries
6202
+ // its own copy of the neighbouring maths (`schema/colorFormat.ts`) for the same
6203
+ // reason.
6204
+ // ---------------------------------------------------------------------------
6205
+
6206
+ /** OKLCH: lightness 0-1, chroma, hue in degrees. */
6207
+
6208
+ /**
6209
+ * Every hex form `s.color({ format: "hex" })` accepts.
6210
+ *
6211
+ * Three and four digits as well as six and eight, because that is what the
6212
+ * schema validates — `detectColorFormat` calls anything starting with `#` hex,
6213
+ * and `#fff` parses. A generator that took only six digits left a
6214
+ * schema-VALID accent doing nothing at all: no ramp, no error, and a Studio
6215
+ * still on Val green with a colour sitting in the settings module.
6216
+ */
6217
+ var HEX = /^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
6218
+ function srgbToLinear(c) {
6219
+ return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
6220
+ }
6221
+ function linearToSrgb(c) {
6222
+ return c <= 0.0031308 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
6223
+ }
6224
+
6225
+ /**
6226
+ * `null` for anything that is not a hex colour.
6227
+ *
6228
+ * Alpha is read and discarded. The ramp is a set of opaque surfaces and text
6229
+ * colours, and `s.color()` rejects a non-opaque value anyway unless the field
6230
+ * enabled alpha — which `theme.accent` does not. So `#0f08` is the same accent
6231
+ * as `#0f0`, rather than nothing at all.
6232
+ */
6233
+ function hexToRgb(hex) {
6234
+ var trimmed = hex.trim();
6235
+ if (!HEX.test(trimmed)) {
6236
+ return null;
6237
+ }
6238
+ var digits = trimmed.slice(1);
6239
+ var _short = digits.length <= 4;
6240
+ var channel = function channel(index) {
6241
+ var slice = _short ? digits[index].repeat(2) : digits.slice(index * 2, index * 2 + 2);
6242
+ return parseInt(slice, 16) / 255;
6243
+ };
6244
+ return [channel(0), channel(1), channel(2)];
6245
+ }
6246
+ function rgbToHex(rgb) {
6247
+ return "#" + rgb.map(function (c) {
6248
+ return Math.round(Math.min(1, Math.max(0, c)) * 255).toString(16).padStart(2, "0");
6249
+ }).join("");
6250
+ }
6251
+ function rgbToOklch(rgb) {
6252
+ var r = srgbToLinear(rgb[0]);
6253
+ var g = srgbToLinear(rgb[1]);
6254
+ var b = srgbToLinear(rgb[2]);
6255
+ var l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
6256
+ var m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
6257
+ var s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
6258
+ var okL = 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s;
6259
+ var okA = 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s;
6260
+ var okB = 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s;
6261
+ return [okL, Math.hypot(okA, okB), Math.atan2(okB, okA) * 180 / Math.PI];
6262
+ }
6263
+ function oklchToRgb(lch) {
6264
+ var okA = lch[1] * Math.cos(lch[2] * Math.PI / 180);
6265
+ var okB = lch[1] * Math.sin(lch[2] * Math.PI / 180);
6266
+ var l = Math.pow(lch[0] + 0.3963377774 * okA + 0.2158037573 * okB, 3);
6267
+ var m = Math.pow(lch[0] - 0.1055613458 * okA - 0.0638541728 * okB, 3);
6268
+ var s = Math.pow(lch[0] - 0.0894841775 * okA - 1.291485548 * okB, 3);
6269
+ return [linearToSrgb(4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s), linearToSrgb(-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s), linearToSrgb(-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s)];
6270
+ }
6271
+
6272
+ /** A hair of slack, so a colour that is exactly at the edge is not clamped. */
6273
+ var GAMUT_EPSILON = 0.0005;
6274
+ function inGamut(rgb) {
6275
+ return rgb.every(function (c) {
6276
+ return c >= -GAMUT_EPSILON && c <= 1 + GAMUT_EPSILON;
6277
+ });
6278
+ }
6279
+
6280
+ /**
6281
+ * Bring a colour inside sRGB by reducing its CHROMA, never its lightness.
6282
+ *
6283
+ * That restriction is the whole reason the contrast argument holds: clipping
6284
+ * RGB channels (which is what naive conversion does) moves lightness, and a
6285
+ * step that has drifted lighter is a step whose contrast guarantee is gone.
6286
+ * Twenty-four bisections is well past single-byte precision.
6287
+ */
6288
+ function clampChromaToGamut(lch) {
6289
+ if (inGamut(oklchToRgb(lch))) {
6290
+ return lch;
6291
+ }
6292
+ var lo = 0;
6293
+ var hi = lch[1];
6294
+ for (var i = 0; i < 24; i++) {
6295
+ var mid = (lo + hi) / 2;
6296
+ if (inGamut(oklchToRgb([lch[0], mid, lch[2]]))) {
6297
+ lo = mid;
6298
+ } else {
6299
+ hi = mid;
6300
+ }
6301
+ }
6302
+ return [lch[0], lo, lch[2]];
6303
+ }
6304
+ var GREEN_PROFILE = RAMP_STEPS.map(function (step) {
6305
+ var rgb = hexToRgb(VAL_GREEN_RAMP[step]);
6306
+ if (rgb === null) {
6307
+ // Unreachable: the ramp above is literal hex. Thrown rather than defaulted
6308
+ // because a broken profile would produce a plausible-looking wrong ramp.
6309
+ throw new Error("Val's green ramp holds a non-hex value at step ".concat(step));
6310
+ }
6311
+ return {
6312
+ step: step,
6313
+ lch: rgbToOklch(rgb)
6314
+ };
6315
+ });
6316
+
6317
+ /**
6318
+ * The ten-step ramp for one accent, or `null` if it is not a six-digit hex.
6319
+ *
6320
+ * `null` rather than a fallback: the caller knows whether it is holding a value
6321
+ * an editor picked (in which case the schema already validated it) or one that
6322
+ * came out of a hand-edited settings file, and only the caller can decide
6323
+ * whether "ignore it" or "report it" is right.
6324
+ */
6325
+ function accentRamp(accent) {
6326
+ var rgb = hexToRgb(accent);
6327
+ if (rgb === null) {
6328
+ return null;
6329
+ }
6330
+ var _rgbToOklch = rgbToOklch(rgb),
6331
+ _rgbToOklch2 = _slicedToArray(_rgbToOklch, 3),
6332
+ accentChroma = _rgbToOklch2[1],
6333
+ accentHue = _rgbToOklch2[2];
6334
+ var anchor = GREEN_PROFILE.find(function (entry) {
6335
+ return entry.step === ANCHOR_STEP;
6336
+ });
6337
+ if (anchor === undefined || anchor.lch[1] === 0) {
6338
+ throw new Error("Val's green ramp has no chroma at its anchor step");
6339
+ }
6340
+ var chromaScale = accentChroma / anchor.lch[1];
6341
+ var at = function at(step) {
6342
+ var entry = GREEN_PROFILE.find(function (candidate) {
6343
+ return candidate.step === step;
6344
+ });
6345
+ if (entry === undefined) {
6346
+ throw new Error("Val's green ramp has no step ".concat(step));
6347
+ }
6348
+ // Green's lightness and chroma, the accent's hue. Green's own hue is the
6349
+ // one thing a generated ramp does not keep.
6350
+ var _entry$lch = _slicedToArray(entry.lch, 2),
6351
+ lightness = _entry$lch[0],
6352
+ chroma = _entry$lch[1];
6353
+ return rgbToHex(oklchToRgb(clampChromaToGamut([lightness, chroma * chromaScale, accentHue])));
6354
+ };
6355
+ // Written out rather than reduced into, so the type says every step is
6356
+ // present instead of a cast saying so.
6357
+ return {
6358
+ 100: at(100),
6359
+ 200: at(200),
6360
+ 300: at(300),
6361
+ 400: at(400),
6362
+ 500: at(500),
6363
+ 600: at(600),
6364
+ 700: at(700),
6365
+ 800: at(800),
6366
+ 900: at(900),
6367
+ 1000: at(1000)
6368
+ };
6369
+ }
6370
+
6371
+ /**
6372
+ * The custom properties that restyle the chrome, ready to put on a `style`
6373
+ * attribute.
6374
+ *
6375
+ * Custom properties on the element rather than a generated stylesheet: nothing
6376
+ * has to be escaped, an inline custom property beats `@layer base` without a
6377
+ * specificity fight, and a subtree that should keep the untouched palette
6378
+ * simply is not given the object. See `useValThemeStyle` for where they go —
6379
+ * every place the Studio stamps `data-mode`, because the theme travels with it.
6380
+ *
6381
+ * Empty when nothing is set, which is what makes "no theme" cost nothing.
6382
+ */
6383
+ function themeCustomProperties(theme) {
6384
+ var properties = {};
6385
+ if (theme.accent) {
6386
+ var ramp = accentRamp(theme.accent);
6387
+ if (ramp !== null) {
6388
+ var _iterator = _createForOfIteratorHelper(RAMP_STEPS),
6389
+ _step;
6390
+ try {
6391
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
6392
+ var step = _step.value;
6393
+ properties["--colors-brand-green-".concat(step)] = ramp[step];
6394
+ }
6395
+ /*
6396
+ * The page-selection trio, written OUT rather than left to follow the
6397
+ * ramp — and the first of them is the one that has to be said out loud,
6398
+ * because it looks like it should follow and does not.
6399
+ *
6400
+ * `index.css` declares `--bg-page-selection: var(--colors-brand-green-600)`,
6401
+ * so overriding the ramp looks like enough. It is not: **a `var()` inside
6402
+ * a custom property is substituted where the property is DECLARED, not
6403
+ * where it is used.** That declaration lives in the light block, whose
6404
+ * selector is `:host, :root, *[data-mode="light"]` — so on a
6405
+ * `data-mode="dark"` element the rule does not match, nothing is declared
6406
+ * there, and what the element inherits from `:host` is the value already
6407
+ * substituted to green. An override of the ramp on the element cannot
6408
+ * reach backwards into it.
6409
+ *
6410
+ * The brand tokens escape this only because the DARK block re-declares
6411
+ * them: `--bg-brand-primary: var(--colors-brand-green-800)` is computed
6412
+ * on the themed element itself, so it resolves against the override. The
6413
+ * page-selection tokens are deliberately declared once and never flipped
6414
+ * with the theme (the page underneath is not a Val surface), which is
6415
+ * exactly what puts them outside that mechanism.
6416
+ *
6417
+ * So: anything that depends on the ramp and is not re-declared per mode
6418
+ * has to be written here. `brandDerivedTokens.test.ts` scans the
6419
+ * stylesheet and fails if another one appears.
6420
+ */
6421
+ } catch (err) {
6422
+ _iterator.e(err);
6423
+ } finally {
6424
+ _iterator.f();
6425
+ }
6426
+ properties["--bg-page-selection"] = ramp[PAGE_SELECTION_STEP];
6427
+ var selection = hexToRgb(ramp[PAGE_SELECTION_STEP]);
6428
+ if (selection !== null) {
6429
+ var channels = selection.map(function (c) {
6430
+ return Math.round(c * 255);
6431
+ }).join(", ");
6432
+ properties["--bg-page-selection-fill"] = "rgba(".concat(channels, ", 0.12)");
6433
+ properties["--bg-page-selection-soft"] = "rgba(".concat(channels, ", 0.4)");
6434
+ }
6435
+ }
6436
+ }
6437
+ if (theme.radius) {
6438
+ properties["--radius"] = theme.radius;
6439
+ }
6440
+ return properties;
6441
+ }
6442
+
6443
+ /**
6444
+ * WCAG relative luminance of a six-digit hex.
6445
+ *
6446
+ * Exported so there is one implementation of the maths that decides whether a
6447
+ * theme is legible; `contrast.test.ts` had its own copy, and two copies of a
6448
+ * threshold is how a guarantee quietly stops being one.
6449
+ */
6450
+ function relativeLuminance(hex) {
6451
+ var rgb = hexToRgb(hex);
6452
+ if (rgb === null) {
6453
+ throw new Error("Not a six-digit hex colour: ".concat(hex));
6454
+ }
6455
+ var _rgb$map = rgb.map(srgbToLinear),
6456
+ _rgb$map2 = _slicedToArray(_rgb$map, 3),
6457
+ r = _rgb$map2[0],
6458
+ g = _rgb$map2[1],
6459
+ b = _rgb$map2[2];
6460
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
6461
+ }
6462
+
6463
+ /** WCAG contrast ratio between two six-digit hex colours, 1 to 21. */
6464
+ function contrastRatio(a, b) {
6465
+ var la = relativeLuminance(a);
6466
+ var lb = relativeLuminance(b);
6467
+ var hi = Math.max(la, lb);
6468
+ var lo = Math.min(la, lb);
6469
+ return (hi + 0.05) / (lo + 0.05);
6470
+ }
6471
+
6472
+ /** WCAG AA for body text. */
6473
+ var AA_TEXT = 4.5;
6474
+ /** WCAG AA for large text and for UI components such as a focus ring. */
6475
+ var AA_LARGE = 3;
6476
+
6477
+ /**
6478
+ * Where a brand pair's background comes from.
6479
+ *
6480
+ * A `RampStep` is a step of the brand ramp, so it moves with the accent. A hex
6481
+ * is a neutral surface that does not — the canvas and the floating panels are
6482
+ * grey in every theme, and a ring has to be legible against them.
6483
+ */
6484
+
6485
+ /**
6486
+ * Every foreground / background pair the chrome renders with BRAND colours.
6487
+ *
6488
+ * The list is data rather than a test body because two different things have to
6489
+ * be held to it, and one table is the only way they can agree:
6490
+ *
6491
+ * - `accentRamp.test.ts` runs it over ramps generated across the hue circle,
6492
+ * which is what makes an arbitrary accent safe.
6493
+ * - `contrast.test.ts` runs it over the ramp resolved out of the real
6494
+ * `index.css`, which is what stops someone nudging Val's own green past AA.
6495
+ *
6496
+ * The neutral pairs — muted text on a panel, a hairline on the canvas — stay in
6497
+ * `contrast.test.ts`. They have nothing to do with the accent, and a theme
6498
+ * cannot move them.
6499
+ */
6500
+ var BRAND_CONTRAST_PAIRS = [
6501
+ // Light: `--bg-brand-primary` is step 200, with `--fg-brand-primary` (1000)
6502
+ // and `--fg-brand-primary-alt` (900) on it.
6503
+ {
6504
+ what: "fg-brand-primary on bg-brand-primary",
6505
+ mode: "light",
6506
+ fg: 1000,
6507
+ bg: 200,
6508
+ min: AA_TEXT
6509
+ }, {
6510
+ what: "fg-brand-primary-alt on bg-brand-primary",
6511
+ mode: "light",
6512
+ fg: 900,
6513
+ bg: 200,
6514
+ min: AA_TEXT
6515
+ },
6516
+ // Light: `--bg-brand-secondary` is the filled step 800.
6517
+ {
6518
+ what: "fg-brand-secondary on bg-brand-secondary",
6519
+ mode: "light",
6520
+ fg: 100,
6521
+ bg: 800,
6522
+ min: AA_TEXT
6523
+ }, {
6524
+ what: "fg-brand-secondary-alt on bg-brand-secondary",
6525
+ mode: "light",
6526
+ fg: 200,
6527
+ bg: 800,
6528
+ min: AA_TEXT
6529
+ },
6530
+ // Light: `--border-focus` is step 600, and it has to read on both neutral
6531
+ // surfaces a focused control can sit on.
6532
+ {
6533
+ what: "border-focus on bg-float",
6534
+ mode: "light",
6535
+ fg: 600,
6536
+ bg: "#fcfcfc",
6537
+ min: AA_LARGE
6538
+ }, {
6539
+ what: "border-focus on bg-canvas",
6540
+ mode: "light",
6541
+ fg: 600,
6542
+ bg: "#f4f4f5",
6543
+ min: AA_LARGE
6544
+ },
6545
+ // The outlines drawn on the customer's own page. Not a Val surface at all,
6546
+ // which is why the grounds are pure white and pure black: the site behind
6547
+ // them can be anything.
6548
+ {
6549
+ what: "bg-page-selection on a white site",
6550
+ mode: "light",
6551
+ fg: 600,
6552
+ bg: "#ffffff",
6553
+ min: AA_LARGE
6554
+ }, {
6555
+ what: "bg-page-selection on a black site",
6556
+ mode: "light",
6557
+ fg: 600,
6558
+ bg: "#000000",
6559
+ min: AA_LARGE
6560
+ },
6561
+ // Dark inverts which step is the surface and which is the text.
6562
+ {
6563
+ what: "fg-brand-primary on bg-brand-primary",
6564
+ mode: "dark",
6565
+ fg: 100,
6566
+ bg: 800,
6567
+ min: AA_TEXT
6568
+ }, {
6569
+ what: "fg-brand-primary-alt on bg-brand-primary",
6570
+ mode: "dark",
6571
+ fg: 200,
6572
+ bg: 800,
6573
+ min: AA_TEXT
6574
+ }, {
6575
+ what: "fg-brand-secondary on bg-brand-secondary",
6576
+ mode: "dark",
6577
+ fg: 1000,
6578
+ bg: 200,
6579
+ min: AA_TEXT
6580
+ }, {
6581
+ what: "fg-brand-secondary-alt on bg-brand-secondary",
6582
+ mode: "dark",
6583
+ fg: 900,
6584
+ bg: 200,
6585
+ min: AA_TEXT
6586
+ },
6587
+ // Dark's focus ring lightens rather than mirroring: step 600 is nearly
6588
+ // invisible against the dark canvas.
6589
+ {
6590
+ what: "border-focus on bg-canvas",
6591
+ mode: "dark",
6592
+ fg: 400,
6593
+ bg: "#08080a",
6594
+ min: AA_LARGE
6595
+ }, {
6596
+ what: "border-focus on bg-float",
6597
+ mode: "dark",
6598
+ fg: 400,
6599
+ bg: "#131316",
6600
+ min: AA_LARGE
6601
+ }];
6602
+
6603
+ /** Resolve one side of a {@link BRAND_CONTRAST_PAIRS} entry against a ramp. */
6604
+ function resolvePairGround(ground, ramp) {
6605
+ return typeof ground === "number" ? ramp[ground] : ground;
6606
+ }
6607
+
6608
+ export { AA_LARGE, AA_TEXT, Api, BRAND_CONTRAST_PAIRS, BinaryFileRef, CodeOptions, ColorOptions, DEFAULT_ERROR_MESSAGE, DateOptions, ENCODE_DEFAULTS, ENCODE_MIME_TYPE_OF, FileOptions, HistoricalCommit, HistoricalModule, HistoricalPatchSet, HistoryError, ImageEncodeOption, ImageOptions, JSONValue, JSON_ENTRIES_BATCH_MAX, ModuleComparison, NEVER_ENCODED, ParentRef$1 as ParentRef, Patch, PatchBlock, PatchGroup, PatchId, PatchJSON, RAMP_STEPS, RemirrorBr, RemirrorBulletList, RemirrorHeading, RemirrorImage, RemirrorJSON, RemirrorLinkMark, RemirrorListItem, RemirrorOrderedList, RemirrorParagraph, RemirrorText, RemirrorTextMark, RestoreVerdict, RichTextOptions, SCHEMA_SOURCE_FIXES, SerializedArraySchema, SerializedBooleanSchema, SerializedCodeSchema, SerializedColorSchema, SerializedDateSchema, SerializedDateTimeSchema, SerializedDiscriminatedUnionSchema, SerializedEnumSchema, SerializedFileSchema, SerializedImageSchema, SerializedKeyOfSchema, SerializedLiteralSchema, SerializedLocaleSchema, SerializedNumberSchema, SerializedObjectSchema, SerializedRecordSchema, SerializedRichTextSchema, SerializedRouteSchema, SerializedSchema, SerializedSettingsSchema, SerializedStringSchema, SharedValConfig, VAL_ENABLE_COOKIE_NAME, VAL_GREEN_RAMP, VAL_SESSION_COOKIE, VAL_STATE_COOKIE, ValCommit, ValDeployment, accentRamp, buildDuplicatePatch, buildEmptyAtPathPatch, buildFileRefValue, buildImageGalleryPatch, buildRemoveImageGalleryEntryPatch, buildSearchIndex, checkCompatibility, chooseEncoded, concatModulePath, contrastRatio, createRegExpFromPattern, createSearchIndex, createValClient, declaredLocales, describeContainerAtPath, emptyOf, explainIncompatible, filterBlockingValidationErrors, filterRoutesByPatterns, findSimilar, fitWithin, flattenRichText, getErrorMessageFromUnknownJson, getFilenameFromRef, getInlineImgInfo, getKeyOfRecordAt, getRefParts, getRoutesOf, getRoutesWithModulePaths, getSourceAt, indexModule, isMimeTypeAccepted, isRemoteSchema, isSchemaSourceFixError, isSkippedSource, keyOfRecordPath, levenshtein, localeAt, newestCommitSha, partitionValidationErrors, performSearch, relativeLuminance, remirrorToRichTextSource, removeModule, resolveEncodeSettings, resolvePairGround, resolveSchemaSourceFixForError, resolveSchemaSourceFixes, resolveSerializedSchemaAtPath, richTextToRemirror, safeParsePatch, sourcePathOfChild, themeCustomProperties, toolNames, traverseSchemaSource, urlOf, validateRoutePatterns, withExtension };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@valbuild/shared",
3
- "version": "0.127.0",
3
+ "version": "0.129.0",
4
4
  "private": false,
5
5
  "description": "Val shared types and utilities",
6
6
  "repository": {
@@ -37,7 +37,7 @@
37
37
  "flexsearch": "^0.8.212",
38
38
  "zod": "^4.4.3",
39
39
  "zod-validation-error": "^5.0.0",
40
- "@valbuild/core": "0.127.0"
40
+ "@valbuild/core": "0.129.0"
41
41
  },
42
42
  "files": [
43
43
  "CHANGELOG.md",