@valbuild/core 0.100.0 → 0.102.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.
@@ -922,6 +922,7 @@ var StringSchema = /*#__PURE__*/function (_Schema) {
922
922
  var _this$options4, _this$options5, _this$options6, _this$customValidateF, _this$customValidateF2;
923
923
  return {
924
924
  type: "string",
925
+ render: this.renderInput ? true : undefined,
925
926
  options: {
926
927
  maxLength: (_this$options4 = this.options) === null || _this$options4 === void 0 ? void 0 : _this$options4.maxLength,
927
928
  minLength: (_this$options5 = this.options) === null || _this$options5 === void 0 ? void 0 : _this$options5.minLength,
@@ -947,7 +948,12 @@ var StringSchema = /*#__PURE__*/function (_Schema) {
947
948
  }
948
949
  }, {
949
950
  key: "executeRender",
950
- value: function executeRender(sourcePath, src) {
951
+ value: function executeRender(sourcePath, src,
952
+ // Accepted and unused: a string's render is a static layout hint
953
+ // (`{as:"textarea"}` / `{as:"code",language}`) with no closure behind it, so
954
+ // there is nothing a scope could prune. Declared so the signature is uniform
955
+ // across every schema rather than something a reader has to check.
956
+ _scope) {
951
957
  if (this.renderInput) {
952
958
  if (this.renderInput.as === "code") {
953
959
  return _defineProperty({}, sourcePath, {
@@ -1160,7 +1166,7 @@ var ObjectSchema = /*#__PURE__*/function (_Schema) {
1160
1166
  }
1161
1167
  }, {
1162
1168
  key: "executeRender",
1163
- value: function executeRender(sourcePath, src) {
1169
+ value: function executeRender(sourcePath, src, scope) {
1164
1170
  var res = {};
1165
1171
  if (src === null) {
1166
1172
  return res;
@@ -1171,7 +1177,13 @@ var ObjectSchema = /*#__PURE__*/function (_Schema) {
1171
1177
  continue;
1172
1178
  }
1173
1179
  var subPath = unsafeCreateSourcePath(sourcePath, _key3);
1174
- var itemResult = this.items[_key3]["executeRender"](subPath, itemSrc);
1180
+ // An object produces no render of its own, so it is pure recursion — and
1181
+ // pruning it is the whole contribution: a field deep in one branch stops
1182
+ // paying for every sibling branch's renders.
1183
+ if (scope !== undefined && !scope.wantsUnder(subPath)) {
1184
+ continue;
1185
+ }
1186
+ var itemResult = this.items[_key3]["executeRender"](subPath, itemSrc, scope);
1175
1187
  for (var keyS in itemResult) {
1176
1188
  var _key4 = keyS;
1177
1189
  res[_key4] = itemResult[_key4];
@@ -1313,6 +1325,7 @@ var ArraySchema = /*#__PURE__*/function (_Schema) {
1313
1325
  type: "array",
1314
1326
  item: this.item["executeSerialize"](),
1315
1327
  opt: this.opt,
1328
+ render: this.renderInput ? true : undefined,
1316
1329
  customValidate: this.customValidateFunctions && ((_this$customValidateF = this.customValidateFunctions) === null || _this$customValidateF === void 0 ? void 0 : _this$customValidateF.length) > 0,
1317
1330
  readonly: this.isReadonly,
1318
1331
  hidden: this.isHidden,
@@ -1321,7 +1334,7 @@ var ArraySchema = /*#__PURE__*/function (_Schema) {
1321
1334
  }
1322
1335
  }, {
1323
1336
  key: "executeRender",
1324
- value: function executeRender(sourcePath, src) {
1337
+ value: function executeRender(sourcePath, src, scope) {
1325
1338
  var res = {};
1326
1339
  if (src === null) {
1327
1340
  return res;
@@ -1333,7 +1346,10 @@ var ArraySchema = /*#__PURE__*/function (_Schema) {
1333
1346
  continue;
1334
1347
  }
1335
1348
  var subPath = unsafeCreateSourcePath(sourcePath, key);
1336
- var itemResult = this.item["executeRender"](subPath, itemSrc);
1349
+ if (scope !== undefined && !scope.wantsUnder(subPath)) {
1350
+ continue;
1351
+ }
1352
+ var itemResult = this.item["executeRender"](subPath, itemSrc, scope);
1337
1353
  for (var keyS in itemResult) {
1338
1354
  var _key = keyS;
1339
1355
  res[_key] = itemResult[_key];
@@ -1349,34 +1365,51 @@ var ArraySchema = /*#__PURE__*/function (_Schema) {
1349
1365
  message: "Unknown layout type: " + layout
1350
1366
  };
1351
1367
  }
1352
- try {
1353
- res[sourcePath] = {
1354
- status: "success",
1355
- data: {
1356
- layout: "list",
1357
- parent: "array",
1358
- items: src.map(function (val) {
1359
- // NB NB: display is actually defined by the user
1360
- var _select = select({
1361
- val: val
1362
- }),
1363
- title = _select.title,
1364
- subtitle = _select.subtitle,
1365
- image = _select.image;
1366
- return {
1367
- title: title,
1368
- subtitle: subtitle,
1369
- image: image
1370
- };
1371
- })
1372
- }
1373
- };
1374
- } catch (e) {
1375
- res[sourcePath] = {
1376
- status: "error",
1377
- message: e instanceof Error ? e.message : "Unknown error"
1378
- };
1368
+ // The whole list when the LIST is what is being shown; only the wanted
1369
+ // rows when it is not. `select` is the user's closure and the real expense
1370
+ // — a list view asks for the container and gets every row, a single field
1371
+ // asks for its own path and costs one call.
1372
+ // Non-null once, as a value, rather than a boolean the compiler cannot
1373
+ // tie back to `scope`: no scope and a scope that wants this exact path
1374
+ // both mean the whole list, and anything else is a window.
1375
+ var window = scope !== undefined && !scope.wants(sourcePath) ? scope : null;
1376
+ var items = [];
1377
+ for (var index = 0; index < src.length; index++) {
1378
+ if (window !== null && !window.wantsUnder(unsafeCreateSourcePath(sourcePath, index))) {
1379
+ continue;
1380
+ }
1381
+ // Per ITEM, not per list, matching what `record` already does: `select`
1382
+ // is user code, and one row whose data trips it up must not take out the
1383
+ // whole list. Before scoping, one throwing row produced an error at the
1384
+ // container and no items at all.
1385
+ try {
1386
+ // NB NB: display is actually defined by the user
1387
+ var _select = select({
1388
+ val: src[index]
1389
+ }),
1390
+ title = _select.title,
1391
+ subtitle = _select.subtitle,
1392
+ image = _select.image;
1393
+ items.push([index, {
1394
+ title: title,
1395
+ subtitle: subtitle,
1396
+ image: image
1397
+ }]);
1398
+ } catch (e) {
1399
+ res[unsafeCreateSourcePath(sourcePath, index)] = {
1400
+ status: "error",
1401
+ message: e instanceof Error ? e.message : "Unknown error"
1402
+ };
1403
+ }
1379
1404
  }
1405
+ res[sourcePath] = {
1406
+ status: "success",
1407
+ data: {
1408
+ layout: "list",
1409
+ parent: "array",
1410
+ items: items
1411
+ }
1412
+ };
1380
1413
  }
1381
1414
  return res;
1382
1415
  }
@@ -1950,7 +1983,7 @@ var UnionSchema = /*#__PURE__*/function (_Schema) {
1950
1983
  }
1951
1984
  }, {
1952
1985
  key: "executeRender",
1953
- value: function executeRender(sourcePath, src) {
1986
+ value: function executeRender(sourcePath, src, scope) {
1954
1987
  var res = {};
1955
1988
  if (src === null) {
1956
1989
  return res;
@@ -1972,7 +2005,7 @@ var UnionSchema = /*#__PURE__*/function (_Schema) {
1972
2005
  return false;
1973
2006
  });
1974
2007
  if (thisSchema) {
1975
- var itemResult = thisSchema["executeRender"](sourcePath, src);
2008
+ var itemResult = thisSchema["executeRender"](sourcePath, src, scope);
1976
2009
  for (var keyS in itemResult) {
1977
2010
  var _key3 = keyS;
1978
2011
  res[_key3] = itemResult[_key3];
@@ -3859,6 +3892,7 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
3859
3892
  item: this.item["executeSerialize"](),
3860
3893
  key: (_this$keySchema = this.keySchema) === null || _this$keySchema === void 0 ? void 0 : _this$keySchema["executeSerialize"](),
3861
3894
  opt: this.opt,
3895
+ render: this.renderInput ? true : undefined,
3862
3896
  router: (_this$currentRouter = this.currentRouter) === null || _this$currentRouter === void 0 ? void 0 : _this$currentRouter.getRouterId(),
3863
3897
  customValidate: this.customValidateFunctions && ((_this$customValidateF = this.customValidateFunctions) === null || _this$customValidateF === void 0 ? void 0 : _this$customValidateF.length) > 0,
3864
3898
  jsonValues: this.isJsonValues ? true : undefined,
@@ -3891,7 +3925,7 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
3891
3925
  }
3892
3926
  }, {
3893
3927
  key: "executeRender",
3894
- value: function executeRender(sourcePath, src) {
3928
+ value: function executeRender(sourcePath, src, scope) {
3895
3929
  var res = {};
3896
3930
  if (src === null) {
3897
3931
  return res;
@@ -3909,7 +3943,10 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
3909
3943
  continue;
3910
3944
  }
3911
3945
  var subPath = unsafeCreateSourcePath(sourcePath, _key);
3912
- var itemResult = this.item["executeRender"](subPath, itemSrc);
3946
+ if (scope !== undefined && !scope.wantsUnder(subPath)) {
3947
+ continue;
3948
+ }
3949
+ var itemResult = this.item["executeRender"](subPath, itemSrc, scope);
3913
3950
  for (var keyS in itemResult) {
3914
3951
  var _key2 = keyS;
3915
3952
  res[_key2] = itemResult[_key2];
@@ -3925,6 +3962,9 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
3925
3962
  message: "Unknown layout type: " + layout
3926
3963
  };
3927
3964
  }
3965
+ // See the same block in `array`: the whole record when the record is what
3966
+ // is being shown, only the wanted entries when it is not.
3967
+ var window = scope !== undefined && !scope.wants(sourcePath) ? scope : null;
3928
3968
  var items = [];
3929
3969
  for (var _i = 0, _Object$entries = Object.entries(src); _i < _Object$entries.length; _i++) {
3930
3970
  var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
@@ -3933,6 +3973,9 @@ var RecordSchema = /*#__PURE__*/function (_Schema) {
3933
3973
  if (isJson(val)) {
3934
3974
  continue; // as above: nothing to select from an un-loaded entry
3935
3975
  }
3976
+ if (window !== null && !window.wantsUnder(unsafeCreateSourcePath(sourcePath, _key3))) {
3977
+ continue;
3978
+ }
3936
3979
  // Per KEY, not per record: `select` is user code, and one entry whose
3937
3980
  // data trips it up must not take out the whole list.
3938
3981
  try {
@@ -5065,8 +5108,8 @@ var keyOf = function keyOf(valModule) {
5065
5108
  * ```
5066
5109
  */
5067
5110
  var files = function files(options) {
5068
- var _options$directory, _options$remote;
5069
- var directory = (_options$directory = options.directory) !== null && _options$directory !== void 0 ? _options$directory : "/public/val";
5111
+ var _options$remote;
5112
+ var directory = options.directory;
5070
5113
  var itemSchema = new ObjectSchema({
5071
5114
  mimeType: new StringSchema({}, false)
5072
5115
  }, false);
@@ -5454,6 +5497,647 @@ var datetime = function datetime(options) {
5454
5497
  return new DateTimeSchema(options);
5455
5498
  };
5456
5499
 
5500
+ /**
5501
+ * Parsing / formatting of the CSS color strings stored by `s.color()`.
5502
+ *
5503
+ * Colors are stored as plain CSS strings so that they can be used directly in
5504
+ * `style` attributes and CSS custom properties. The schema decides which family
5505
+ * (`hex`, `rgb`, `hsl` or `oklch`) the string is written in, this module does
5506
+ * the conversion between them.
5507
+ *
5508
+ * All colors are sRGB: `oklch` is only used as an output notation, so any
5509
+ * `oklch` value that falls outside the sRGB gamut is clipped when converted
5510
+ * back to the other formats.
5511
+ */
5512
+
5513
+ var COLOR_FORMATS = ["hex", "rgb", "hsl", "oklch"];
5514
+ var DEFAULT_COLOR_FORMAT = "hsl";
5515
+
5516
+ /**
5517
+ * A color, normalized to sRGB.
5518
+ *
5519
+ * `r`, `g` and `b` are 0-255 (not rounded), `a` is 0-1.
5520
+ */
5521
+
5522
+ /**
5523
+ * Which color notation a string uses, or `null` if it is not a notation we
5524
+ * recognize. This looks at the syntax only: `detectColorFormat` says nothing
5525
+ * about whether the value actually parses (`hsl(nope)` is still `"hsl"`).
5526
+ */
5527
+ function detectColorFormat(value) {
5528
+ var trimmed = value.trim().toLowerCase();
5529
+ if (trimmed.startsWith("#")) {
5530
+ return "hex";
5531
+ }
5532
+ if (trimmed.startsWith("rgb(") || trimmed.startsWith("rgba(")) {
5533
+ return "rgb";
5534
+ }
5535
+ if (trimmed.startsWith("hsl(") || trimmed.startsWith("hsla(")) {
5536
+ return "hsl";
5537
+ }
5538
+ if (trimmed.startsWith("oklch(")) {
5539
+ return "oklch";
5540
+ }
5541
+ return null;
5542
+ }
5543
+ function clamp(value, min, max) {
5544
+ if (Number.isNaN(value)) {
5545
+ return min;
5546
+ }
5547
+ return Math.min(max, Math.max(min, value));
5548
+ }
5549
+
5550
+ /**
5551
+ * Splits the inside of a CSS color function into components.
5552
+ *
5553
+ * Handles both the legacy comma syntax (`rgb(1, 2, 3, 0.5)`) and the modern
5554
+ * space syntax (`rgb(1 2 3 / 0.5)`), returning the alpha (if any) separately.
5555
+ */
5556
+ function splitComponents(args) {
5557
+ var slashParts = args.split("/");
5558
+ if (slashParts.length > 2) {
5559
+ return null;
5560
+ }
5561
+ var alpha = slashParts.length === 2 ? slashParts[1].trim() : null;
5562
+ var head = slashParts[0].trim();
5563
+ if (head === "") {
5564
+ return null;
5565
+ }
5566
+ var components;
5567
+ if (head.includes(",")) {
5568
+ if (alpha !== null) {
5569
+ // Mixing the comma and the slash syntax is not valid CSS
5570
+ return null;
5571
+ }
5572
+ components = head.split(",").map(function (part) {
5573
+ return part.trim();
5574
+ });
5575
+ if (components.length === 4) {
5576
+ alpha = components[3];
5577
+ components = components.slice(0, 3);
5578
+ }
5579
+ } else {
5580
+ components = head.split(/\s+/);
5581
+ }
5582
+ if (components.length !== 3 || components.some(function (part) {
5583
+ return part === "";
5584
+ })) {
5585
+ return null;
5586
+ }
5587
+ if (alpha === "") {
5588
+ return null;
5589
+ }
5590
+ return {
5591
+ components: components,
5592
+ alpha: alpha
5593
+ };
5594
+ }
5595
+ function parseNumber(value) {
5596
+ if (!/^[+-]?(\d+\.?\d*|\.\d+)$/.test(value)) {
5597
+ return null;
5598
+ }
5599
+ var parsed = Number(value);
5600
+ return Number.isFinite(parsed) ? parsed : null;
5601
+ }
5602
+
5603
+ /**
5604
+ * A `<number>` or a `<percentage>`, where 100% maps to `percentageBasis`.
5605
+ */
5606
+ function parseNumberOrPercentage(value, percentageBasis) {
5607
+ if (value.endsWith("%")) {
5608
+ var percentage = parseNumber(value.slice(0, -1));
5609
+ return percentage === null ? null : percentage / 100 * percentageBasis;
5610
+ }
5611
+ return parseNumber(value);
5612
+ }
5613
+ function parsePercentage(value) {
5614
+ if (!value.endsWith("%")) {
5615
+ return null;
5616
+ }
5617
+ return parseNumber(value.slice(0, -1));
5618
+ }
5619
+ function parseAlpha(value) {
5620
+ if (value === null) {
5621
+ return 1;
5622
+ }
5623
+ if (value === "none") {
5624
+ return 0;
5625
+ }
5626
+ var parsed = parseNumberOrPercentage(value, 1);
5627
+ return parsed === null ? null : clamp(parsed, 0, 1);
5628
+ }
5629
+ function parseHue(value) {
5630
+ var match = /^([+-]?(?:\d+\.?\d*|\.\d+))(deg|grad|rad|turn)?$/.exec(value);
5631
+ if (!match) {
5632
+ return null;
5633
+ }
5634
+ var amount = Number(match[1]);
5635
+ switch (match[2]) {
5636
+ case "grad":
5637
+ return amount / 400 * 360;
5638
+ case "rad":
5639
+ return amount * 180 / Math.PI;
5640
+ case "turn":
5641
+ return amount * 360;
5642
+ default:
5643
+ return amount;
5644
+ }
5645
+ }
5646
+ function normalizeHue(hue) {
5647
+ var wrapped = hue % 360;
5648
+ return wrapped < 0 ? wrapped + 360 : wrapped;
5649
+ }
5650
+ function parseHex(value) {
5651
+ var hex = value.slice(1);
5652
+ if (!/^[0-9a-f]+$/i.test(hex)) {
5653
+ return null;
5654
+ }
5655
+ var expand = function expand(_char) {
5656
+ return parseInt(_char + _char, 16);
5657
+ };
5658
+ var pair = function pair(index) {
5659
+ return parseInt(hex.slice(index, index + 2), 16);
5660
+ };
5661
+ if (hex.length === 3 || hex.length === 4) {
5662
+ return {
5663
+ r: expand(hex[0]),
5664
+ g: expand(hex[1]),
5665
+ b: expand(hex[2]),
5666
+ a: hex.length === 4 ? expand(hex[3]) / 255 : 1
5667
+ };
5668
+ }
5669
+ if (hex.length === 6 || hex.length === 8) {
5670
+ return {
5671
+ r: pair(0),
5672
+ g: pair(2),
5673
+ b: pair(4),
5674
+ a: hex.length === 8 ? pair(6) / 255 : 1
5675
+ };
5676
+ }
5677
+ return null;
5678
+ }
5679
+ function extractFunctionArgs(value, name) {
5680
+ if (!value.startsWith(name + "(") || !value.endsWith(")")) {
5681
+ return null;
5682
+ }
5683
+ return value.slice(name.length + 1, -1);
5684
+ }
5685
+ function parseRgb(value) {
5686
+ var _extractFunctionArgs;
5687
+ var args = (_extractFunctionArgs = extractFunctionArgs(value, "rgba")) !== null && _extractFunctionArgs !== void 0 ? _extractFunctionArgs : extractFunctionArgs(value, "rgb");
5688
+ if (args === null) {
5689
+ return null;
5690
+ }
5691
+ var split = splitComponents(args);
5692
+ if (!split) {
5693
+ return null;
5694
+ }
5695
+ var channels = split.components.map(function (component) {
5696
+ return component === "none" ? 0 : parseNumberOrPercentage(component, 255);
5697
+ });
5698
+ var alpha = parseAlpha(split.alpha);
5699
+ if (channels.some(function (channel) {
5700
+ return channel === null;
5701
+ }) || alpha === null) {
5702
+ return null;
5703
+ }
5704
+ return {
5705
+ r: clamp(channels[0], 0, 255),
5706
+ g: clamp(channels[1], 0, 255),
5707
+ b: clamp(channels[2], 0, 255),
5708
+ a: alpha
5709
+ };
5710
+ }
5711
+ function parseHsl(value) {
5712
+ var _extractFunctionArgs2, _parsePercentage, _parsePercentage2;
5713
+ var args = (_extractFunctionArgs2 = extractFunctionArgs(value, "hsla")) !== null && _extractFunctionArgs2 !== void 0 ? _extractFunctionArgs2 : extractFunctionArgs(value, "hsl");
5714
+ if (args === null) {
5715
+ return null;
5716
+ }
5717
+ var split = splitComponents(args);
5718
+ if (!split) {
5719
+ return null;
5720
+ }
5721
+ var _split$components = _slicedToArray(split.components, 3),
5722
+ rawHue = _split$components[0],
5723
+ rawSaturation = _split$components[1],
5724
+ rawLightness = _split$components[2];
5725
+ var hue = rawHue === "none" ? 0 : parseHue(rawHue);
5726
+ // CSS requires percentages for s/l in hsl(), but the modern syntax also
5727
+ // allows bare numbers, so accept both.
5728
+ var saturation = rawSaturation === "none" ? 0 : (_parsePercentage = parsePercentage(rawSaturation)) !== null && _parsePercentage !== void 0 ? _parsePercentage : parseNumber(rawSaturation);
5729
+ var lightness = rawLightness === "none" ? 0 : (_parsePercentage2 = parsePercentage(rawLightness)) !== null && _parsePercentage2 !== void 0 ? _parsePercentage2 : parseNumber(rawLightness);
5730
+ var alpha = parseAlpha(split.alpha);
5731
+ if (hue === null || saturation === null || lightness === null || alpha === null) {
5732
+ return null;
5733
+ }
5734
+ return _objectSpread2(_objectSpread2({}, hslToRgb(normalizeHue(hue), clamp(saturation, 0, 100), clamp(lightness, 0, 100))), {}, {
5735
+ a: alpha
5736
+ });
5737
+ }
5738
+ function parseOklch(value) {
5739
+ var args = extractFunctionArgs(value, "oklch");
5740
+ if (args === null) {
5741
+ return null;
5742
+ }
5743
+ var split = splitComponents(args);
5744
+ if (!split) {
5745
+ return null;
5746
+ }
5747
+ var _split$components2 = _slicedToArray(split.components, 3),
5748
+ rawLightness = _split$components2[0],
5749
+ rawChroma = _split$components2[1],
5750
+ rawHue = _split$components2[2];
5751
+ // Lightness is a <number> in 0-1 or a <percentage>, chroma is a <number>
5752
+ // where 0.4 is roughly the maximum, or a <percentage> where 100% = 0.4.
5753
+ var lightness = rawLightness === "none" ? 0 : parseNumberOrPercentage(rawLightness, 1);
5754
+ var chroma = rawChroma === "none" ? 0 : parseNumberOrPercentage(rawChroma, 0.4);
5755
+ var hue = rawHue === "none" ? 0 : parseHue(rawHue);
5756
+ var alpha = parseAlpha(split.alpha);
5757
+ if (lightness === null || chroma === null || hue === null || alpha === null) {
5758
+ return null;
5759
+ }
5760
+ return _objectSpread2(_objectSpread2({}, oklchToRgb(clamp(lightness, 0, 1), Math.max(0, chroma), normalizeHue(hue))), {}, {
5761
+ a: alpha
5762
+ });
5763
+ }
5764
+
5765
+ /**
5766
+ * Parses a CSS color string into sRGB channels.
5767
+ *
5768
+ * Accepts hex (3, 4, 6 and 8 digits), `rgb()` / `rgba()`, `hsl()` / `hsla()`
5769
+ * and `oklch()`, in both the legacy comma and the modern space syntax. Returns
5770
+ * `null` if the value is not a color we can parse - notably: named colors
5771
+ * (`red`), `color()`, `lab()` and `color-mix()` are not supported.
5772
+ */
5773
+ function parseColor(value) {
5774
+ var normalized = value.trim().toLowerCase();
5775
+ if (normalized === "") {
5776
+ return null;
5777
+ }
5778
+ switch (detectColorFormat(normalized)) {
5779
+ case "hex":
5780
+ return parseHex(normalized);
5781
+ case "rgb":
5782
+ return parseRgb(normalized);
5783
+ case "hsl":
5784
+ return parseHsl(normalized);
5785
+ case "oklch":
5786
+ return parseOklch(normalized);
5787
+ default:
5788
+ return null;
5789
+ }
5790
+ }
5791
+
5792
+ /**
5793
+ * Rounds to at most `decimals` decimals, without trailing zeros.
5794
+ */
5795
+ function round(value, decimals) {
5796
+ var factor = Math.pow(10, decimals);
5797
+ // `|| 0` normalizes -0 to 0
5798
+ return Math.round(value * factor) / factor || 0;
5799
+ }
5800
+ function toHexPair(value) {
5801
+ return clamp(Math.round(value), 0, 255).toString(16).padStart(2, "0");
5802
+ }
5803
+ function rgbToHsl(r, g, b) {
5804
+ var red = r / 255;
5805
+ var green = g / 255;
5806
+ var blue = b / 255;
5807
+ var max = Math.max(red, green, blue);
5808
+ var min = Math.min(red, green, blue);
5809
+ var delta = max - min;
5810
+ var l = (max + min) / 2;
5811
+ if (delta === 0) {
5812
+ return {
5813
+ h: 0,
5814
+ s: 0,
5815
+ l: l * 100
5816
+ };
5817
+ }
5818
+ var s = delta / (1 - Math.abs(2 * l - 1));
5819
+ var h;
5820
+ if (max === red) {
5821
+ h = (green - blue) / delta % 6;
5822
+ } else if (max === green) {
5823
+ h = (blue - red) / delta + 2;
5824
+ } else {
5825
+ h = (red - green) / delta + 4;
5826
+ }
5827
+ return {
5828
+ h: normalizeHue(h * 60),
5829
+ s: s * 100,
5830
+ l: l * 100
5831
+ };
5832
+ }
5833
+ function hslToRgb(h, s, l) {
5834
+ var saturation = s / 100;
5835
+ var lightness = l / 100;
5836
+ var c = (1 - Math.abs(2 * lightness - 1)) * saturation;
5837
+ var hPrime = normalizeHue(h) / 60;
5838
+ var x = c * (1 - Math.abs(hPrime % 2 - 1));
5839
+ var m = lightness - c / 2;
5840
+ var rgb;
5841
+ if (hPrime < 1) {
5842
+ rgb = [c, x, 0];
5843
+ } else if (hPrime < 2) {
5844
+ rgb = [x, c, 0];
5845
+ } else if (hPrime < 3) {
5846
+ rgb = [0, c, x];
5847
+ } else if (hPrime < 4) {
5848
+ rgb = [0, x, c];
5849
+ } else if (hPrime < 5) {
5850
+ rgb = [x, 0, c];
5851
+ } else {
5852
+ rgb = [c, 0, x];
5853
+ }
5854
+ return {
5855
+ r: (rgb[0] + m) * 255,
5856
+ g: (rgb[1] + m) * 255,
5857
+ b: (rgb[2] + m) * 255
5858
+ };
5859
+ }
5860
+ function srgbToLinear(channel) {
5861
+ var c = channel / 255;
5862
+ return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
5863
+ }
5864
+ function linearToSrgb(channel) {
5865
+ var c = channel <= 0.0031308 ? channel * 12.92 : 1.055 * Math.pow(channel, 1 / 2.4) - 0.055;
5866
+ return clamp(c * 255, 0, 255);
5867
+ }
5868
+
5869
+ /**
5870
+ * sRGB to Oklch, using Björn Ottosson's Oklab matrices.
5871
+ */
5872
+ function rgbToOklch(r, g, b) {
5873
+ var lr = srgbToLinear(r);
5874
+ var lg = srgbToLinear(g);
5875
+ var lb = srgbToLinear(b);
5876
+ var _long = Math.cbrt(0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb);
5877
+ var medium = Math.cbrt(0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb);
5878
+ var _short = Math.cbrt(0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb);
5879
+ var lightness = 0.2104542553 * _long + 0.793617785 * medium - 0.0040720468 * _short;
5880
+ var a = 1.9779984951 * _long - 2.428592205 * medium + 0.4505937099 * _short;
5881
+ var bComponent = 0.0259040371 * _long + 0.7827717662 * medium - 0.808675766 * _short;
5882
+ var chroma = Math.sqrt(a * a + bComponent * bComponent);
5883
+ // Achromatic colors have an undefined hue: report 0 rather than the noise
5884
+ // that atan2 of two near-zero numbers produces.
5885
+ var hue = chroma < 1e-6 ? 0 : normalizeHue(Math.atan2(bComponent, a) * 180 / Math.PI);
5886
+ return {
5887
+ l: clamp(lightness, 0, 1),
5888
+ c: chroma,
5889
+ h: hue
5890
+ };
5891
+ }
5892
+
5893
+ /**
5894
+ * Oklch to sRGB. Out of gamut colors are clipped per channel.
5895
+ */
5896
+ function oklchToRgb(l, c, h) {
5897
+ var hueRadians = normalizeHue(h) * Math.PI / 180;
5898
+ var a = c * Math.cos(hueRadians);
5899
+ var bComponent = c * Math.sin(hueRadians);
5900
+ var _long2 = Math.pow(l + 0.3963377774 * a + 0.2158037573 * bComponent, 3);
5901
+ var medium = Math.pow(l - 0.1055613458 * a - 0.0638541728 * bComponent, 3);
5902
+ var _short2 = Math.pow(l - 0.0894841775 * a - 1.291485548 * bComponent, 3);
5903
+ return {
5904
+ r: linearToSrgb(4.0767416621 * _long2 - 3.3077115913 * medium + 0.2309699292 * _short2),
5905
+ g: linearToSrgb(-1.2684380046 * _long2 + 2.6097574011 * medium - 0.3413193965 * _short2),
5906
+ b: linearToSrgb(-0.0041960863 * _long2 - 0.7034186147 * medium + 1.707614701 * _short2)
5907
+ };
5908
+ }
5909
+
5910
+ /**
5911
+ * Serializes a color to the canonical string of the given format.
5912
+ *
5913
+ * The modern, space separated CSS syntax is used for the color functions
5914
+ * (`rgb(255 0 0 / 0.5)`). Alpha is only included when it is not fully opaque.
5915
+ */
5916
+ function formatColor(color, format) {
5917
+ var alpha = clamp(color.a, 0, 1);
5918
+ var alphaSuffix = alpha >= 1 ? "" : " / ".concat(round(alpha, 4));
5919
+ switch (format) {
5920
+ case "hex":
5921
+ {
5922
+ var hex = "#".concat(toHexPair(color.r)).concat(toHexPair(color.g)).concat(toHexPair(color.b));
5923
+ return alpha >= 1 ? hex : "".concat(hex).concat(toHexPair(alpha * 255));
5924
+ }
5925
+ case "rgb":
5926
+ {
5927
+ var r = clamp(Math.round(color.r), 0, 255);
5928
+ var g = clamp(Math.round(color.g), 0, 255);
5929
+ var b = clamp(Math.round(color.b), 0, 255);
5930
+ return "rgb(".concat(r, " ").concat(g, " ").concat(b).concat(alphaSuffix, ")");
5931
+ }
5932
+ case "hsl":
5933
+ {
5934
+ var _rgbToHsl = rgbToHsl(color.r, color.g, color.b),
5935
+ h = _rgbToHsl.h,
5936
+ s = _rgbToHsl.s,
5937
+ l = _rgbToHsl.l;
5938
+ return "hsl(".concat(round(h, 2), " ").concat(round(s, 2), "% ").concat(round(l, 2), "%").concat(alphaSuffix, ")");
5939
+ }
5940
+ case "oklch":
5941
+ {
5942
+ var _rgbToOklch = rgbToOklch(color.r, color.g, color.b),
5943
+ _l = _rgbToOklch.l,
5944
+ c = _rgbToOklch.c,
5945
+ _h = _rgbToOklch.h;
5946
+ return "oklch(".concat(round(_l, 4), " ").concat(round(c, 4), " ").concat(round(_h, 2)).concat(alphaSuffix, ")");
5947
+ }
5948
+ }
5949
+ }
5950
+
5951
+ /**
5952
+ * Converts a color string to the canonical string of the given format, or
5953
+ * `null` if the input cannot be parsed.
5954
+ */
5955
+ function convertColor(value, format) {
5956
+ var parsed = parseColor(value);
5957
+ if (parsed === null) {
5958
+ return null;
5959
+ }
5960
+ return formatColor(parsed, format);
5961
+ }
5962
+
5963
+ /**
5964
+ * `#rrggbb`, which is what `<input type="color">` uses as its value.
5965
+ */
5966
+ function colorToHex(color) {
5967
+ return "#".concat(toHexPair(color.r)).concat(toHexPair(color.g)).concat(toHexPair(color.b));
5968
+ }
5969
+
5970
+ /**
5971
+ * An example of the canonical output of each format, used in error messages so
5972
+ * that the expected syntax is obvious from the error alone.
5973
+ */
5974
+ var FORMAT_EXAMPLES = {
5975
+ hex: "#3b82f6",
5976
+ rgb: "rgb(59 130 246)",
5977
+ hsl: "hsl(217.22 91.22% 59.8%)",
5978
+ oklch: "oklch(0.6231 0.188 259.81)"
5979
+ };
5980
+ var ColorSchema = /*#__PURE__*/function (_Schema) {
5981
+ function ColorSchema(options) {
5982
+ var _this;
5983
+ var opt = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
5984
+ var customValidateFunctions = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];
5985
+ var isReadonly = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
5986
+ var isHidden = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
5987
+ var description = arguments.length > 5 ? arguments[5] : undefined;
5988
+ _classCallCheck(this, ColorSchema);
5989
+ _this = _callSuper(this, ColorSchema);
5990
+ _this.options = options;
5991
+ _this.opt = opt;
5992
+ _this.customValidateFunctions = customValidateFunctions;
5993
+ _this.isReadonly = isReadonly;
5994
+ _this.isHidden = isHidden;
5995
+ _this.description = description;
5996
+ return _this;
5997
+ }
5998
+ _inherits(ColorSchema, _Schema);
5999
+ return _createClass(ColorSchema, [{
6000
+ key: "describe",
6001
+ value: function describe(description) {
6002
+ return new ColorSchema(this.options, this.opt, this.customValidateFunctions, this.isReadonly, this.isHidden, description !== null && description !== void 0 ? description : undefined);
6003
+ }
6004
+ }, {
6005
+ key: "validate",
6006
+ value: function validate(validationFunction) {
6007
+ return new ColorSchema(this.options, this.opt, [].concat(_toConsumableArray(this.customValidateFunctions), [validationFunction]), this.isReadonly, this.isHidden, this.description);
6008
+ }
6009
+ }, {
6010
+ key: "executeCustomValidateAt",
6011
+ value: function executeCustomValidateAt(path, src) {
6012
+ return this.executeCustomValidateFunctions(src, this.customValidateFunctions, {
6013
+ path: path
6014
+ });
6015
+ }
6016
+ }, {
6017
+ key: "executeValidate",
6018
+ value: function executeValidate(path, src) {
6019
+ var _this$options$format, _this$options;
6020
+ var errors = this.executeCustomValidateFunctions(src, this.customValidateFunctions, {
6021
+ path: path
6022
+ });
6023
+ if (this.opt && (src === null || src === undefined)) {
6024
+ return errors.length > 0 ? _defineProperty({}, path, errors) : false;
6025
+ }
6026
+ if (typeof src !== "string") {
6027
+ errors.push({
6028
+ message: "Expected 'string', got '".concat(_typeof(src), "'"),
6029
+ value: src
6030
+ });
6031
+ return _defineProperty({}, path, errors);
6032
+ }
6033
+ var expectedFormat = (_this$options$format = (_this$options = this.options) === null || _this$options === void 0 ? void 0 : _this$options.format) !== null && _this$options$format !== void 0 ? _this$options$format : DEFAULT_COLOR_FORMAT;
6034
+ var parsed = parseColor(src);
6035
+ if (parsed === null) {
6036
+ errors.push({
6037
+ message: "Invalid color: '".concat(src, "'. Expected a CSS color in the '").concat(expectedFormat, "' format (e.g. '").concat(FORMAT_EXAMPLES[expectedFormat], "')"),
6038
+ value: src
6039
+ });
6040
+ } else {
6041
+ var _this$options3;
6042
+ var actualFormat = detectColorFormat(src);
6043
+ if (actualFormat !== expectedFormat) {
6044
+ var _this$options2;
6045
+ // Drop alpha from the SUGGESTION when alpha is not enabled, the same way
6046
+ // the field's commit does. Keeping it would suggest a value that fails
6047
+ // the alpha check immediately below, so following the advice would not
6048
+ // clear the error.
6049
+ var suggestion = formatColor((_this$options2 = this.options) !== null && _this$options2 !== void 0 && _this$options2.alpha ? parsed : _objectSpread2(_objectSpread2({}, parsed), {}, {
6050
+ a: 1
6051
+ }), expectedFormat);
6052
+ errors.push({
6053
+ message: "Expected a color in the '".concat(expectedFormat, "' format (e.g. '").concat(FORMAT_EXAMPLES[expectedFormat], "'), got '").concat(src, "'. Did you mean '").concat(suggestion, "'?"),
6054
+ value: src
6055
+ });
6056
+ }
6057
+ if (!((_this$options3 = this.options) !== null && _this$options3 !== void 0 && _this$options3.alpha) && parsed.a < 1) {
6058
+ errors.push({
6059
+ message: "Color '".concat(src, "' has an alpha channel, but alpha is not enabled. Use s.color({ alpha: true }) to allow transparency"),
6060
+ value: src
6061
+ });
6062
+ }
6063
+ }
6064
+ if (errors.length > 0) {
6065
+ return _defineProperty({}, path, errors);
6066
+ }
6067
+ return false;
6068
+ }
6069
+ }, {
6070
+ key: "executeAssert",
6071
+ value: function executeAssert(path, src) {
6072
+ if (this.opt && src === null) {
6073
+ return {
6074
+ success: true,
6075
+ data: src
6076
+ };
6077
+ }
6078
+ if (src === null) {
6079
+ return {
6080
+ success: false,
6081
+ errors: _defineProperty({}, path, [{
6082
+ message: "Expected 'string', got 'null'",
6083
+ typeError: true
6084
+ }])
6085
+ };
6086
+ }
6087
+ if (typeof src !== "string") {
6088
+ return {
6089
+ success: false,
6090
+ errors: _defineProperty({}, path, [{
6091
+ message: "Expected 'string', got '".concat(_typeof(src), "'"),
6092
+ typeError: true
6093
+ }])
6094
+ };
6095
+ }
6096
+ return {
6097
+ success: true,
6098
+ data: src
6099
+ };
6100
+ }
6101
+ }, {
6102
+ key: "nullable",
6103
+ value: function nullable() {
6104
+ return new ColorSchema(this.options, true, [], this.isReadonly, this.isHidden, this.description);
6105
+ }
6106
+ }, {
6107
+ key: "readonly",
6108
+ value: function readonly() {
6109
+ return new ColorSchema(this.options, this.opt, this.customValidateFunctions, true, this.isHidden, this.description);
6110
+ }
6111
+ }, {
6112
+ key: "hidden",
6113
+ value: function hidden() {
6114
+ return new ColorSchema(this.options, this.opt, this.customValidateFunctions, this.isReadonly, true, this.description);
6115
+ }
6116
+ }, {
6117
+ key: "executeSerialize",
6118
+ value: function executeSerialize() {
6119
+ var _this$customValidateF;
6120
+ return {
6121
+ type: "color",
6122
+ opt: this.opt,
6123
+ options: this.options,
6124
+ customValidate: this.customValidateFunctions && ((_this$customValidateF = this.customValidateFunctions) === null || _this$customValidateF === void 0 ? void 0 : _this$customValidateF.length) > 0,
6125
+ readonly: this.isReadonly,
6126
+ hidden: this.isHidden,
6127
+ description: this.description
6128
+ };
6129
+ }
6130
+ }, {
6131
+ key: "executeRender",
6132
+ value: function executeRender() {
6133
+ return {};
6134
+ }
6135
+ }]);
6136
+ }(Schema);
6137
+ var color = function color(options) {
6138
+ return new ColorSchema(options);
6139
+ };
6140
+
5457
6141
  function router(router, keyOrItem, maybeItem) {
5458
6142
  if (maybeItem) {
5459
6143
  return new RecordSchema(maybeItem, false, [], router, keyOrItem);
@@ -5478,8 +6162,9 @@ function router(router, keyOrItem, maybeItem) {
5478
6162
  /**
5479
6163
  * Define a collection of images.
5480
6164
  *
5481
- * All options are optional: `s.images()` accepts any image type (`"image/*"`) in
5482
- * the default `/public/val` directory, with nullable alt text and remote disabled.
6165
+ * `directory` is required — it decides where uploads land, so it is not something
6166
+ * to be inferred. The rest defaults: any image type (`"image/*"`), nullable alt
6167
+ * text, remote disabled.
5483
6168
  *
5484
6169
  * @example
5485
6170
  * ```typescript
@@ -5499,9 +6184,9 @@ function router(router, keyOrItem, maybeItem) {
5499
6184
  * ```
5500
6185
  */
5501
6186
  var images = function images(options) {
5502
- var _options$directory, _options$alt, _options$accept, _options$remote;
5503
- var directory = (_options$directory = options === null || options === void 0 ? void 0 : options.directory) !== null && _options$directory !== void 0 ? _options$directory : "/public/val";
5504
- var altSchema = (_options$alt = options === null || options === void 0 ? void 0 : options.alt) !== null && _options$alt !== void 0 ? _options$alt : string().nullable();
6187
+ var _options$alt, _options$accept, _options$remote;
6188
+ var directory = options.directory;
6189
+ var altSchema = (_options$alt = options.alt) !== null && _options$alt !== void 0 ? _options$alt : string().nullable();
5505
6190
  var itemSchema = new ObjectSchema({
5506
6191
  width: new NumberSchema(undefined, false),
5507
6192
  height: new NumberSchema(undefined, false),
@@ -5510,9 +6195,9 @@ var images = function images(options) {
5510
6195
  }, false);
5511
6196
  return new RecordSchema(itemSchema, false, [], null, null, {
5512
6197
  type: "images",
5513
- accept: (_options$accept = options === null || options === void 0 ? void 0 : options.accept) !== null && _options$accept !== void 0 ? _options$accept : "image/*",
6198
+ accept: (_options$accept = options.accept) !== null && _options$accept !== void 0 ? _options$accept : "image/*",
5514
6199
  directory: directory,
5515
- remote: (_options$remote = options === null || options === void 0 ? void 0 : options.remote) !== null && _options$remote !== void 0 ? _options$remote : false,
6200
+ remote: (_options$remote = options.remote) !== null && _options$remote !== void 0 ? _options$remote : false,
5516
6201
  altSchema: altSchema
5517
6202
  });
5518
6203
  };
@@ -5543,6 +6228,7 @@ function initSchema() {
5543
6228
  files: files,
5544
6229
  date: date,
5545
6230
  datetime: datetime,
6231
+ color: color,
5546
6232
  route: route,
5547
6233
  router: router,
5548
6234
  images: images
@@ -6112,7 +6798,7 @@ function collectObjectRecursive(item, collector) {
6112
6798
  * Extracts schemas and sources from a ValModules registry and computes the
6113
6799
  * deterministic SHAs that the server and client both use to detect changes.
6114
6800
  *
6115
- * Used by ValOps on the server and by ValSyncEngine on the client so the
6801
+ * Used by ValOps on the server and by `HostStore` on the client so the
6116
6802
  * hash algorithm has a single source of truth.
6117
6803
  */
6118
6804
  function extractValModules(_x) {
@@ -6288,6 +6974,104 @@ function _extractValModules() {
6288
6974
  return _extractValModules.apply(this, arguments);
6289
6975
  }
6290
6976
 
6977
+ /**
6978
+ * Does this schema store any file remotely?
6979
+ *
6980
+ * The one answer to that question. There used to be two — `hasRemoteFileSchema`
6981
+ * in the server, gating whether `/save` demands remote credentials, and
6982
+ * `findRequiredRemoteFiles` in the Studio, gating the `/remote/settings` fetch —
6983
+ * and they disagreed about `s.images({ remote: true })`: the Studio's counted a
6984
+ * remote media record, the server's did not, because a media collection
6985
+ * serializes as a `record` of metadata rather than as an image schema.
6986
+ *
6987
+ * The server's answer was the wrong one, and silently so. With it false,
6988
+ * `/save` runs `saveOrUploadFiles` in `skip-remote` mode, and that mode does not
6989
+ * merely skip the upload — it drops every remote descriptor without an error. The
6990
+ * commit then lands a remote ref with no bytes behind it: a broken image, in the
6991
+ * repository, with nothing anywhere saying why.
6992
+ *
6993
+ * ## Why it stays a walk over the SERIALIZED schema
6994
+ *
6995
+ * Both callers have a `SerializedSchema` and not a `Schema`: the server reads
6996
+ * them out of `getSchemas()`, and the Studio is handed them by `host.receive`.
6997
+ * Asking the schema classes directly would mean a method on every one of them,
6998
+ * and the two places that need the answer cannot call it.
6999
+ *
7000
+ * ## Throwing
7001
+ *
7002
+ * The `never` assignment at the end is the point of the default branch: adding a
7003
+ * schema type without teaching this function about it must not compile. The throw
7004
+ * is what happens if one arrives anyway — which for a caller reading schemas out
7005
+ * of its own process should be impossible. A caller that cannot afford to throw
7006
+ * (the Studio, whose whole UI is downstream of this) catches it; returning `false`
7007
+ * instead would reintroduce exactly the silent skip described above.
7008
+ */
7009
+ function hasRemoteFileSchema(schema) {
7010
+ if (schema.type === "file" || schema.type === "image") {
7011
+ return !!schema.remote;
7012
+ } else if (schema.type === "richtext") {
7013
+ var _schema$options;
7014
+ if (_typeof((_schema$options = schema.options) === null || _schema$options === void 0 || (_schema$options = _schema$options.inline) === null || _schema$options === void 0 ? void 0 : _schema$options.img) === "object") {
7015
+ return hasRemoteFileSchema(schema.options.inline.img);
7016
+ }
7017
+ return false;
7018
+ } else if (schema.type === "array") {
7019
+ return hasRemoteFileSchema(schema.item);
7020
+ } else if (schema.type === "record") {
7021
+ /**
7022
+ * A media collection — `s.images()` / `s.files()` — is the case both of the
7023
+ * old functions were written around, and the one they disagreed on.
7024
+ *
7025
+ * It has no file or image schema inside it to find: `item` is an object of
7026
+ * width/height/mimeType/alt, and the file itself is named by the record's
7027
+ * KEY. So `remote` on the record is the only place the answer is written
7028
+ * down, and recursing into `item` can only ever say no.
7029
+ */
7030
+ if (schema.mediaType && schema.remote) {
7031
+ return true;
7032
+ }
7033
+ return hasRemoteFileSchema(schema.item);
7034
+ } else if (schema.type === "object") {
7035
+ for (var key in schema.items) {
7036
+ var hasRemoteFile = hasRemoteFileSchema(schema.items[key]);
7037
+ if (hasRemoteFile) {
7038
+ return true;
7039
+ }
7040
+ }
7041
+ return false;
7042
+ } else if (schema.type === "union") {
7043
+ var unionStringSchema = _typeof(schema.key) === "object" && schema.key.type === "literal" ? schema : undefined;
7044
+ var unionObjectSchema = typeof schema.key === "string" ? schema : undefined;
7045
+ if (unionStringSchema) {
7046
+ // A string union's items are literals, so there is nothing to look inside.
7047
+ return false;
7048
+ }
7049
+ if (unionObjectSchema) {
7050
+ var _iterator = result._createForOfIteratorHelper(unionObjectSchema.items),
7051
+ _step;
7052
+ try {
7053
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
7054
+ var item = _step.value;
7055
+ var _hasRemoteFile = hasRemoteFileSchema(item);
7056
+ if (_hasRemoteFile) {
7057
+ return true;
7058
+ }
7059
+ }
7060
+ } catch (err) {
7061
+ _iterator.e(err);
7062
+ } finally {
7063
+ _iterator.f();
7064
+ }
7065
+ }
7066
+ return false;
7067
+ } else if (schema.type === "boolean" || schema.type === "number" || schema.type === "string" || schema.type === "literal" || schema.type === "date" || schema.type === "dateTime" || schema.type === "color" || schema.type === "keyOf" || schema.type === "route") {
7068
+ return false;
7069
+ } else {
7070
+ var exhaustiveCheck = schema;
7071
+ throw new Error("Unexpected schema: ".concat(JSON.stringify(exhaustiveCheck)));
7072
+ }
7073
+ }
7074
+
6291
7075
  var DEFAULT_VAL_REMOTE_HOST = "https://remote.val.build";
6292
7076
  function convertRemoteSource(src) {
6293
7077
  if (src !== null && src !== void 0 && src.patch_id) {
@@ -7265,6 +8049,8 @@ function deserializeSchemaImpl(serialized) {
7265
8049
  return new DateSchema(serialized.options, serialized.opt, [], false, false, serialized.description);
7266
8050
  case "dateTime":
7267
8051
  return new DateTimeSchema(serialized.options, serialized.opt, [], false, false, serialized.description);
8052
+ case "color":
8053
+ return new ColorSchema(serialized.options, serialized.opt, [], false, false, serialized.description);
7268
8054
  default:
7269
8055
  {
7270
8056
  var exhaustiveCheck = serialized;
@@ -7278,6 +8064,94 @@ function deserializeSchemaImpl(serialized) {
7278
8064
  }
7279
8065
  }
7280
8066
 
8067
+ // TODO: we want to change layout -> as to be more consistent across the board
8068
+
8069
+ // Main render type:
8070
+
8071
+ //
8072
+
8073
+ /**
8074
+ * Which paths a render is being computed FOR.
8075
+ *
8076
+ * `executeRender` takes a whole module, so one request has always walked every
8077
+ * node in it and run every `select` closure — for `handboka`, with `select` at
8078
+ * two nested array levels, that is every chapter and every section to serve one
8079
+ * visible row. A scope is what makes the walk proportional to what is being
8080
+ * looked at instead of to the project.
8081
+ *
8082
+ * Two questions, because a container and its items need different answers:
8083
+ *
8084
+ * - {@link RenderScope.wants} — is a render AT this exact path wanted? A
8085
+ * container answers `true` here when the whole of it is being shown, and its
8086
+ * list render is then computed in full.
8087
+ * - {@link RenderScope.wantsUnder} — could anything at or below this path be
8088
+ * wanted? Recursion is pruned where this is `false`, and a container whose own
8089
+ * path is not wanted but which has wanted descendants renders a WINDOW: only
8090
+ * the items that were asked for. That is the case a single visible row is, and
8091
+ * it is why {@link ListArrayRender} carries indices.
8092
+ *
8093
+ * Absent scope means the whole module, which is what every existing caller
8094
+ * passes and what every existing caller got.
8095
+ */
8096
+
8097
+ /**
8098
+ * A scope covering exactly `paths` and their subtrees.
8099
+ *
8100
+ * Compares SEGMENTS rather than string prefixes. A source path's module path is
8101
+ * a quoted, dot-joined encoding (`?p="a".1."b"`), so `startsWith` gets the
8102
+ * ancestor test wrong wherever a key is a prefix of a sibling key or contains a
8103
+ * dot or a quote — `"title"` against `"titles"` being the cheapest example.
8104
+ * Segments are parsed once here, not per comparison.
8105
+ */
8106
+ function renderScope(paths) {
8107
+ var wanted = paths.map(function (path) {
8108
+ var _splitModuleFilePathA = splitModuleFilePathAndModulePath(path),
8109
+ _splitModuleFilePathA2 = _slicedToArray(_splitModuleFilePathA, 2),
8110
+ moduleFilePath = _splitModuleFilePathA2[0],
8111
+ modulePath = _splitModuleFilePathA2[1];
8112
+ return {
8113
+ moduleFilePath: moduleFilePath,
8114
+ segments: modulePath === "" ? [] : splitModulePath(modulePath)
8115
+ };
8116
+ });
8117
+ var parse = function parse(path) {
8118
+ var _splitModuleFilePathA3 = splitModuleFilePathAndModulePath(path),
8119
+ _splitModuleFilePathA4 = _slicedToArray(_splitModuleFilePathA3, 2),
8120
+ moduleFilePath = _splitModuleFilePathA4[0],
8121
+ modulePath = _splitModuleFilePathA4[1];
8122
+ return {
8123
+ moduleFilePath: moduleFilePath,
8124
+ segments: modulePath === "" ? [] : splitModulePath(modulePath)
8125
+ };
8126
+ };
8127
+ var isPrefix = function isPrefix(prefix, of) {
8128
+ if (prefix.length > of.length) return false;
8129
+ for (var i = 0; i < prefix.length; i++) {
8130
+ if (prefix[i] !== of[i]) return false;
8131
+ }
8132
+ return true;
8133
+ };
8134
+ return {
8135
+ wants: function wants(path) {
8136
+ var at = parse(path);
8137
+ return wanted.some(function (entry) {
8138
+ return entry.moduleFilePath === at.moduleFilePath && entry.segments.length === at.segments.length && isPrefix(entry.segments, at.segments);
8139
+ });
8140
+ },
8141
+ wantsUnder: function wantsUnder(path) {
8142
+ var at = parse(path);
8143
+ return wanted.some(function (entry) {
8144
+ return entry.moduleFilePath === at.moduleFilePath && (
8145
+ // Either direction: a wanted path below `path` means recurse into it,
8146
+ // and a wanted path ABOVE it means the whole subtree was asked for.
8147
+ isPrefix(at.segments, entry.segments) || isPrefix(entry.segments, at.segments));
8148
+ });
8149
+ }
8150
+ };
8151
+ }
8152
+
8153
+ // TODO: improve this so that we do not get RawString and string, only string. Are there other things?
8154
+
7281
8155
  var externalPageRouter = {
7282
8156
  getRouterId: function getRouterId() {
7283
8157
  return "external-url-router";
@@ -7551,6 +8425,13 @@ var Internal = {
7551
8425
  joinModuleFilePathAndModulePath: joinModuleFilePathAndModulePath,
7552
8426
  nextAppRouter: nextAppRouter,
7553
8427
  externalPageRouter: externalPageRouter,
8428
+ color: {
8429
+ parseColor: parseColor,
8430
+ formatColor: formatColor,
8431
+ convertColor: convertColor,
8432
+ detectColorFormat: detectColorFormat,
8433
+ colorToHex: colorToHex
8434
+ },
7554
8435
  remote: {
7555
8436
  createRemoteRef: createRemoteRef,
7556
8437
  getValidationBasis: getValidationBasis,
@@ -7633,7 +8514,10 @@ function tryJsonParse(str) {
7633
8514
 
7634
8515
  exports.ArraySchema = ArraySchema;
7635
8516
  exports.BooleanSchema = BooleanSchema;
8517
+ exports.COLOR_FORMATS = COLOR_FORMATS;
8518
+ exports.ColorSchema = ColorSchema;
7636
8519
  exports.DEFAULT_APP_HOST = DEFAULT_APP_HOST;
8520
+ exports.DEFAULT_COLOR_FORMAT = DEFAULT_COLOR_FORMAT;
7637
8521
  exports.DEFAULT_CONTENT_HOST = DEFAULT_CONTENT_HOST;
7638
8522
  exports.DEFAULT_VAL_REMOTE_HOST = DEFAULT_VAL_REMOTE_HOST;
7639
8523
  exports.DateSchema = DateSchema;
@@ -7667,6 +8551,8 @@ exports.derefPatch = derefPatch;
7667
8551
  exports.deserializeSchema = deserializeSchema;
7668
8552
  exports.extractValModules = extractValModules;
7669
8553
  exports.getSourcePathFromRoute = getSourcePathFromRoute;
8554
+ exports.hasRemoteFileSchema = hasRemoteFileSchema;
7670
8555
  exports.initVal = initVal;
7671
8556
  exports.modules = modules;
8557
+ exports.renderScope = renderScope;
7672
8558
  exports.splitModuleFilePathAndModulePath = splitModuleFilePathAndModulePath;