slexkit 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/LICENSE +21 -21
  3. package/README.md +4 -3
  4. package/README.zh-CN.md +4 -3
  5. package/dist/ai/llms-authoring.txt +2 -0
  6. package/dist/ai/llms-capabilities.txt +126 -0
  7. package/dist/ai/llms-components.txt +29 -7
  8. package/dist/ai/llms-full.txt +1909 -153
  9. package/dist/ai/llms-runtime.txt +18 -13
  10. package/dist/ai/llms.txt +22 -1
  11. package/dist/ai/slexkit-ai-manifest.json +717 -62
  12. package/dist/base.css +359 -359
  13. package/dist/chunks/{accordion-cfjyxw93.js → button-53ccjq5p.js} +11 -11
  14. package/dist/chunks/{accordion-nw12ytps.js → button-cr1fhsa7.js} +48 -2
  15. package/dist/chunks/{accordion-5f0nvjjm.js → button-dsftwzvg.js} +4 -3
  16. package/dist/chunks/{accordion-hzyrngd6.js → button-faf563xf.js} +2 -2
  17. package/dist/chunks/{accordion-ehnhpeca.js → button-jxv4c65t.js} +2 -2
  18. package/dist/chunks/{accordion-cw5r75jm.js → button-xv2dz7vn.js} +1 -1
  19. package/dist/chunks/{accordion-830dw78f.js → button-z5yv24ks.js} +2 -2
  20. package/dist/components/accordion.js +2 -2
  21. package/dist/components/badge.js +2 -2
  22. package/dist/components/button.css +101 -101
  23. package/dist/components/button.js +3 -3
  24. package/dist/components/callout.js +4 -4
  25. package/dist/components/card.js +2 -2
  26. package/dist/components/checkbox.js +3 -2
  27. package/dist/components/choice.css +151 -151
  28. package/dist/components/code-block.js +2 -2
  29. package/dist/components/collapsible.js +2 -2
  30. package/dist/components/column.js +1 -1
  31. package/dist/components/content.css +273 -250
  32. package/dist/components/display.css +1 -1
  33. package/dist/components/divider.js +2 -2
  34. package/dist/components/grid.js +1 -1
  35. package/dist/components/index.js +13994 -172
  36. package/dist/components/input.css +786 -852
  37. package/dist/components/input.js +34 -144
  38. package/dist/components/link.js +2 -2
  39. package/dist/components/progress.js +2 -2
  40. package/dist/components/radio-group.js +3 -2
  41. package/dist/components/row.js +1 -1
  42. package/dist/components/section.js +2 -2
  43. package/dist/components/select.css +175 -181
  44. package/dist/components/select.js +3 -3
  45. package/dist/components/slider.css +125 -116
  46. package/dist/components/slider.js +2 -2
  47. package/dist/components/specs.js +34 -1
  48. package/dist/components/stat.js +2 -2
  49. package/dist/components/submit.css +8 -8
  50. package/dist/components/submit.js +1 -1
  51. package/dist/components/switch.css +105 -105
  52. package/dist/components/switch.js +4 -3
  53. package/dist/components/table.js +4 -4
  54. package/dist/components/tabs.css +95 -95
  55. package/dist/components/tabs.js +4 -4
  56. package/dist/components/text-input.css +26 -95
  57. package/dist/components/text.js +13 -1
  58. package/dist/components/toast.js +4 -4
  59. package/dist/components/tooling.css +0 -1
  60. package/dist/components/tooling.js +73 -8
  61. package/dist/runtime.cjs +1610 -17
  62. package/dist/runtime.js +1609 -16
  63. package/dist/slexkit.cjs +28191 -13865
  64. package/dist/slexkit.css +1525 -1569
  65. package/dist/slexkit.js +28190 -13864
  66. package/dist/tooling.js +117 -11
  67. package/dist/types/components/svelte/helpers.d.ts +8 -1
  68. package/dist/types/engine/capabilities.d.ts +54 -0
  69. package/dist/types/engine/index.d.ts +6 -0
  70. package/dist/types/engine/secure-runtime.d.ts +9 -1
  71. package/dist/types/engine/stdlib.d.ts +30 -0
  72. package/dist/types/engine/types.d.ts +1 -0
  73. package/dist/types/engine/validation.d.ts +28 -0
  74. package/dist/types/index.d.ts +6 -3
  75. package/dist/types/runtime.d.ts +6 -3
  76. package/dist/types/version.d.ts +2 -2
  77. package/dist/umd/slexkit.tooling.umd.js +45084 -44775
  78. package/dist/umd/slexkit.umd.js +28191 -13865
  79. package/package.json +5 -3
  80. package/skills/slexkit-host-integration/SKILL.md +1 -1
  81. package/src/components/svelte/content/Formula.svelte +27 -0
  82. package/src/components/svelte/content/Table.svelte +1 -1
  83. package/src/components/svelte/display/Text.svelte +14 -1
  84. package/src/components/svelte/helpers.ts +56 -1
  85. package/src/components/svelte/input/Checkbox.svelte +1 -1
  86. package/src/components/svelte/input/Input.svelte +0 -110
  87. package/src/components/svelte/input/RadioGroup.svelte +1 -1
  88. package/src/components/svelte/input/Select.svelte +2 -2
  89. package/src/components/svelte/input/Switch.svelte +2 -2
  90. package/src/components/svelte/input/Tabs.svelte +7 -7
  91. package/src/components/svelte/tooling/PlaygroundMarkdown.svelte +84 -2
  92. package/src/styles/components/button.css +101 -101
  93. package/src/styles/components/choice.css +152 -152
  94. package/src/styles/components/select.css +175 -181
  95. package/src/styles/components/slider.css +125 -116
  96. package/src/styles/components/submit.css +8 -8
  97. package/src/styles/components/switch.css +105 -105
  98. package/src/styles/components/tabs.css +95 -95
  99. package/src/styles/components/text-input.css +26 -95
  100. package/src/styles/content.css +274 -251
  101. package/src/styles/display.css +1 -1
  102. package/src/styles/input.css +8 -8
  103. package/src/styles/layout.css +360 -360
  104. package/src/styles/tooling.css +0 -1
package/dist/runtime.cjs CHANGED
@@ -39,7 +39,9 @@ var __export = (target, all) => {
39
39
  // src/runtime.ts
40
40
  var exports_runtime = {};
41
41
  __export(exports_runtime, {
42
+ validateSlexSource: () => validateSlexSourceApi,
42
43
  startSlexKitSandboxRunner: () => startSlexKitSandboxRunnerApi,
44
+ slexkitStd: () => slexkitStdApi,
43
45
  setSlexKitRuntimeUrl: () => setSlexKitRuntimeUrlApi,
44
46
  register: () => registerApi,
45
47
  parseSlexSource: () => parseSlexSourceApi,
@@ -583,6 +585,163 @@ function isEngineeringNumberResult(value) {
583
585
  return !!value && typeof value === "object" && "raw" in value && "number" in value && "valid" in value && "prefix" in value && "unit" in value && "normalized" in value;
584
586
  }
585
587
 
588
+ // src/engine/stdlib.ts
589
+ function toNumber(value) {
590
+ return Number(value);
591
+ }
592
+ function finiteNumber(value, fallback = 0) {
593
+ const parsed = toNumber(value);
594
+ return Number.isFinite(parsed) ? parsed : fallback;
595
+ }
596
+ function digits(value, fallback) {
597
+ const parsed = Math.trunc(finiteNumber(value, fallback));
598
+ return Math.max(0, Math.min(20, parsed));
599
+ }
600
+ function finiteValues(values) {
601
+ if (!Array.isArray(values))
602
+ return [];
603
+ return values.map(toNumber).filter(Number.isFinite);
604
+ }
605
+ function formatFixed(value, digitCount = 2) {
606
+ const parsed = toNumber(value);
607
+ return Number.isFinite(parsed) ? parsed.toFixed(digitCount) : "NaN";
608
+ }
609
+ function locale(value) {
610
+ return typeof value === "string" && value.trim() ? value : "en-US";
611
+ }
612
+ function currencyCode(value) {
613
+ return typeof value === "string" && /^[A-Za-z]{3}$/.test(value) ? value.toUpperCase() : "USD";
614
+ }
615
+ function formatNumber(value, digitCount, localeName, notation, style, currency, minimumFractionDigits = 0) {
616
+ const parsed = toNumber(value);
617
+ if (!Number.isFinite(parsed))
618
+ return "NaN";
619
+ return new Intl.NumberFormat(localeName, {
620
+ maximumFractionDigits: digitCount,
621
+ minimumFractionDigits,
622
+ notation,
623
+ style,
624
+ currency
625
+ }).format(parsed);
626
+ }
627
+ function freezeDeep(value) {
628
+ for (const child of Object.values(value)) {
629
+ if (child && typeof child === "object")
630
+ freezeDeep(child);
631
+ }
632
+ return Object.freeze(value);
633
+ }
634
+ var slexkitStd = freezeDeep({
635
+ math: {
636
+ clamp(value, min, max) {
637
+ const lower = finiteNumber(min);
638
+ const upper = finiteNumber(max);
639
+ const parsed = finiteNumber(value);
640
+ return Math.min(Math.max(parsed, Math.min(lower, upper)), Math.max(lower, upper));
641
+ },
642
+ round(value, digitValue = 0) {
643
+ const factor = 10 ** digits(digitValue, 0);
644
+ return Math.round(finiteNumber(value) * factor) / factor;
645
+ },
646
+ safeDivide(numerator, denominator, fallback = 0) {
647
+ const divisor = toNumber(denominator);
648
+ if (!Number.isFinite(divisor) || divisor === 0)
649
+ return finiteNumber(fallback);
650
+ const quotient = toNumber(numerator) / divisor;
651
+ return Number.isFinite(quotient) ? quotient : finiteNumber(fallback);
652
+ },
653
+ percent(part, total, digitValue = 1) {
654
+ const ratio = slexkitStd.math.safeDivide(part, total, NaN) * 100;
655
+ return slexkitStd.math.round(ratio, digitValue);
656
+ },
657
+ lerp(start, end, t) {
658
+ const a = finiteNumber(start);
659
+ return a + (finiteNumber(end) - a) * finiteNumber(t);
660
+ }
661
+ },
662
+ stats: {
663
+ sum(values) {
664
+ return finiteValues(values).reduce((total, value) => total + value, 0);
665
+ },
666
+ mean(values) {
667
+ const numbers = finiteValues(values);
668
+ return numbers.length ? slexkitStd.stats.sum(numbers) / numbers.length : NaN;
669
+ },
670
+ min(values) {
671
+ const numbers = finiteValues(values);
672
+ return numbers.length ? Math.min(...numbers) : NaN;
673
+ },
674
+ max(values) {
675
+ const numbers = finiteValues(values);
676
+ return numbers.length ? Math.max(...numbers) : NaN;
677
+ },
678
+ median(values) {
679
+ const numbers = finiteValues(values).sort((a, b) => a - b);
680
+ if (!numbers.length)
681
+ return NaN;
682
+ const middle = Math.floor(numbers.length / 2);
683
+ return numbers.length % 2 ? numbers[middle] : (numbers[middle - 1] + numbers[middle]) / 2;
684
+ }
685
+ },
686
+ format: {
687
+ fixed(value, digitValue = 2) {
688
+ return formatFixed(value, digits(digitValue, 2));
689
+ },
690
+ number(value, digitValue = 0, localeName = "en-US") {
691
+ return formatNumber(value, digits(digitValue, 0), locale(localeName));
692
+ },
693
+ compact(value, digitValue = 1, localeName = "en-US") {
694
+ return formatNumber(value, digits(digitValue, 1), locale(localeName), "compact");
695
+ },
696
+ percent(ratio, digitValue = 1) {
697
+ return `${formatFixed(finiteNumber(ratio) * 100, digits(digitValue, 1))}%`;
698
+ },
699
+ currency(value, currency = "USD", localeName = "en-US") {
700
+ return formatNumber(value, 2, locale(localeName), undefined, "currency", currencyCode(currency), 2);
701
+ }
702
+ },
703
+ units: {
704
+ withUnit(value, unit, digitValue = 2) {
705
+ const suffix = typeof unit === "string" ? unit : String(unit ?? "");
706
+ return `${formatFixed(value, digits(digitValue, 2))}${suffix ? ` ${suffix}` : ""}`;
707
+ },
708
+ bytes(value, digitValue = 1) {
709
+ const units = ["B", "KB", "MB", "GB", "TB", "PB"];
710
+ let amount = Math.abs(finiteNumber(value));
711
+ let index = 0;
712
+ while (amount >= 1024 && index < units.length - 1) {
713
+ amount /= 1024;
714
+ index += 1;
715
+ }
716
+ const sign = finiteNumber(value) < 0 ? -1 : 1;
717
+ return `${formatFixed(amount * sign, digits(digitValue, 1))} ${units[index]}`;
718
+ },
719
+ duration(ms, digitValue = 1) {
720
+ const value = finiteNumber(ms);
721
+ const abs = Math.abs(value);
722
+ if (abs < 1000)
723
+ return `${formatFixed(value, 0)} ms`;
724
+ if (abs < 60000)
725
+ return `${formatFixed(value / 1000, digits(digitValue, 1))} s`;
726
+ if (abs < 3600000)
727
+ return `${formatFixed(value / 60000, digits(digitValue, 1))} min`;
728
+ return `${formatFixed(value / 3600000, digits(digitValue, 1))} h`;
729
+ },
730
+ si(value, unit = "", digitValue = 2) {
731
+ const units = ["", "k", "M", "G", "T", "P"];
732
+ let amount = Math.abs(finiteNumber(value));
733
+ let index = 0;
734
+ while (amount >= 1000 && index < units.length - 1) {
735
+ amount /= 1000;
736
+ index += 1;
737
+ }
738
+ const sign = finiteNumber(value) < 0 ? -1 : 1;
739
+ const suffix = typeof unit === "string" ? unit : String(unit ?? "");
740
+ return `${formatFixed(amount * sign, digits(digitValue, 2))} ${units[index]}${suffix}`.trim();
741
+ }
742
+ }
743
+ });
744
+
586
745
  // src/engine/component-state.ts
587
746
  var IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
588
747
  var componentStateProxies = new WeakMap;
@@ -602,6 +761,9 @@ function isWritableComponent(type) {
602
761
  const mode = getComponentStateMode(type);
603
762
  return mode === "value" || mode === "checked" || mode === "enabled";
604
763
  }
764
+ function isReadableComponent(type) {
765
+ return getComponentStateMode(type) === "readable";
766
+ }
605
767
  function isStatefulComponent(type) {
606
768
  return getComponentStateMode(type) !== "none";
607
769
  }
@@ -718,7 +880,10 @@ function createGProxy(g, components, componentTypes) {
718
880
  function ensureComponentState(name, type, components, componentTypes) {
719
881
  if (!components[name])
720
882
  components[name] = {};
721
- componentTypes[name] = type;
883
+ const previousType = componentTypes[name] ?? "";
884
+ if (!(isWritableComponent(previousType) && isReadableComponent(type))) {
885
+ componentTypes[name] = type;
886
+ }
722
887
  return components[name];
723
888
  }
724
889
  function syncReadableComponentProp(type, state, propName, value) {
@@ -746,6 +911,9 @@ function syncReadableComponentProp(type, state, propName, value) {
746
911
  function syncComponentProps(type, name, props, components, componentTypes) {
747
912
  if (!name || !isStatefulComponent(type))
748
913
  return;
914
+ const previousType = componentTypes[name] ?? "";
915
+ if (isWritableComponent(previousType) && isReadableComponent(type))
916
+ return;
749
917
  const state = ensureComponentState(name, type, components, componentTypes);
750
918
  if (type === "input" && typeof props.type === "string") {
751
919
  assignInputType(state, props.type);
@@ -810,9 +978,26 @@ function seedStaticComponentState(type, state, props) {
810
978
  function warnDuplicateState(ns, name, currentType, currentPath, previous) {
811
979
  console.warn(`[SlexKit][${ns}] Component state '${name}' is declared more than once at ${previous.path} and ${currentPath}; state is shared by namespace and component name.`);
812
980
  if (previous.type !== currentType) {
813
- console.warn(`[SlexKit][${ns}] Component state '${name}' is used by multiple component types (${previous.type}, ${currentType}); the latest rendered type controls write behavior.`);
981
+ console.warn(`[SlexKit][${ns}] Component state '${name}' is used by multiple component types (${previous.type}, ${currentType}); use distinct names when components should not share state.`);
814
982
  }
815
983
  }
984
+ function dynamicStateBinding(type, props) {
985
+ const mode = getComponentStateMode(type);
986
+ const value = mode === "checked" ? props.$checked ?? props.$value : mode === "enabled" ? props.$enabled : props.$value;
987
+ return typeof value === "string" ? value.trim() : undefined;
988
+ }
989
+ function isMirroredValueControlPair(previousType, currentType) {
990
+ return previousType === "input" && currentType === "slider" || previousType === "slider" && currentType === "input";
991
+ }
992
+ function shouldWarnDuplicateState(currentType, currentBinding, previous) {
993
+ if (isReadableComponent(previous.type) && isReadableComponent(currentType)) {
994
+ return false;
995
+ }
996
+ if (currentBinding && previous.stateBinding === currentBinding && isMirroredValueControlPair(previous.type, currentType)) {
997
+ return false;
998
+ }
999
+ return true;
1000
+ }
816
1001
  function warnForState(ns, name, path) {
817
1002
  console.warn(`[SlexKit][${ns}] Component state '${name}' is used with $for at ${path}; repeated items share one namespace-level instance state.`);
818
1003
  }
@@ -826,11 +1011,15 @@ function prepareComponentStatesInner(layout, components, componentTypes, ns, see
826
1011
  const props = val;
827
1012
  const path = parentPath ? `${parentPath}.${key}` : key;
828
1013
  if (name && isStatefulComponent(type)) {
1014
+ const stateBinding = dynamicStateBinding(type, props);
829
1015
  const previous = seen.get(name);
830
- if (previous)
831
- warnDuplicateState(ns, name, type, path, previous);
832
- else
833
- seen.set(name, { type, path });
1016
+ if (previous) {
1017
+ if (shouldWarnDuplicateState(type, stateBinding, previous)) {
1018
+ warnDuplicateState(ns, name, type, path, previous);
1019
+ }
1020
+ } else {
1021
+ seen.set(name, { type, path, stateBinding });
1022
+ }
834
1023
  if (props.$for && isWritableComponent(type))
835
1024
  warnForState(ns, name, path);
836
1025
  const state = ensureComponentState(name, type, components, componentTypes);
@@ -843,9 +1032,15 @@ function prepareComponentStates(layout, components, componentTypes, ns) {
843
1032
  prepareComponentStatesInner(layout, components, componentTypes, ns, new Map);
844
1033
  }
845
1034
  function buildComponentEvalContext(g, components, componentTypes, api, forCtx) {
846
- const ctx = { g: createGProxy(g, components, componentTypes) };
1035
+ const ctx = {
1036
+ g: createGProxy(g, components, componentTypes),
1037
+ std: slexkitStd
1038
+ };
1039
+ const gKeys = new Set(Object.keys(rawRecord(g)));
847
1040
  for (const name of Object.keys(rawRecord(components))) {
848
- if (IDENTIFIER.test(name)) {
1041
+ if (name === "std" || name === "g" || name === "api")
1042
+ continue;
1043
+ if (IDENTIFIER.test(name) && !gKeys.has(name)) {
849
1044
  ctx[name] = publicComponentState(name, components[name], componentTypes);
850
1045
  }
851
1046
  }
@@ -853,6 +1048,8 @@ function buildComponentEvalContext(g, components, componentTypes, api, forCtx) {
853
1048
  ctx.api = api;
854
1049
  if (forCtx) {
855
1050
  for (const k of Object.keys(forCtx)) {
1051
+ if (k === "std")
1052
+ continue;
856
1053
  Object.defineProperty(ctx, k, {
857
1054
  get: () => {
858
1055
  const current = forCtx[k];
@@ -1023,6 +1220,7 @@ function renderIfNode(fullKey, props, container, g, components, componentTypes,
1023
1220
  };
1024
1221
  currentEl = renderWithFallback(renderer, innerProps, name, {
1025
1222
  g,
1223
+ std: slexkitStd,
1026
1224
  api,
1027
1225
  dir: options.dir,
1028
1226
  labels: options.labels,
@@ -1101,6 +1299,7 @@ function renderAndMountSlot(item, index, keyVal, indexSignal, revisionSignal, re
1101
1299
  };
1102
1300
  el = renderWithFallback(renderer, innerProps, name, {
1103
1301
  g,
1302
+ std: slexkitStd,
1104
1303
  api,
1105
1304
  dir: options.dir,
1106
1305
  labels: options.labels,
@@ -1250,6 +1449,7 @@ function renderNormalNode(fullKey, props, container, g, components, componentTyp
1250
1449
  };
1251
1450
  const el = renderWithFallback(renderer, nodeProps, name, {
1252
1451
  g,
1452
+ std: slexkitStd,
1253
1453
  api,
1254
1454
  dir: options.dir,
1255
1455
  labels: options.labels,
@@ -1603,7 +1803,7 @@ ${parseSource}
1603
1803
  var parseSlexKitDsl = parseSlexSource;
1604
1804
 
1605
1805
  // src/version.ts
1606
- var SLEXKIT_VERSION = "0.2.0";
1806
+ var SLEXKIT_VERSION = "0.3.1";
1607
1807
  var SLEX_PROTOCOL_VERSION = "0.1";
1608
1808
  var SLEXKIT_COMPONENTS_VERSION = SLEXKIT_VERSION;
1609
1809
  function getSlexKitInfo() {
@@ -1643,6 +1843,9 @@ function currentOrigin() {
1643
1843
  return "http://localhost";
1644
1844
  }
1645
1845
  function resolveUrl(url) {
1846
+ try {
1847
+ return new URL(url);
1848
+ } catch {}
1646
1849
  try {
1647
1850
  return new URL(url, currentOrigin());
1648
1851
  } catch {
@@ -2203,6 +2406,1325 @@ function createSecureRuntime(policy, hostAdapter) {
2203
2406
  }
2204
2407
  };
2205
2408
  }
2409
+ // src/components/spec-helpers.ts
2410
+ var SINCE = "0.1.0";
2411
+ function docs(type) {
2412
+ return {
2413
+ href: `/docs/components/${type}`,
2414
+ anchors: {
2415
+ api: `${type}-api-reference`,
2416
+ examples: `${type}-canonical-example`
2417
+ }
2418
+ };
2419
+ }
2420
+ function isSlexExpression(value) {
2421
+ return typeof value.namespace === "string" && typeof value.layout === "object" && value.layout !== null;
2422
+ }
2423
+ function example(type, propsOrSource, title = "Basic usage") {
2424
+ return {
2425
+ id: "basic",
2426
+ title,
2427
+ source: isSlexExpression(propsOrSource) ? {
2428
+ slex: propsOrSource.slex ?? SLEX_PROTOCOL_VERSION,
2429
+ ...propsOrSource
2430
+ } : {
2431
+ slex: SLEX_PROTOCOL_VERSION,
2432
+ namespace: `spec_${type.replaceAll("-", "_")}_basic`,
2433
+ layout: {
2434
+ [`${type}:demo`]: propsOrSource
2435
+ }
2436
+ }
2437
+ };
2438
+ }
2439
+ var semanticTones = ["info", "success", "warning", "danger", "muted"];
2440
+ var inputState = ["input", "slider", "select", "tabs", "radio-group"];
2441
+ var checkedState = ["checkbox"];
2442
+ var enabledState = ["switch"];
2443
+ var readableState = ["stat", "text", "progress", "badge", "callout", "code-block", "divider", "link", "table", "section"];
2444
+ function stateFor(type) {
2445
+ if (inputState.includes(type))
2446
+ return "value";
2447
+ if (checkedState.includes(type))
2448
+ return "checked";
2449
+ if (enabledState.includes(type))
2450
+ return "enabled";
2451
+ if (readableState.includes(type))
2452
+ return "readable";
2453
+ return "none";
2454
+ }
2455
+ function component(spec) {
2456
+ return {
2457
+ status: "ready",
2458
+ since: SINCE,
2459
+ docs: docs(spec.type),
2460
+ state: spec.state ?? stateFor(spec.type),
2461
+ ...spec
2462
+ };
2463
+ }
2464
+ var childContent = {
2465
+ allowed: true,
2466
+ description: "Nested component fields are rendered as child content in field order."
2467
+ };
2468
+ var noChildren = {
2469
+ allowed: false,
2470
+ description: "This component does not render nested child components."
2471
+ };
2472
+
2473
+ // src/components/entries/accordion.spec.ts
2474
+ var accordionSpec = component({
2475
+ type: "accordion",
2476
+ category: "Disclosure",
2477
+ title: "Accordion",
2478
+ summary: "Expandable grouped panels.",
2479
+ description: "Use accordion to reveal one or more sections from a compact list of panels.",
2480
+ props: {
2481
+ value: { type: "string | string[]", dynamic: true, description: "Current expanded item value; use an array when multiple is true." },
2482
+ multiple: { type: "boolean", default: false, description: "Allow multiple items to be expanded at the same time." },
2483
+ items: { type: "array", description: "Panel definitions with value, label, content, and optional icon." },
2484
+ "items[].icon": { type: "string", description: "Icon name shown before an item trigger label." },
2485
+ onchange: { type: "write-expression", description: "Write expression invoked when expanded items change." }
2486
+ },
2487
+ children: noChildren,
2488
+ examples: [example("accordion", {
2489
+ namespace: "doc_accordion_typical",
2490
+ layout: {
2491
+ "accordion:faq": {
2492
+ multiple: true,
2493
+ value: [
2494
+ "install"
2495
+ ],
2496
+ items: [
2497
+ {
2498
+ value: "install",
2499
+ label: "Install",
2500
+ icon: "download-simple",
2501
+ content: "Prepare dependencies."
2502
+ },
2503
+ {
2504
+ value: "review",
2505
+ label: "Review",
2506
+ icon: "check-circle",
2507
+ content: "Check the result."
2508
+ },
2509
+ {
2510
+ value: "ship",
2511
+ label: "Ship",
2512
+ icon: "rocket-launch",
2513
+ content: "Publish the change."
2514
+ }
2515
+ ]
2516
+ }
2517
+ }
2518
+ })]
2519
+ });
2520
+
2521
+ // src/components/entries/badge.spec.ts
2522
+ var badgeSpec = component({
2523
+ type: "badge",
2524
+ category: "Content",
2525
+ title: "Badge",
2526
+ summary: "Compact label for status or classification.",
2527
+ description: "Use badge to annotate nearby content with a short semantic label.",
2528
+ props: {
2529
+ label: { type: "string", dynamic: true, description: "Badge text." },
2530
+ text: { type: "string", dynamic: true, description: "Alias for label." },
2531
+ content: { type: "string", dynamic: true, description: "Alias for label." },
2532
+ icon: { type: "string", description: "Icon name shown before the badge label." },
2533
+ tone: { type: "string", values: semanticTones, default: "info", description: "Semantic tone applied to the badge." },
2534
+ variant: { type: "string", values: semanticTones, description: "Alias for tone." }
2535
+ },
2536
+ children: noChildren,
2537
+ examples: [example("badge", {
2538
+ namespace: "doc_badge_typical",
2539
+ layout: {
2540
+ "row:badges": {
2541
+ "badge:ready": {
2542
+ label: "ready",
2543
+ icon: "check-circle",
2544
+ tone: "success"
2545
+ },
2546
+ "badge:pending": {
2547
+ label: "pending",
2548
+ tone: "warning"
2549
+ },
2550
+ "badge:info": {
2551
+ label: "info",
2552
+ tone: "info"
2553
+ }
2554
+ }
2555
+ }
2556
+ })]
2557
+ });
2558
+
2559
+ // src/components/entries/button.spec.ts
2560
+ var buttonSpec = component({
2561
+ type: "button",
2562
+ category: "Action",
2563
+ title: "Button",
2564
+ summary: "Action trigger.",
2565
+ description: "Use button for explicit actions in interactive SlexKit layouts.",
2566
+ props: {
2567
+ label: { type: "string", dynamic: true, description: "Visible button text and accessible name." },
2568
+ icon: { type: "string", description: "Icon name shown before the label." },
2569
+ iconOnly: { type: "boolean", default: false, description: "Show only the icon while retaining label as the accessible name." },
2570
+ variant: { type: "string", values: ["primary", "secondary", "danger", "ghost"], default: "primary", description: "Semantic action variant." },
2571
+ disabled: { type: "boolean", default: false, dynamic: true, description: "Disable the action." },
2572
+ href: { type: "string", dynamic: true, description: "Render the button surface as a link to this URL." },
2573
+ target: { type: "string", description: "Link target used when href is present." },
2574
+ title: { type: "string", dynamic: true, description: "Tooltip and accessible-label fallback." },
2575
+ selected: { type: "boolean", dynamic: true, description: "Render the icon in its selected visual state." },
2576
+ active: { type: "boolean", dynamic: true, description: "Render the icon in its active visual state." },
2577
+ pressed: { type: "boolean", dynamic: true, description: "Expose pressed state and render the selected icon style." },
2578
+ onclick: { type: "write-expression", description: "Write expression invoked when the button is clicked." }
2579
+ },
2580
+ children: noChildren,
2581
+ examples: [example("button", {
2582
+ namespace: "doc_button_typical",
2583
+ layout: {
2584
+ "row:actions": {
2585
+ "button:save": {
2586
+ label: "Save",
2587
+ icon: "floppy-disk",
2588
+ variant: "primary"
2589
+ },
2590
+ "button:cancel": {
2591
+ label: "Cancel",
2592
+ variant: "secondary"
2593
+ },
2594
+ "button:delete": {
2595
+ label: "Delete",
2596
+ variant: "danger"
2597
+ }
2598
+ }
2599
+ }
2600
+ })]
2601
+ });
2602
+
2603
+ // src/components/entries/callout.spec.ts
2604
+ var calloutSpec = component({
2605
+ type: "callout",
2606
+ category: "Content",
2607
+ title: "Callout",
2608
+ summary: "Highlighted contextual message.",
2609
+ description: "Use callout for notes, warnings, and other contextual blocks.",
2610
+ props: {
2611
+ title: { type: "string", dynamic: true, description: "Callout title." },
2612
+ heading: { type: "string", dynamic: true, description: "Alias for title." },
2613
+ label: { type: "string", dynamic: true, description: "Alias for title." },
2614
+ icon: { type: "string", description: "Icon name shown before the title." },
2615
+ text: { type: "string", dynamic: true, description: "Callout body text." },
2616
+ message: { type: "string", dynamic: true, description: "Alias for text." },
2617
+ content: { type: "string", dynamic: true, description: "Alias for text." },
2618
+ tone: { type: "string", values: ["info", "success", "warning", "danger"], default: "info", description: "Semantic tone for the callout." }
2619
+ },
2620
+ children: childContent,
2621
+ examples: [example("callout", {
2622
+ namespace: "doc_callout_typical",
2623
+ layout: {
2624
+ "callout:notice": {
2625
+ tone: "info",
2626
+ title: "Notice",
2627
+ icon: "info",
2628
+ text: "Use callout for information that should stand out."
2629
+ }
2630
+ }
2631
+ })]
2632
+ });
2633
+
2634
+ // src/components/entries/card.spec.ts
2635
+ var cardSpec = component({
2636
+ type: "card",
2637
+ category: "Layout",
2638
+ title: "Card",
2639
+ summary: "Bordered grouping container.",
2640
+ description: "Use card to group related content on a bounded surface.",
2641
+ props: {
2642
+ title: { type: "string", dynamic: true, description: "Card title." },
2643
+ icon: { type: "string", description: "Icon name shown before the title." },
2644
+ tone: { type: "string", values: semanticTones, description: "Optional semantic tone for the card surface." }
2645
+ },
2646
+ children: childContent,
2647
+ examples: [example("card", {
2648
+ namespace: "doc_card_typical",
2649
+ layout: {
2650
+ "card:metrics": {
2651
+ title: "Metrics",
2652
+ icon: "chart-bar",
2653
+ "grid:items": {
2654
+ columns: 2,
2655
+ "stat:requests": {
2656
+ label: "Requests",
2657
+ value: "1.2k",
2658
+ unit: "/min"
2659
+ },
2660
+ "stat:latency": {
2661
+ label: "Latency",
2662
+ value: "42",
2663
+ unit: "ms"
2664
+ }
2665
+ }
2666
+ }
2667
+ }
2668
+ })]
2669
+ });
2670
+
2671
+ // src/components/entries/checkbox.spec.ts
2672
+ var checkboxSpec = component({
2673
+ type: "checkbox",
2674
+ category: "Input",
2675
+ title: "Checkbox",
2676
+ summary: "Boolean checkbox input.",
2677
+ description: "Use checkbox for binary choices that can be toggled independently.",
2678
+ props: {
2679
+ checked: { type: "boolean", default: false, dynamic: true, description: "Checked state." },
2680
+ label: { type: "string", dynamic: true, description: "Checkbox label." },
2681
+ icon: { type: "string", description: "Icon name shown before the visible label." },
2682
+ disabled: { type: "boolean", default: false, dynamic: true, description: "Disable the checkbox." },
2683
+ haptic: { type: "boolean", default: true, description: "Enable vibration feedback on supported devices." },
2684
+ haptics: { type: "boolean", default: true, description: "Alias for haptic." },
2685
+ onchange: { type: "write-expression", description: "Write expression invoked when checked state changes." }
2686
+ },
2687
+ children: noChildren,
2688
+ examples: [example("checkbox", {
2689
+ namespace: "doc_checkbox_typical",
2690
+ layout: {
2691
+ "checkbox:agree": {
2692
+ checked: true,
2693
+ label: "I agree",
2694
+ icon: "handshake"
2695
+ }
2696
+ }
2697
+ })]
2698
+ });
2699
+
2700
+ // src/components/entries/code-block.spec.ts
2701
+ var codeBlockSpec = component({
2702
+ type: "code-block",
2703
+ category: "Content",
2704
+ title: "Code Block",
2705
+ summary: "Formatted code or log block.",
2706
+ description: "Use code-block for static code samples, configuration snippets, and logs.",
2707
+ props: {
2708
+ code: { type: "string", dynamic: true, description: "Code text content." },
2709
+ source: { type: "string", dynamic: true, description: "Alias for code." },
2710
+ content: { type: "string", dynamic: true, description: "Alias for code." },
2711
+ language: { type: "string", description: "Language label." },
2712
+ title: { type: "string", description: "Code block title." },
2713
+ icon: { type: "string", description: "Icon name shown before the title." },
2714
+ lineNumbers: { type: "boolean", default: true, description: "Show line numbers." }
2715
+ },
2716
+ children: noChildren,
2717
+ examples: [example("code-block", {
2718
+ namespace: "doc_code_block_typical",
2719
+ layout: {
2720
+ "code-block:config": {
2721
+ title: "Config",
2722
+ icon: "code",
2723
+ language: "js",
2724
+ code: "export const enabled = true;"
2725
+ }
2726
+ }
2727
+ })]
2728
+ });
2729
+
2730
+ // src/components/entries/collapsible.spec.ts
2731
+ var collapsibleSpec = component({
2732
+ type: "collapsible",
2733
+ category: "Disclosure",
2734
+ title: "Collapsible",
2735
+ summary: "Single expandable region.",
2736
+ description: "Use collapsible to show or hide one related content region.",
2737
+ props: {
2738
+ open: { type: "boolean", default: false, dynamic: true, description: "Expanded state." },
2739
+ trigger: { type: "string", dynamic: true, description: "Trigger button text." },
2740
+ icon: { type: "string", description: "Icon name shown before trigger text." },
2741
+ content: { type: "string", dynamic: true, description: "Static body content." },
2742
+ onchange: { type: "write-expression", description: "Write expression invoked when open state changes." }
2743
+ },
2744
+ children: childContent,
2745
+ examples: [example("collapsible", {
2746
+ namespace: "doc_collapsible_typical",
2747
+ layout: {
2748
+ "collapsible:more": {
2749
+ open: true,
2750
+ trigger: "Details",
2751
+ icon: "caret-circle-down",
2752
+ content: "This secondary content can be collapsed."
2753
+ }
2754
+ }
2755
+ })]
2756
+ });
2757
+
2758
+ // src/components/entries/column.spec.ts
2759
+ var columnSpec = component({
2760
+ type: "column",
2761
+ category: "Layout",
2762
+ title: "Column",
2763
+ summary: "Vertical layout container.",
2764
+ description: "Use column to stack child components vertically in field order.",
2765
+ props: {},
2766
+ children: childContent,
2767
+ examples: [example("column", {
2768
+ namespace: "doc_column_typical",
2769
+ layout: {
2770
+ "column:form": {
2771
+ "input:name": {
2772
+ placeholder: "Name"
2773
+ },
2774
+ "input:email": {
2775
+ placeholder: "Email"
2776
+ },
2777
+ "button:save": {
2778
+ label: "Save"
2779
+ }
2780
+ }
2781
+ }
2782
+ })]
2783
+ });
2784
+
2785
+ // src/components/entries/divider.spec.ts
2786
+ var dividerSpec = component({
2787
+ type: "divider",
2788
+ category: "Content",
2789
+ title: "Divider",
2790
+ summary: "Visual separator.",
2791
+ description: "Use divider to separate related regions, optionally with a label.",
2792
+ props: {
2793
+ label: { type: "string", dynamic: true, description: "Text shown in the divider." },
2794
+ icon: { type: "string", description: "Icon name shown before the label." }
2795
+ },
2796
+ children: noChildren,
2797
+ examples: [example("divider", {
2798
+ namespace: "doc_divider_typical",
2799
+ layout: {
2800
+ "column:content": {
2801
+ "text:top": {
2802
+ text: "Above"
2803
+ },
2804
+ "divider:line": {
2805
+ label: "Divider",
2806
+ icon: "flag"
2807
+ },
2808
+ "text:bottom": {
2809
+ text: "Below"
2810
+ }
2811
+ }
2812
+ }
2813
+ })]
2814
+ });
2815
+
2816
+ // src/components/entries/formula.spec.ts
2817
+ var formulaSpec = component({
2818
+ type: "formula",
2819
+ category: "Display",
2820
+ title: "Formula",
2821
+ summary: "Reactive KaTeX formula display.",
2822
+ description: "Use formula to render SlexKit state and computed values through KaTeX.",
2823
+ props: {
2824
+ tex: { type: "string", dynamic: true, description: "KaTeX source to render." },
2825
+ formula: { type: "string", dynamic: true, description: "Alias for tex." },
2826
+ value: { type: "string", dynamic: true, description: "Alias for tex." },
2827
+ displayMode: { type: "boolean", default: true, description: "Render as display math when true; inline math when false." },
2828
+ display: { type: "boolean", default: true, description: "Alias for displayMode." },
2829
+ block: { type: "boolean", default: true, description: "Alias for displayMode." }
2830
+ },
2831
+ children: noChildren,
2832
+ examples: [example("formula", {
2833
+ namespace: "doc_formula_typical",
2834
+ g: {
2835
+ r: 1e4,
2836
+ c: 100,
2837
+ fc: 159.15
2838
+ },
2839
+ layout: {
2840
+ "formula:cutoff": {
2841
+ $tex: "'f_c = \\\\frac{1}{2\\\\pi RC} = ' + g.fc + '\\\\text{ Hz}'"
2842
+ }
2843
+ }
2844
+ })]
2845
+ });
2846
+
2847
+ // src/components/entries/grid.spec.ts
2848
+ var gridSpec = component({
2849
+ type: "grid",
2850
+ category: "Layout",
2851
+ title: "Grid",
2852
+ summary: "Responsive grid container.",
2853
+ description: "Use grid to arrange child components in responsive columns.",
2854
+ props: {
2855
+ columns: { type: "number", default: 1, dynamic: true, description: "Base column count." },
2856
+ smColumns: { type: "number", dynamic: true, description: "Column count at the small breakpoint." },
2857
+ mdColumns: { type: "number", dynamic: true, description: "Column count at the medium breakpoint." },
2858
+ lgColumns: { type: "number", dynamic: true, description: "Column count at the large breakpoint." },
2859
+ xlColumns: { type: "number", dynamic: true, description: "Column count at the extra-large breakpoint." },
2860
+ gap: { type: "string", dynamic: true, description: "Spacing between grid items." }
2861
+ },
2862
+ children: childContent,
2863
+ examples: [example("grid", {
2864
+ namespace: "doc_grid_typical",
2865
+ layout: {
2866
+ "grid:stats": {
2867
+ columns: 1,
2868
+ mdColumns: 3,
2869
+ "stat:a": {
2870
+ label: "Requests",
2871
+ value: "1.2k"
2872
+ },
2873
+ "stat:b": {
2874
+ label: "Success",
2875
+ value: "98%"
2876
+ },
2877
+ "stat:c": {
2878
+ label: "Errors",
2879
+ value: "3"
2880
+ }
2881
+ }
2882
+ }
2883
+ })]
2884
+ });
2885
+
2886
+ // src/components/entries/icon.spec.ts
2887
+ var iconSpec = component({
2888
+ type: "icon",
2889
+ category: "Component",
2890
+ title: "Icon",
2891
+ summary: "Shared icon field capability.",
2892
+ description: "Icon is a shared field capability, not an independent icon component type.",
2893
+ props: {
2894
+ icon: { type: "string", description: "Icon name resolved through the global icon manager." },
2895
+ iconOnly: { type: "boolean", description: "Render only the icon while retaining an accessible label where supported." },
2896
+ "items[].icon": { type: "string", description: "Accordion item trigger icon." },
2897
+ "options[].icon": { type: "string", description: "Select or radio option icon." },
2898
+ "columns[].icon": { type: "string", description: "Table column header icon." },
2899
+ "tabs[].icon": { type: "string", description: "Tab trigger icon." },
2900
+ "tabs[].iconOnly": { type: "boolean", description: "Tab trigger icon-only mode." }
2901
+ },
2902
+ children: noChildren,
2903
+ examples: [example("button", { label: "Settings", icon: "gear-six", iconOnly: true, variant: "ghost" }, "Icon field usage")]
2904
+ });
2905
+
2906
+ // src/components/entries/input.spec.ts
2907
+ var inputSpec = component({
2908
+ type: "input",
2909
+ category: "Input",
2910
+ title: "Input",
2911
+ summary: "Text or engineering-value input.",
2912
+ description: "Use input for editable text and engineering numeric values.",
2913
+ props: {
2914
+ value: { type: "string", dynamic: true, description: "Current input value." },
2915
+ label: { type: "string", dynamic: true, description: "Input label." },
2916
+ unit: { type: "string", dynamic: true, description: "Trailing unit text." },
2917
+ description: { type: "string", dynamic: true, description: "Assistive description below the input." },
2918
+ help: { type: "string", dynamic: true, description: "Alias for description." },
2919
+ hint: { type: "string", dynamic: true, description: "Alias for description." },
2920
+ error: { type: "string", dynamic: true, description: "Error text shown below the input and linked with aria-describedby." },
2921
+ errorMessage: { type: "string", dynamic: true, description: "Alias for error." },
2922
+ invalid: { type: "boolean", default: false, dynamic: true, description: "Mark the input as invalid with aria-invalid and error styling." },
2923
+ placeholder: { type: "string", description: "Placeholder text for empty values." },
2924
+ type: { type: "string", default: "text", description: "Input value kind; use engineering for parsed engineering values." },
2925
+ disabled: { type: "boolean", default: false, dynamic: true, description: "Disable editing." },
2926
+ readonly: { type: "boolean", default: false, dynamic: true, description: "Make the input read-only." },
2927
+ readOnly: { type: "boolean", default: false, dynamic: true, description: "Alias for readonly." },
2928
+ required: { type: "boolean", default: false, dynamic: true, description: "Mark the input as required." },
2929
+ id: { type: "string", description: "Native input id; defaults to a stable id derived from the component name." },
2930
+ name: { type: "string", description: "Native input name attribute." },
2931
+ min: { type: "string | number", dynamic: true, description: "Minimum value used by numeric input controls." },
2932
+ max: { type: "string | number", dynamic: true, description: "Maximum value used by numeric input controls." },
2933
+ step: { type: "string | number", dynamic: true, description: "Step size used by numeric input controls." },
2934
+ onchange: { type: "write-expression", description: "Write expression invoked when the value changes." }
2935
+ },
2936
+ children: noChildren,
2937
+ examples: [example("input", {
2938
+ namespace: "doc_input_typical",
2939
+ layout: {
2940
+ "input:name": {
2941
+ label: "Project",
2942
+ value: "SlexKit",
2943
+ placeholder: "Enter name",
2944
+ description: "Visible labels keep form fields scannable."
2945
+ }
2946
+ }
2947
+ })]
2948
+ });
2949
+
2950
+ // src/components/entries/link.spec.ts
2951
+ var linkSpec = component({
2952
+ type: "link",
2953
+ category: "Content",
2954
+ title: "Link",
2955
+ summary: "Inline navigation link.",
2956
+ description: "Use link to navigate to another page or resource.",
2957
+ props: {
2958
+ href: { type: "string", description: "Target URL." },
2959
+ text: { type: "string", dynamic: true, description: "Visible link text." },
2960
+ label: { type: "string", dynamic: true, description: "Alias for text." },
2961
+ content: { type: "string", dynamic: true, description: "Alias for text." },
2962
+ icon: { type: "string", description: "Icon name shown before link text." },
2963
+ target: { type: "string", description: "Native link target attribute." },
2964
+ variant: { type: "string", values: ["default", "muted"], default: "default", description: "Link visual variant." }
2965
+ },
2966
+ children: noChildren,
2967
+ examples: [example("link", {
2968
+ namespace: "doc_link_typical",
2969
+ layout: {
2970
+ "column:links": {
2971
+ "link:docs": {
2972
+ href: "/components",
2973
+ icon: "arrow-square-out",
2974
+ text: "View components"
2975
+ }
2976
+ }
2977
+ }
2978
+ })]
2979
+ });
2980
+
2981
+ // src/components/entries/playground.spec.ts
2982
+ var playgroundSpec = component({
2983
+ type: "playground",
2984
+ category: "Tooling",
2985
+ title: "Playground",
2986
+ summary: "Interactive source preview.",
2987
+ description: "Use playground in documentation surfaces to render editable SlexKit or Markdown examples.",
2988
+ props: {
2989
+ source: { type: "object | string", description: "SlexKit or Markdown source to preview." },
2990
+ sourceType: { type: "string", values: ["slex", "markdown", "auto-markdown"], default: "slex", description: "Source parser mode." },
2991
+ title: { type: "string", description: "Playground title." },
2992
+ previewAlign: { type: "string", values: ["center", "start"], default: "center", description: "Vertical preview alignment in render mode." },
2993
+ alignPreview: { type: "string", values: ["center", "start"], description: "Alias for previewAlign." },
2994
+ previewPlacement: { type: "string", values: ["center", "start"], description: "Alias for previewAlign." },
2995
+ previewMinHeight: { type: "string", description: "Minimum preview area height." },
2996
+ previewMaxWidth: { type: "string", description: "Maximum preview content width." },
2997
+ themeToggle: { type: "boolean", default: false, description: "Show the theme toggle action." },
2998
+ showThemeToggle: { type: "boolean", default: false, description: "Alias for themeToggle." },
2999
+ enableThemeToggle: { type: "boolean", default: false, description: "Alias for themeToggle." },
3000
+ themeLabel: { type: "string", description: "Accessible label for the theme toggle action." },
3001
+ themeToggleLabel: { type: "string", description: "Alias for themeLabel." },
3002
+ sourceTypeLabel: { type: "string", description: "Accessible label for the source type selector." },
3003
+ copyLabel: { type: "string", description: "Accessible label for the copy source action." },
3004
+ openWebLabel: { type: "string", description: "Accessible label for opening the source in the standalone playground." },
3005
+ webUrl: { type: "string", description: "Standalone playground URL used by the open action." },
3006
+ playgroundUrl: { type: "string", description: "Alias for webUrl." }
3007
+ },
3008
+ children: noChildren,
3009
+ examples: [example("playground", {
3010
+ namespace: "doc_playground_typical",
3011
+ layout: {
3012
+ "playground:demo": {
3013
+ title: "Stat Playground",
3014
+ previewMinHeight: "180px",
3015
+ source: {
3016
+ namespace: "inner_stat_demo",
3017
+ layout: {
3018
+ "stat:value": {
3019
+ label: "Requests",
3020
+ value: "1.2k",
3021
+ unit: "/min"
3022
+ }
3023
+ }
3024
+ }
3025
+ }
3026
+ }
3027
+ })]
3028
+ });
3029
+
3030
+ // src/components/entries/progress.spec.ts
3031
+ var progressSpec = component({
3032
+ type: "progress",
3033
+ category: "Feedback",
3034
+ title: "Progress",
3035
+ summary: "Progress bar.",
3036
+ description: "Use progress to show completion as a percentage.",
3037
+ props: {
3038
+ value: { type: "number", default: 0, dynamic: true, description: "Progress percentage from 0 to 100." },
3039
+ label: { type: "string", dynamic: true, description: "Progress label." },
3040
+ icon: { type: "string", description: "Icon name shown before the label." },
3041
+ indeterminate: { type: "boolean", default: false, dynamic: true, description: "Render an indeterminate progress state without aria-valuenow." }
3042
+ },
3043
+ children: noChildren,
3044
+ examples: [example("progress", {
3045
+ namespace: "doc_progress_typical",
3046
+ layout: {
3047
+ "progress:build": {
3048
+ label: "Build progress",
3049
+ icon: "gear-six",
3050
+ value: 64
3051
+ }
3052
+ }
3053
+ })]
3054
+ });
3055
+
3056
+ // src/components/entries/radio-group.spec.ts
3057
+ var radioGroupSpec = component({
3058
+ type: "radio-group",
3059
+ category: "Input",
3060
+ title: "Radio Group",
3061
+ summary: "Single-choice option group.",
3062
+ description: "Use radio-group for one-of-many choices.",
3063
+ props: {
3064
+ value: { type: "string", dynamic: true, description: "Current selected value." },
3065
+ label: { type: "string", dynamic: true, description: "Group label." },
3066
+ icon: { type: "string", description: "Icon name shown before the group label." },
3067
+ options: { type: "array", description: "Options with label, value, and optional icon." },
3068
+ "options[].icon": { type: "string", description: "Icon name shown before a single option label." },
3069
+ disabled: { type: "boolean", default: false, dynamic: true, description: "Disable every radio option in the group." },
3070
+ orientation: { type: "string", values: ["vertical", "horizontal"], default: "vertical", description: "Radio option layout direction." },
3071
+ haptic: { type: "boolean", default: true, description: "Enable vibration feedback on supported devices." },
3072
+ haptics: { type: "boolean", default: true, description: "Alias for haptic." },
3073
+ name: { type: "string", description: "Native radio group name shared by options." },
3074
+ onchange: { type: "write-expression", description: "Write expression invoked when selection changes." }
3075
+ },
3076
+ children: noChildren,
3077
+ examples: [example("radio-group", {
3078
+ namespace: "doc_radio_group_typical",
3079
+ layout: {
3080
+ "radio-group:mode": {
3081
+ label: "Mode",
3082
+ icon: "sliders-horizontal",
3083
+ value: "auto",
3084
+ options: [
3085
+ {
3086
+ label: "Auto",
3087
+ value: "auto",
3088
+ icon: "sparkle"
3089
+ },
3090
+ {
3091
+ label: "Manual",
3092
+ value: "manual",
3093
+ icon: "wrench"
3094
+ }
3095
+ ]
3096
+ }
3097
+ }
3098
+ })]
3099
+ });
3100
+
3101
+ // src/components/entries/row.spec.ts
3102
+ var rowSpec = component({
3103
+ type: "row",
3104
+ category: "Layout",
3105
+ title: "Row",
3106
+ summary: "Horizontal layout container.",
3107
+ description: "Use row to place child components horizontally in field order.",
3108
+ props: {
3109
+ justify: { type: "string", values: ["start", "center", "end", "space-between", "space-around"], default: "start", description: "Main-axis distribution." },
3110
+ align: { type: "string", values: ["start", "center", "end", "baseline", "stretch"], default: "center", description: "Cross-axis alignment." },
3111
+ gap: { type: "string", dynamic: true, description: "Spacing between children." }
3112
+ },
3113
+ children: childContent,
3114
+ examples: [example("row", {
3115
+ namespace: "doc_row_typical",
3116
+ layout: {
3117
+ "row:toolbar": {
3118
+ justify: "space-between",
3119
+ "text:title": {
3120
+ text: "Runtime status"
3121
+ },
3122
+ "button:refresh": {
3123
+ label: "Refresh"
3124
+ }
3125
+ }
3126
+ }
3127
+ })]
3128
+ });
3129
+
3130
+ // src/components/entries/section.spec.ts
3131
+ var sectionSpec = component({
3132
+ type: "section",
3133
+ category: "Content",
3134
+ title: "Section",
3135
+ summary: "Page section with optional heading chrome.",
3136
+ description: "Use section to group page content with title, subtitle, and optional action link.",
3137
+ props: {
3138
+ title: { type: "string", dynamic: true, description: "Section title." },
3139
+ icon: { type: "string", description: "Icon name shown before the title." },
3140
+ eyebrow: { type: "string", dynamic: true, description: "Small label above the title." },
3141
+ subtitle: { type: "string", dynamic: true, description: "Subtitle text below the title." },
3142
+ actionLabel: { type: "string", dynamic: true, description: "Optional action link label." },
3143
+ actionHref: { type: "string", description: "Optional action link target." }
3144
+ },
3145
+ children: childContent,
3146
+ examples: [example("section", {
3147
+ namespace: "doc_section_typical",
3148
+ layout: {
3149
+ "section:overview": {
3150
+ eyebrow: "Dashboard",
3151
+ title: "Runtime overview",
3152
+ icon: "chart-bar",
3153
+ subtitle: "This section groups the most important state.",
3154
+ "stat:latency": {
3155
+ label: "Latency",
3156
+ value: "42",
3157
+ unit: "ms"
3158
+ }
3159
+ }
3160
+ }
3161
+ })]
3162
+ });
3163
+
3164
+ // src/components/entries/select.spec.ts
3165
+ var selectSpec = component({
3166
+ type: "select",
3167
+ category: "Input",
3168
+ title: "Select",
3169
+ summary: "Dropdown selection input.",
3170
+ description: "Use select for compact single-choice selection from an option list.",
3171
+ props: {
3172
+ label: { type: "string", dynamic: true, description: "Select label." },
3173
+ icon: { type: "string", description: "Icon name shown before the top label." },
3174
+ value: { type: "string", dynamic: true, description: "Current selected value." },
3175
+ options: { type: "array", description: "Options with label, value, and optional icon." },
3176
+ "options[].icon": { type: "string", description: "Icon name shown before an option label." },
3177
+ placeholder: { type: "string", description: "Placeholder shown when no value is selected." },
3178
+ disabled: { type: "boolean", default: false, dynamic: true, description: "Disable the select trigger and native select." },
3179
+ required: { type: "boolean", default: false, dynamic: true, description: "Require a non-placeholder value in the native select." },
3180
+ variant: { type: "string", values: ["default", "toolbar"], default: "default", description: "Select surface variant." },
3181
+ onchange: { type: "write-expression", description: "Write expression invoked when selection changes." }
3182
+ },
3183
+ children: noChildren,
3184
+ examples: [example("select", {
3185
+ namespace: "doc_select_typical",
3186
+ layout: {
3187
+ "select:env": {
3188
+ label: "Environment",
3189
+ icon: "server",
3190
+ value: "prod",
3191
+ options: [
3192
+ {
3193
+ label: "Development",
3194
+ value: "dev",
3195
+ icon: "code"
3196
+ },
3197
+ {
3198
+ label: "Production",
3199
+ value: "prod",
3200
+ icon: "rocket-launch"
3201
+ }
3202
+ ]
3203
+ }
3204
+ }
3205
+ })]
3206
+ });
3207
+
3208
+ // src/components/entries/slider.spec.ts
3209
+ var sliderSpec = component({
3210
+ type: "slider",
3211
+ category: "Input",
3212
+ title: "Slider",
3213
+ summary: "Numeric range input.",
3214
+ description: "Use slider for bounded numeric adjustments such as volume, brightness, or thresholds.",
3215
+ props: {
3216
+ label: { type: "string", dynamic: true, description: "Slider label." },
3217
+ icon: { type: "string", description: "Icon name shown before the label." },
3218
+ value: { type: "number", default: 0, dynamic: true, description: "Current numeric value." },
3219
+ min: { type: "number", default: 0, dynamic: true, description: "Minimum value." },
3220
+ max: { type: "number", default: 100, dynamic: true, description: "Maximum value." },
3221
+ step: { type: "number", default: 1, dynamic: true, description: "Step interval." },
3222
+ unit: { type: "string", dynamic: true, description: "Unit shown after the value." },
3223
+ disabled: { type: "boolean", default: false, dynamic: true, description: "Disable the range input." },
3224
+ orientation: { type: "string", values: ["horizontal", "vertical"], default: "horizontal", description: "Slider orientation metadata used for styling." },
3225
+ haptic: { type: "boolean", default: true, description: "Enable vibration feedback on supported devices." },
3226
+ haptics: { type: "boolean", default: true, description: "Alias for haptic." },
3227
+ onchange: { type: "write-expression", description: "Write expression invoked when the value changes." }
3228
+ },
3229
+ children: noChildren,
3230
+ examples: [example("slider", {
3231
+ namespace: "doc_slider_typical",
3232
+ layout: {
3233
+ "slider:volume": {
3234
+ label: "Volume",
3235
+ icon: "speaker-high",
3236
+ value: 42,
3237
+ min: 0,
3238
+ max: 100,
3239
+ step: 1,
3240
+ unit: "%"
3241
+ }
3242
+ }
3243
+ })]
3244
+ });
3245
+
3246
+ // src/components/entries/stat.spec.ts
3247
+ var statSpec = component({
3248
+ type: "stat",
3249
+ category: "Display",
3250
+ title: "Stat",
3251
+ summary: "Metric display.",
3252
+ description: "Use stat to present a labeled metric value with optional unit and semantic tone.",
3253
+ props: {
3254
+ label: { type: "string", dynamic: true, description: "Metric label." },
3255
+ icon: { type: "string", description: "Icon name shown before the label." },
3256
+ value: { type: "string | number", dynamic: true, description: "Metric value." },
3257
+ unit: { type: "string", dynamic: true, description: "Unit shown after the value." },
3258
+ tone: { type: "string", values: semanticTones, description: "Optional semantic tone." },
3259
+ animateInitial: { type: "boolean", default: false, description: "Animate the initial rendered value." }
3260
+ },
3261
+ children: noChildren,
3262
+ examples: [example("stat", {
3263
+ namespace: "doc_stat_typical",
3264
+ layout: {
3265
+ "grid:stats": {
3266
+ columns: 2,
3267
+ "stat:requests": {
3268
+ label: "Requests",
3269
+ icon: "activity",
3270
+ value: "1.2k",
3271
+ unit: "/min"
3272
+ },
3273
+ "stat:success": {
3274
+ label: "Success",
3275
+ icon: "check-circle",
3276
+ value: "98.4",
3277
+ unit: "%",
3278
+ tone: "success"
3279
+ }
3280
+ }
3281
+ }
3282
+ })]
3283
+ });
3284
+
3285
+ // src/components/entries/submit.spec.ts
3286
+ var submitSpec = component({
3287
+ type: "submit",
3288
+ category: "Action",
3289
+ title: "Submit",
3290
+ summary: "ToolHost submit and ignore controls.",
3291
+ description: "Use submit inside tool templates to return selected state fields to the host.",
3292
+ props: {
3293
+ submitLabel: { type: "string", default: "Submit", description: "Submit button text." },
3294
+ ignoreLabel: { type: "string", default: "Ignore", description: "Ignore button text." },
3295
+ returnKeys: { type: "string[]", description: "State field paths returned to ToolHost." },
3296
+ disabled: { type: "boolean", default: false, dynamic: true, description: "Disable submit action." }
3297
+ },
3298
+ children: noChildren,
3299
+ examples: [example("submit", {
3300
+ namespace: "doc_submit_typical",
3301
+ layout: {
3302
+ "column:tool": {
3303
+ "input:title": {
3304
+ value: "Release note",
3305
+ placeholder: "Title"
3306
+ },
3307
+ "submit:done": {
3308
+ submitLabel: "Submit",
3309
+ ignoreLabel: "Ignore",
3310
+ returnKeys: [
3311
+ "title"
3312
+ ]
3313
+ }
3314
+ }
3315
+ }
3316
+ })]
3317
+ });
3318
+
3319
+ // src/components/entries/switch.spec.ts
3320
+ var switchSpec = component({
3321
+ type: "switch",
3322
+ category: "Input",
3323
+ title: "Switch",
3324
+ summary: "Boolean switch input.",
3325
+ description: "Use switch for an on/off setting.",
3326
+ props: {
3327
+ enabled: { type: "boolean", default: false, dynamic: true, description: "Enabled state." },
3328
+ label: { type: "string", dynamic: true, description: "Switch label." },
3329
+ icon: { type: "string", description: "Icon name shown before the visible label." },
3330
+ disabled: { type: "boolean", default: false, dynamic: true, description: "Disable the switch." },
3331
+ haptic: { type: "boolean", default: true, description: "Enable vibration feedback on supported devices." },
3332
+ haptics: { type: "boolean", default: true, description: "Alias for haptic." },
3333
+ onchange: { type: "write-expression", description: "Write expression invoked when enabled state changes." }
3334
+ },
3335
+ children: noChildren,
3336
+ examples: [example("switch", {
3337
+ namespace: "doc_switch_typical",
3338
+ layout: {
3339
+ "switch:feature": {
3340
+ enabled: true,
3341
+ label: "Enable sync",
3342
+ icon: "arrows-clockwise"
3343
+ }
3344
+ }
3345
+ })]
3346
+ });
3347
+
3348
+ // src/components/entries/table.spec.ts
3349
+ var tableSpec = component({
3350
+ type: "table",
3351
+ category: "Data",
3352
+ title: "Table",
3353
+ summary: "Simple data table.",
3354
+ description: "Use table to render rows of keyed data against a column definition.",
3355
+ props: {
3356
+ columns: { type: "array", description: "Column definitions with key, label, and optional icon." },
3357
+ "columns[].icon": { type: "string", description: "Icon name shown before a column label." },
3358
+ rows: { type: "array", description: "Row data objects keyed by column key." },
3359
+ items: { type: "array", description: "Alias for rows." }
3360
+ },
3361
+ children: noChildren,
3362
+ examples: [example("table", {
3363
+ namespace: "doc_table_typical",
3364
+ layout: {
3365
+ "table:routes": {
3366
+ columns: [
3367
+ {
3368
+ key: "name",
3369
+ label: "Name",
3370
+ icon: "text-t"
3371
+ },
3372
+ {
3373
+ key: "status",
3374
+ label: "Status",
3375
+ icon: "check-circle"
3376
+ }
3377
+ ],
3378
+ rows: [
3379
+ {
3380
+ name: "Parse",
3381
+ status: "ready"
3382
+ },
3383
+ {
3384
+ name: "Publish",
3385
+ status: "pending"
3386
+ }
3387
+ ]
3388
+ }
3389
+ }
3390
+ })]
3391
+ });
3392
+
3393
+ // src/components/entries/tabs.spec.ts
3394
+ var tabsSpec = component({
3395
+ type: "tabs",
3396
+ category: "Navigation",
3397
+ title: "Tabs",
3398
+ summary: "Tabbed view switcher.",
3399
+ description: "Use tabs to switch between named content panels.",
3400
+ props: {
3401
+ value: { type: "string", dynamic: true, description: "Current active tab value." },
3402
+ tabs: { type: "array", description: "Tab definitions with value, label, content, icon, and iconOnly." },
3403
+ "tabs[].icon": { type: "string", description: "Icon name shown before a tab trigger label." },
3404
+ "tabs[].iconOnly": { type: "boolean", description: "Show only the tab icon while retaining label as accessible text." },
3405
+ orientation: { type: "string", values: ["horizontal", "vertical"], default: "horizontal", description: "Tab list orientation." },
3406
+ onchange: { type: "write-expression", description: "Write expression invoked when the active tab changes." }
3407
+ },
3408
+ children: noChildren,
3409
+ examples: [example("tabs", {
3410
+ namespace: "doc_tabs_typical",
3411
+ layout: {
3412
+ "tabs:main": {
3413
+ value: "overview",
3414
+ tabs: [
3415
+ {
3416
+ value: "overview",
3417
+ label: "Overview"
3418
+ },
3419
+ {
3420
+ value: "settings",
3421
+ label: "Settings"
3422
+ }
3423
+ ]
3424
+ }
3425
+ }
3426
+ })]
3427
+ });
3428
+
3429
+ // src/components/entries/text.spec.ts
3430
+ var textSpec = component({
3431
+ type: "text",
3432
+ category: "Display",
3433
+ title: "Text",
3434
+ summary: "Plain text display.",
3435
+ description: "Use text for short static or dynamic copy inside SlexKit layouts.",
3436
+ props: {
3437
+ text: { type: "string", dynamic: true, description: "Displayed text." },
3438
+ content: { type: "string", dynamic: true, description: "Alias for text." },
3439
+ label: { type: "string", dynamic: true, description: "Alias for text." },
3440
+ variant: { type: "string", values: ["default", "muted"], default: "default", description: "Text visual variant." },
3441
+ color: { type: "string", dynamic: true, description: "Optional CSS color for controlled previews." },
3442
+ size: { type: "string | number", dynamic: true, description: "Optional font size. Numbers are treated as px." },
3443
+ class: { type: "string", description: "Additional host-controlled CSS class." }
3444
+ },
3445
+ children: noChildren,
3446
+ examples: [example("text", {
3447
+ namespace: "doc_text_typical",
3448
+ layout: {
3449
+ "text:status": {
3450
+ text: "System is healthy"
3451
+ }
3452
+ }
3453
+ })]
3454
+ });
3455
+
3456
+ // src/components/entries/toast.spec.ts
3457
+ var toastSpec = component({
3458
+ type: "toast",
3459
+ category: "Feedback",
3460
+ title: "Toast",
3461
+ summary: "Transient notification.",
3462
+ description: "Use toast to show an inline notification with semantic status.",
3463
+ props: {
3464
+ title: { type: "string", dynamic: true, description: "Toast title." },
3465
+ heading: { type: "string", dynamic: true, description: "Alias for title." },
3466
+ label: { type: "string", dynamic: true, description: "Alias for title." },
3467
+ icon: { type: "string", description: "Icon name shown at the left of the toast." },
3468
+ description: { type: "string", dynamic: true, description: "Toast body text." },
3469
+ text: { type: "string", dynamic: true, description: "Alias for description." },
3470
+ message: { type: "string", dynamic: true, description: "Alias for description." },
3471
+ content: { type: "string", dynamic: true, description: "Alias for description." },
3472
+ type: { type: "string", values: ["info", "success", "warning", "danger"], default: "info", description: "Semantic notification type." },
3473
+ tone: { type: "string", values: ["info", "success", "warning", "danger"], default: "info", description: "Alias for type." },
3474
+ duration: { type: "number", description: "Auto-hide delay in milliseconds." },
3475
+ dismissable: { type: "boolean", default: true, description: "Show a close button." },
3476
+ dismissible: { type: "boolean", default: true, description: "Alias for dismissable." },
3477
+ closeLabel: { type: "string", default: "Close notification", description: "Accessible close button label." },
3478
+ closeAriaLabel: { type: "string", description: "Alias for closeLabel." }
3479
+ },
3480
+ children: noChildren,
3481
+ examples: [example("toast", {
3482
+ namespace: "doc_toast_typical",
3483
+ layout: {
3484
+ "toast:saved": {
3485
+ type: "success",
3486
+ title: "Saved",
3487
+ icon: "check-circle",
3488
+ description: "Changes have been written."
3489
+ }
3490
+ }
3491
+ })]
3492
+ });
3493
+
3494
+ // src/components/entries/specs.ts
3495
+ var componentSpecs = [
3496
+ accordionSpec,
3497
+ badgeSpec,
3498
+ buttonSpec,
3499
+ calloutSpec,
3500
+ cardSpec,
3501
+ checkboxSpec,
3502
+ codeBlockSpec,
3503
+ collapsibleSpec,
3504
+ columnSpec,
3505
+ dividerSpec,
3506
+ formulaSpec,
3507
+ gridSpec,
3508
+ iconSpec,
3509
+ inputSpec,
3510
+ linkSpec,
3511
+ playgroundSpec,
3512
+ progressSpec,
3513
+ radioGroupSpec,
3514
+ rowSpec,
3515
+ sectionSpec,
3516
+ selectSpec,
3517
+ sliderSpec,
3518
+ statSpec,
3519
+ submitSpec,
3520
+ switchSpec,
3521
+ tableSpec,
3522
+ tabsSpec,
3523
+ textSpec,
3524
+ toastSpec
3525
+ ];
3526
+
3527
+ // src/components/spec-registry.ts
3528
+ var publicComponentTypes = componentSpecs.map((spec) => spec.type);
3529
+ var componentSpecByType = new Map(componentSpecs.map((spec) => [spec.type, spec]));
3530
+
3531
+ // src/engine/capabilities.ts
3532
+ var slexkitStdlibDocs = [
3533
+ {
3534
+ name: "math",
3535
+ summary: "Small numeric helpers for common interactive calculations.",
3536
+ functions: [
3537
+ { name: "std.math.clamp", signature: "clamp(value, min, max)", summary: "Clamp a number into a range.", pure: true, example: "std.math.clamp(g.score, 0, 100)" },
3538
+ { name: "std.math.round", signature: "round(value, digits = 0)", summary: "Round with a fixed number of decimal digits.", pure: true, example: "std.math.round(g.latency, 1)" },
3539
+ { name: "std.math.safeDivide", signature: "safeDivide(numerator, denominator, fallback = 0)", summary: "Divide with a fallback for zero or invalid denominators.", pure: true, example: "std.math.safeDivide(g.used, g.total, 0)" },
3540
+ { name: "std.math.percent", signature: "percent(part, total, digits = 1)", summary: "Return part / total as a percentage number.", pure: true, example: "std.math.percent(g.done, g.total, 1)" },
3541
+ { name: "std.math.lerp", signature: "lerp(start, end, t)", summary: "Linear interpolation.", pure: true, example: "std.math.lerp(0, 100, g.progress)" }
3542
+ ]
3543
+ },
3544
+ {
3545
+ name: "stats",
3546
+ summary: "Finite-number aggregations for arrays.",
3547
+ functions: [
3548
+ { name: "std.stats.sum", signature: "sum(values)", summary: "Sum finite numeric values. Empty arrays return 0.", pure: true, example: "std.stats.sum(g.samples)" },
3549
+ { name: "std.stats.mean", signature: "mean(values)", summary: "Average finite numeric values. Empty arrays return NaN.", pure: true, example: "std.stats.mean(g.samples)" },
3550
+ { name: "std.stats.min", signature: "min(values)", summary: "Minimum finite numeric value. Empty arrays return NaN.", pure: true, example: "std.stats.min(g.samples)" },
3551
+ { name: "std.stats.max", signature: "max(values)", summary: "Maximum finite numeric value. Empty arrays return NaN.", pure: true, example: "std.stats.max(g.samples)" },
3552
+ { name: "std.stats.median", signature: "median(values)", summary: "Median finite numeric value. Empty arrays return NaN.", pure: true, example: "std.stats.median(g.samples)" }
3553
+ ]
3554
+ },
3555
+ {
3556
+ name: "format",
3557
+ summary: "Deterministic display formatting with en-US defaults.",
3558
+ functions: [
3559
+ { name: "std.format.fixed", signature: "fixed(value, digits = 2)", summary: "Format a number with fixed decimal places.", pure: true, example: "std.format.fixed(g.voltage, 3)" },
3560
+ { name: "std.format.number", signature: "number(value, digits = 0, locale = 'en-US')", summary: "Locale number formatting.", pure: true, example: "std.format.number(g.requests)" },
3561
+ { name: "std.format.compact", signature: "compact(value, digits = 1, locale = 'en-US')", summary: "Compact number formatting.", pure: true, example: "std.format.compact(g.users)" },
3562
+ { name: "std.format.percent", signature: "percent(ratio, digits = 1)", summary: "Format a ratio as a percentage string.", pure: true, example: "std.format.percent(g.done / g.total, 1)" },
3563
+ { name: "std.format.currency", signature: "currency(value, currency = 'USD', locale = 'en-US')", summary: "Format a currency value.", pure: true, example: "std.format.currency(g.revenue, 'USD')" }
3564
+ ]
3565
+ },
3566
+ {
3567
+ name: "units",
3568
+ summary: "Small unit display helpers for common dashboards.",
3569
+ functions: [
3570
+ { name: "std.units.withUnit", signature: "withUnit(value, unit, digits = 2)", summary: "Format a value with a unit suffix.", pure: true, example: "std.units.withUnit(g.power, 'W', 1)" },
3571
+ { name: "std.units.bytes", signature: "bytes(value, digits = 1)", summary: "Format bytes as B, KB, MB, GB, TB, or PB.", pure: true, example: "std.units.bytes(g.payloadBytes)" },
3572
+ { name: "std.units.duration", signature: "duration(ms, digits = 1)", summary: "Format milliseconds as ms, s, min, or h.", pure: true, example: "std.units.duration(g.elapsedMs)" },
3573
+ { name: "std.units.si", signature: "si(value, unit = '', digits = 2)", summary: "Format with SI prefixes.", pure: true, example: "std.units.si(g.frequency, 'Hz', 2)" }
3574
+ ]
3575
+ }
3576
+ ];
3577
+ var slexkitRuntimeCapabilities = [
3578
+ { name: "api.get", policy: "network", signature: "get(url, options)", summary: "Policy-gated GET request.", example: "await api.get('https://api.example.com/status')", secureDefault: "denied", forbidden: ["fetch(url)", "XMLHttpRequest", "WebSocket"] },
3579
+ { name: "api.post", policy: "network", signature: "post(url, body, options)", summary: "Policy-gated POST request.", example: "await api.post('https://api.example.com/items', { ok: true })", secureDefault: "denied", forbidden: ["fetch(url)", "XMLHttpRequest", "WebSocket"] },
3580
+ { name: "api.fetch", policy: "network", signature: "fetch(url, options)", summary: "Policy-gated GET or POST request.", example: "await api.fetch(url, { method: 'GET' })", secureDefault: "denied", forbidden: ["fetch(url)", "XMLHttpRequest", "WebSocket"] },
3581
+ { name: "api.setTimeout", policy: "timer", signature: "setTimeout(fn, ms)", summary: "Policy-gated timeout.", example: "api.setTimeout(function () { g.ready = true; }, 500)", secureDefault: "denied", forbidden: ["setTimeout(fn, ms)"] },
3582
+ { name: "api.clearTimeout", policy: "timer", signature: "clearTimeout(id)", summary: "Clear a policy-gated timeout.", example: "api.clearTimeout(g.timeoutId)", secureDefault: "denied" },
3583
+ { name: "api.setInterval", policy: "timer", signature: "setInterval(fn, ms)", summary: "Policy-gated interval.", example: "api.setInterval(function () { g.ticks += 1; }, 1000)", secureDefault: "denied", forbidden: ["setInterval(fn, ms)"] },
3584
+ { name: "api.clearInterval", policy: "timer", signature: "clearInterval(id)", summary: "Clear a policy-gated interval.", example: "api.clearInterval(g.intervalId)", secureDefault: "denied" },
3585
+ { name: "api.raf", policy: "animation", signature: "raf(fn)", summary: "Policy-gated animation frame.", example: "api.raf(function (time) { g.time = time; })", secureDefault: "denied", forbidden: ["requestAnimationFrame(fn)"] },
3586
+ { name: "api.cancelRaf", policy: "animation", signature: "cancelRaf(id)", summary: "Cancel a policy-gated animation frame.", example: "api.cancelRaf(g.rafId)", secureDefault: "denied" },
3587
+ { name: "api.createCanvas", policy: "canvas", signature: "createCanvas(width, height)", summary: "Create a policy-counted canvas.", example: "var canvas = api.createCanvas(320, 180)", secureDefault: "denied" },
3588
+ { name: "api.getCanvasContext", policy: "canvas", signature: "getCanvasContext(canvas, contextId, options)", summary: "Get a policy-checked canvas context.", example: "var ctx = api.getCanvasContext(canvas, '2d')", secureDefault: "denied" },
3589
+ { name: "api.onDispose", policy: "lifecycle", signature: "onDispose(fn)", summary: "Register runtime cleanup.", example: "api.onDispose(function () { g.closed = true; })", secureDefault: "available" },
3590
+ { name: "api.now", policy: "lifecycle", signature: "now()", summary: "Runtime clock.", example: "api.now()", secureDefault: "available" },
3591
+ { name: "api.isTimeoutError", policy: "diagnostics", signature: "isTimeoutError(error)", summary: "Check timeout errors.", example: "api.isTimeoutError(error)", secureDefault: "available" },
3592
+ { name: "api.isNetworkError", policy: "diagnostics", signature: "isNetworkError(error)", summary: "Check network errors.", example: "api.isNetworkError(error)", secureDefault: "available" },
3593
+ { name: "api.isPolicyError", policy: "diagnostics", signature: "isPolicyError(error)", summary: "Check policy errors.", example: "api.isPolicyError(error)", secureDefault: "available" },
3594
+ { name: "api.errorMessage", policy: "diagnostics", signature: "errorMessage(error)", summary: "Extract a displayable error message.", example: "api.errorMessage(error)", secureDefault: "available" }
3595
+ ];
3596
+ var slexkitStdlibFunctionNames = slexkitStdlibDocs.flatMap((namespace) => namespace.functions.map((fn) => fn.name));
3597
+ var slexkitRuntimeCapabilityNames = slexkitRuntimeCapabilities.map((capability) => capability.name);
3598
+
3599
+ // src/engine/validation.ts
3600
+ var componentSpecByType2 = new Map(componentSpecs.map((spec) => [spec.type, spec]));
3601
+ var stdlibFunctions = new Set(slexkitStdlibFunctionNames);
3602
+ var stdlibNamespaces = new Set(slexkitStdlibFunctionNames.map((name) => name.split(".").slice(0, 2).join(".")));
3603
+ var apiMembers = new Set(slexkitRuntimeCapabilityNames);
3604
+ var directiveProps = new Set(["$if", "$for", "$key"]);
3605
+ var nativeSecureCapabilities = [
3606
+ { value: "fetch", pattern: /(?<!\.)\bfetch\s*\(/ },
3607
+ { value: "XMLHttpRequest", pattern: /\bXMLHttpRequest\b/ },
3608
+ { value: "WebSocket", pattern: /\bWebSocket\b/ },
3609
+ { value: "setTimeout", pattern: /(?<!\.)\bsetTimeout\s*\(/ },
3610
+ { value: "requestAnimationFrame", pattern: /(?<!\.)\brequestAnimationFrame\s*\(/ }
3611
+ ];
3612
+ function collectMemberUsage(source, root) {
3613
+ const matches = source.matchAll(new RegExp(`\\b${root}\\.([A-Za-z_$][\\w$]*)(?:\\.([A-Za-z_$][\\w$]*))?`, "g"));
3614
+ const found = new Set;
3615
+ for (const match of matches) {
3616
+ found.add([root, match[1], match[2]].filter(Boolean).join("."));
3617
+ }
3618
+ return [...found].sort();
3619
+ }
3620
+ function componentKeyType(key) {
3621
+ const colon = key.indexOf(":");
3622
+ return colon > 0 ? key.slice(0, colon) : null;
3623
+ }
3624
+ function isKnownProp(type, key) {
3625
+ if (directiveProps.has(key) || key.startsWith("on"))
3626
+ return true;
3627
+ const spec = componentSpecByType2.get(type);
3628
+ if (!spec)
3629
+ return true;
3630
+ const propName = key.startsWith("$") ? key.slice(1) : key;
3631
+ return propName in spec.props;
3632
+ }
3633
+ function walkComponents(value, warnings, usage, path = "") {
3634
+ if (!value || typeof value !== "object")
3635
+ return;
3636
+ for (const [key, child] of Object.entries(value)) {
3637
+ const childPath = path ? `${path}.${key}` : key;
3638
+ const type = componentKeyType(key);
3639
+ if (type) {
3640
+ usage.add(type);
3641
+ const spec = componentSpecByType2.get(type);
3642
+ if (!spec) {
3643
+ warnings.push({
3644
+ code: "unknown_component",
3645
+ message: `Unknown SlexKit component '${type}'.`,
3646
+ path: childPath,
3647
+ value: type
3648
+ });
3649
+ }
3650
+ if (child && typeof child === "object") {
3651
+ for (const propName of Object.keys(child)) {
3652
+ if (componentKeyType(propName))
3653
+ continue;
3654
+ if (!isKnownProp(type, propName)) {
3655
+ warnings.push({
3656
+ code: "unknown_prop",
3657
+ message: `Unknown prop '${propName}' on component '${type}'.`,
3658
+ path: `${childPath}.${propName}`,
3659
+ value: propName
3660
+ });
3661
+ }
3662
+ }
3663
+ }
3664
+ }
3665
+ walkComponents(child, warnings, usage, childPath);
3666
+ }
3667
+ }
3668
+ function sourceWarnings(source, mode) {
3669
+ const warnings = [];
3670
+ const stdlibUsage = collectMemberUsage(source, "std");
3671
+ const apiUsage = collectMemberUsage(source, "api");
3672
+ for (const name of stdlibUsage) {
3673
+ if (stdlibFunctions.has(name) || stdlibNamespaces.has(name))
3674
+ continue;
3675
+ warnings.push({
3676
+ code: "unknown_std_member",
3677
+ message: `Unknown SlexKit stdlib member '${name}'.`,
3678
+ value: name
3679
+ });
3680
+ }
3681
+ for (const name of apiUsage) {
3682
+ if (apiMembers.has(name))
3683
+ continue;
3684
+ warnings.push({
3685
+ code: "unknown_api_member",
3686
+ message: `Unknown SlexKit runtime API member '${name}'.`,
3687
+ value: name
3688
+ });
3689
+ }
3690
+ if (mode === "secure") {
3691
+ for (const capability of nativeSecureCapabilities) {
3692
+ if (capability.pattern.test(source)) {
3693
+ warnings.push({
3694
+ code: "native_secure_capability",
3695
+ message: `Native '${capability.value}' is not supported in secure mode. Use policy-gated api.* instead.`,
3696
+ value: capability.value
3697
+ });
3698
+ }
3699
+ }
3700
+ }
3701
+ return { warnings, stdlibUsage, apiUsage };
3702
+ }
3703
+ function validateSlexSource(source, options = {}) {
3704
+ const mode = options.mode ?? "trusted";
3705
+ const parsed = parseSlexSource(source);
3706
+ const usage = new Set;
3707
+ const { warnings, stdlibUsage, apiUsage } = sourceWarnings(source, mode);
3708
+ if (!parsed.ok) {
3709
+ return {
3710
+ ok: false,
3711
+ diagnostic: parsed.diagnostic,
3712
+ warnings,
3713
+ componentUsage: [],
3714
+ stdlibUsage,
3715
+ apiUsage
3716
+ };
3717
+ }
3718
+ walkComponents(parsed.value, warnings, usage);
3719
+ return {
3720
+ ok: true,
3721
+ value: parsed.value,
3722
+ warnings,
3723
+ componentUsage: [...usage].sort(),
3724
+ stdlibUsage,
3725
+ apiUsage
3726
+ };
3727
+ }
2206
3728
  // src/engine/markdown-runtime.ts
2207
3729
  var DEFAULT_POLICY = {};
2208
3730
  function isRecord(value) {
@@ -2218,7 +3740,11 @@ function isRenderableSource(value) {
2218
3740
  return Object.keys(value).some((key) => key.includes(":"));
2219
3741
  }
2220
3742
  function bareLayoutFromSource(value) {
2221
- const { slex: _slex, namespace: _namespace, g: _g, layout: _layout, ...layout } = value;
3743
+ const layout = { ...value };
3744
+ delete layout.slex;
3745
+ delete layout.namespace;
3746
+ delete layout.g;
3747
+ delete layout.layout;
2222
3748
  return layout;
2223
3749
  }
2224
3750
  function isStateOnlySource(value) {
@@ -2328,7 +3854,7 @@ function createSlexKitMarkdownRuntimeHost(initialOptions = {}) {
2328
3854
  const __target = { slex: "0.1", namespace: __artifactPrefix + "default", g: {}, layout: {} };
2329
3855
  const __layouts = [];
2330
3856
  for (const __entry of __sources) {
2331
- const __script = __entry.kind === "json" ? JSON.parse(__entry.source) : (0, eval)(__entry.source);
3857
+ const __script = __entry.kind === "json" ? JSON.parse(__entry.source) : (0, eval)("(" + __entry.source + ")");
2332
3858
  if (!__isRecord(__script)) continue;
2333
3859
  if ("slex" in __script) __target.slex = String(__script.slex || "0.1");
2334
3860
  if ("namespace" in __script) __target.namespace = __artifactPrefix + String(__script.namespace || "default");
@@ -2766,14 +4292,20 @@ function createSecureFrameTarget(input, container, mountOptions) {
2766
4292
  iframe.title = options.title ?? "SlexKit secure artifact";
2767
4293
  iframe.setAttribute("data-slexkit-secure-frame", "true");
2768
4294
  iframe.setAttribute("referrerpolicy", "no-referrer");
4295
+ iframe.style.display = "block";
4296
+ iframe.style.width = "100%";
4297
+ iframe.style.border = "0";
4298
+ iframe.style.background = "transparent";
2769
4299
  const runtimeUrl = options.runtimeUrl ?? options.runnerUrl ?? defaultRuntimeUrl;
2770
4300
  if (runtimeUrl) {
2771
4301
  const resolvedRuntimeUrl = resolveRuntimeUrl(runtimeUrl);
4302
+ const styleUrl = options.styleUrl === false ? "" : options.styleUrl ?? defaultSecureFrameStyleUrl(resolvedRuntimeUrl);
4303
+ const resolvedStyleUrl = styleUrl ? resolveRuntimeUrl(styleUrl) : undefined;
2772
4304
  assertSandboxCloneable(input);
2773
4305
  iframe.setAttribute("sandbox", secureSandboxAttribute(options));
2774
4306
  container.replaceChildren(iframe);
2775
4307
  const bridge = createSandboxBridge(input, container, iframe, mountOptions);
2776
- iframe.srcdoc = secureRunnerSrcdoc(resolvedRuntimeUrl);
4308
+ iframe.srcdoc = secureRunnerSrcdoc(resolvedRuntimeUrl, resolvedStyleUrl);
2777
4309
  return bridge;
2778
4310
  }
2779
4311
  if (frame) {
@@ -2833,23 +4365,38 @@ function cspSourceForRuntime(runtimeUrl) {
2833
4365
  return "'self'";
2834
4366
  }
2835
4367
  }
4368
+ function defaultSecureFrameStyleUrl(runtimeUrl) {
4369
+ try {
4370
+ const url = new URL(runtimeUrl);
4371
+ if (url.protocol === "blob:" || url.protocol === "data:")
4372
+ return "";
4373
+ url.pathname = url.pathname.replace(/[^/]+$/, "slexkit.css");
4374
+ url.search = "";
4375
+ url.hash = "";
4376
+ return url.href;
4377
+ } catch {
4378
+ return "";
4379
+ }
4380
+ }
2836
4381
  function escapeHtmlAttribute(value) {
2837
4382
  return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2838
4383
  }
2839
- function secureRunnerSrcdoc(runtimeUrl) {
4384
+ function secureRunnerSrcdoc(runtimeUrl, styleUrl) {
2840
4385
  const nonce = randomToken(12);
4386
+ const styleSource = styleUrl ? ` ${cspSourceForRuntime(styleUrl)}` : "";
2841
4387
  const csp = [
2842
4388
  "default-src 'none'",
2843
4389
  `script-src 'nonce-${nonce}' 'unsafe-eval' ${cspSourceForRuntime(runtimeUrl)}`,
2844
4390
  "connect-src 'none'",
2845
4391
  "img-src data: blob:",
2846
- "style-src 'unsafe-inline'",
4392
+ `style-src 'unsafe-inline'${styleSource}`,
2847
4393
  "font-src data:",
2848
4394
  "form-action 'none'",
2849
4395
  "base-uri 'none'"
2850
4396
  ].join("; ");
2851
4397
  const style = "html,body{margin:0;min-height:100%;overflow:hidden;}#slexkit-secure-root{min-height:100%;}";
2852
- return `<!doctype html><html><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="${escapeHtmlAttribute(csp)}"><style>${style}</style></head><body><div id="slexkit-secure-root"></div><script type="module" nonce="${nonce}">import { startSlexKitSandboxRunner } from ${JSON.stringify(runtimeUrl)}; startSlexKitSandboxRunner();</script></body></html>`;
4398
+ const stylesheet = styleUrl ? `<link rel="stylesheet" href="${escapeHtmlAttribute(styleUrl)}">` : "";
4399
+ return `<!doctype html><html><head><meta charset="utf-8"><meta http-equiv="Content-Security-Policy" content="${escapeHtmlAttribute(csp)}">${stylesheet}<style>${style}</style></head><body><div id="slexkit-secure-root"></div><script type="module" nonce="${nonce}">import { startSlexKitSandboxRunner } from ${JSON.stringify(runtimeUrl)}; startSlexKitSandboxRunner();</script></body></html>`;
2853
4400
  }
2854
4401
  function secureFrameLoadTimeout(options) {
2855
4402
  const frame = options.frame;
@@ -3101,11 +4648,21 @@ function createSandboxBridge(input, container, iframe, options) {
3101
4648
  if (data.type === "slot-size" && data.id === id && data.token === token && typeof data.slotId === "string") {
3102
4649
  const slot = artifactSlots.find((item) => item.id === data.slotId);
3103
4650
  if (slot && typeof data.height === "number" && Number.isFinite(data.height)) {
3104
- slot.container.style.minHeight = `${Math.max(0, Math.ceil(data.height))}px`;
4651
+ const height = Math.max(0, Math.ceil(data.height));
4652
+ slot.container.style.minHeight = `${height}px`;
4653
+ if (artifactSlots.length === 1) {
4654
+ iframe.style.height = `${Math.max(1, height)}px`;
4655
+ }
3105
4656
  requestArtifactSlotSync();
3106
4657
  }
3107
4658
  return;
3108
4659
  }
4660
+ if (data.type === "frame-size" && data.id === id && data.token === token) {
4661
+ if (artifactSlots.length <= 1 && typeof data.height === "number" && Number.isFinite(data.height)) {
4662
+ iframe.style.height = `${Math.max(1, Math.ceil(data.height))}px`;
4663
+ }
4664
+ return;
4665
+ }
3109
4666
  if (data.type === "error" && (!data.id || data.id === id) && (!data.token || data.token === token)) {
3110
4667
  const error = data;
3111
4668
  failLoad(ready ? "mounted" : "ready", typeof error.error?.message === "string" ? error.error.message : undefined);
@@ -3522,6 +5079,7 @@ function startSlexKitSandboxRunner() {
3522
5079
  let activeId;
3523
5080
  let activeToken;
3524
5081
  let heartbeatTimer;
5082
+ let rootResizeObserver;
3525
5083
  let slotResizeObserver;
3526
5084
  function clearHeartbeat() {
3527
5085
  if (heartbeatTimer === undefined)
@@ -3547,6 +5105,8 @@ function startSlexKitSandboxRunner() {
3547
5105
  if (id && activeId && id !== activeId)
3548
5106
  return;
3549
5107
  clearHeartbeat();
5108
+ rootResizeObserver?.disconnect();
5109
+ rootResizeObserver = undefined;
3550
5110
  slotResizeObserver?.disconnect();
3551
5111
  slotResizeObserver = undefined;
3552
5112
  cleanup?.();
@@ -3580,6 +5140,36 @@ function startSlexKitSandboxRunner() {
3580
5140
  height
3581
5141
  });
3582
5142
  }
5143
+ function reportFrameSize() {
5144
+ if (!activeId || !activeToken)
5145
+ return;
5146
+ const root = frameRoot();
5147
+ const rootRect = root.getBoundingClientRect();
5148
+ let contentBottom = rootRect.height;
5149
+ for (const child of root.querySelectorAll("*")) {
5150
+ const rect = child.getBoundingClientRect();
5151
+ contentBottom = Math.max(contentBottom, rect.bottom - rootRect.top);
5152
+ }
5153
+ const height = Math.max(root.scrollHeight, root.getBoundingClientRect().height, contentBottom, document.body?.scrollHeight ?? 0, document.documentElement?.scrollHeight ?? 0, 1);
5154
+ post({
5155
+ channel: "slexkit-secure",
5156
+ type: "frame-size",
5157
+ id: activeId,
5158
+ token: activeToken,
5159
+ height
5160
+ });
5161
+ }
5162
+ function observeFrameSize() {
5163
+ rootResizeObserver?.disconnect();
5164
+ rootResizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => reportFrameSize()) : undefined;
5165
+ const root = frameRoot();
5166
+ rootResizeObserver?.observe(root);
5167
+ for (const child of root.children) {
5168
+ rootResizeObserver?.observe(child);
5169
+ }
5170
+ reportFrameSize();
5171
+ schedulingSnapshot.requestAnimationFrame?.(() => reportFrameSize());
5172
+ }
3583
5173
  function applySlotRects(slots) {
3584
5174
  const root = frameRoot();
3585
5175
  const multiSlot = slots.length > 1;
@@ -3636,6 +5226,7 @@ function startSlexKitSandboxRunner() {
3636
5226
  api: runtime.api
3637
5227
  });
3638
5228
  startHeartbeat(message.policy, message.id, message.token);
5229
+ observeFrameSize();
3639
5230
  post({
3640
5231
  channel: "slexkit-secure",
3641
5232
  type: "mounted",
@@ -3702,7 +5293,7 @@ function startSlexKitSandboxRunner() {
3702
5293
  }
3703
5294
 
3704
5295
  // src/runtime.ts
3705
- setSlexKitRuntimeUrl("file:///D:/Project/slexkit/src/runtime.ts");
5296
+ setSlexKitRuntimeUrl("file:///home/runner/work/slexkit/slexkit/src/runtime.ts");
3706
5297
  var mountApi = mount;
3707
5298
  var ingestApi = ingest;
3708
5299
  var bootApi = boot;
@@ -3715,6 +5306,7 @@ var formatSlexKitDiagnosticApi = formatSlexKitDiagnostic;
3715
5306
  var mountSecureArtifactApi = mountSecureArtifact;
3716
5307
  var parseSlexSourceApi = parseSlexSource;
3717
5308
  var parseSlexKitDslApi = parseSlexKitDsl;
5309
+ var validateSlexSourceApi = validateSlexSource;
3718
5310
  var createSecureRuntimeApi = createSecureRuntime;
3719
5311
  var SlexKitRuntimeErrorApi = SlexKitRuntimeError;
3720
5312
  var getSlexKitRuntimeUrlApi = getSlexKitRuntimeUrl;
@@ -3722,6 +5314,7 @@ var setSlexKitRuntimeUrlApi = setSlexKitRuntimeUrl;
3722
5314
  var createSlexKitMarkdownRuntimeHostApi = createSlexKitMarkdownRuntimeHost;
3723
5315
  var getSlexKitMarkdownRuntimeHostApi = getSlexKitMarkdownRuntimeHost;
3724
5316
  var installSlexKitMarkdownRuntimeHostApi = installSlexKitMarkdownRuntimeHost;
5317
+ var slexkitStdApi = slexkitStd;
3725
5318
  var attachComponentDisposerApi = attachComponentDisposer;
3726
5319
  var configureComponentScopeApi = configureComponentScope;
3727
5320
  var startSlexKitSandboxRunnerApi = startSlexKitSandboxRunner;