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.
- package/CHANGELOG.md +70 -0
- package/LICENSE +21 -21
- package/README.md +4 -3
- package/README.zh-CN.md +4 -3
- package/dist/ai/llms-authoring.txt +2 -0
- package/dist/ai/llms-capabilities.txt +126 -0
- package/dist/ai/llms-components.txt +29 -7
- package/dist/ai/llms-full.txt +1909 -153
- package/dist/ai/llms-runtime.txt +18 -13
- package/dist/ai/llms.txt +22 -1
- package/dist/ai/slexkit-ai-manifest.json +717 -62
- package/dist/base.css +359 -359
- package/dist/chunks/{accordion-cfjyxw93.js → button-53ccjq5p.js} +11 -11
- package/dist/chunks/{accordion-nw12ytps.js → button-cr1fhsa7.js} +48 -2
- package/dist/chunks/{accordion-5f0nvjjm.js → button-dsftwzvg.js} +4 -3
- package/dist/chunks/{accordion-hzyrngd6.js → button-faf563xf.js} +2 -2
- package/dist/chunks/{accordion-ehnhpeca.js → button-jxv4c65t.js} +2 -2
- package/dist/chunks/{accordion-cw5r75jm.js → button-xv2dz7vn.js} +1 -1
- package/dist/chunks/{accordion-830dw78f.js → button-z5yv24ks.js} +2 -2
- package/dist/components/accordion.js +2 -2
- package/dist/components/badge.js +2 -2
- package/dist/components/button.css +101 -101
- package/dist/components/button.js +3 -3
- package/dist/components/callout.js +4 -4
- package/dist/components/card.js +2 -2
- package/dist/components/checkbox.js +3 -2
- package/dist/components/choice.css +151 -151
- package/dist/components/code-block.js +2 -2
- package/dist/components/collapsible.js +2 -2
- package/dist/components/column.js +1 -1
- package/dist/components/content.css +273 -250
- package/dist/components/display.css +1 -1
- package/dist/components/divider.js +2 -2
- package/dist/components/grid.js +1 -1
- package/dist/components/index.js +13994 -172
- package/dist/components/input.css +786 -852
- package/dist/components/input.js +34 -144
- package/dist/components/link.js +2 -2
- package/dist/components/progress.js +2 -2
- package/dist/components/radio-group.js +3 -2
- package/dist/components/row.js +1 -1
- package/dist/components/section.js +2 -2
- package/dist/components/select.css +175 -181
- package/dist/components/select.js +3 -3
- package/dist/components/slider.css +125 -116
- package/dist/components/slider.js +2 -2
- package/dist/components/specs.js +34 -1
- package/dist/components/stat.js +2 -2
- package/dist/components/submit.css +8 -8
- package/dist/components/submit.js +1 -1
- package/dist/components/switch.css +105 -105
- package/dist/components/switch.js +4 -3
- package/dist/components/table.js +4 -4
- package/dist/components/tabs.css +95 -95
- package/dist/components/tabs.js +4 -4
- package/dist/components/text-input.css +26 -95
- package/dist/components/text.js +13 -1
- package/dist/components/toast.js +4 -4
- package/dist/components/tooling.css +0 -1
- package/dist/components/tooling.js +73 -8
- package/dist/runtime.cjs +1610 -17
- package/dist/runtime.js +1609 -16
- package/dist/slexkit.cjs +28191 -13865
- package/dist/slexkit.css +1525 -1569
- package/dist/slexkit.js +28190 -13864
- package/dist/tooling.js +117 -11
- package/dist/types/components/svelte/helpers.d.ts +8 -1
- package/dist/types/engine/capabilities.d.ts +54 -0
- package/dist/types/engine/index.d.ts +6 -0
- package/dist/types/engine/secure-runtime.d.ts +9 -1
- package/dist/types/engine/stdlib.d.ts +30 -0
- package/dist/types/engine/types.d.ts +1 -0
- package/dist/types/engine/validation.d.ts +28 -0
- package/dist/types/index.d.ts +6 -3
- package/dist/types/runtime.d.ts +6 -3
- package/dist/types/version.d.ts +2 -2
- package/dist/umd/slexkit.tooling.umd.js +45084 -44775
- package/dist/umd/slexkit.umd.js +28191 -13865
- package/package.json +5 -3
- package/skills/slexkit-host-integration/SKILL.md +1 -1
- package/src/components/svelte/content/Formula.svelte +27 -0
- package/src/components/svelte/content/Table.svelte +1 -1
- package/src/components/svelte/display/Text.svelte +14 -1
- package/src/components/svelte/helpers.ts +56 -1
- package/src/components/svelte/input/Checkbox.svelte +1 -1
- package/src/components/svelte/input/Input.svelte +0 -110
- package/src/components/svelte/input/RadioGroup.svelte +1 -1
- package/src/components/svelte/input/Select.svelte +2 -2
- package/src/components/svelte/input/Switch.svelte +2 -2
- package/src/components/svelte/input/Tabs.svelte +7 -7
- package/src/components/svelte/tooling/PlaygroundMarkdown.svelte +84 -2
- package/src/styles/components/button.css +101 -101
- package/src/styles/components/choice.css +152 -152
- package/src/styles/components/select.css +175 -181
- package/src/styles/components/slider.css +125 -116
- package/src/styles/components/submit.css +8 -8
- package/src/styles/components/switch.css +105 -105
- package/src/styles/components/tabs.css +95 -95
- package/src/styles/components/text-input.css +26 -95
- package/src/styles/content.css +274 -251
- package/src/styles/display.css +1 -1
- package/src/styles/input.css +8 -8
- package/src/styles/layout.css +360 -360
- package/src/styles/tooling.css +0 -1
package/dist/runtime.js
CHANGED
|
@@ -513,6 +513,163 @@ function isEngineeringNumberResult(value) {
|
|
|
513
513
|
return !!value && typeof value === "object" && "raw" in value && "number" in value && "valid" in value && "prefix" in value && "unit" in value && "normalized" in value;
|
|
514
514
|
}
|
|
515
515
|
|
|
516
|
+
// src/engine/stdlib.ts
|
|
517
|
+
function toNumber(value) {
|
|
518
|
+
return Number(value);
|
|
519
|
+
}
|
|
520
|
+
function finiteNumber(value, fallback = 0) {
|
|
521
|
+
const parsed = toNumber(value);
|
|
522
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
523
|
+
}
|
|
524
|
+
function digits(value, fallback) {
|
|
525
|
+
const parsed = Math.trunc(finiteNumber(value, fallback));
|
|
526
|
+
return Math.max(0, Math.min(20, parsed));
|
|
527
|
+
}
|
|
528
|
+
function finiteValues(values) {
|
|
529
|
+
if (!Array.isArray(values))
|
|
530
|
+
return [];
|
|
531
|
+
return values.map(toNumber).filter(Number.isFinite);
|
|
532
|
+
}
|
|
533
|
+
function formatFixed(value, digitCount = 2) {
|
|
534
|
+
const parsed = toNumber(value);
|
|
535
|
+
return Number.isFinite(parsed) ? parsed.toFixed(digitCount) : "NaN";
|
|
536
|
+
}
|
|
537
|
+
function locale(value) {
|
|
538
|
+
return typeof value === "string" && value.trim() ? value : "en-US";
|
|
539
|
+
}
|
|
540
|
+
function currencyCode(value) {
|
|
541
|
+
return typeof value === "string" && /^[A-Za-z]{3}$/.test(value) ? value.toUpperCase() : "USD";
|
|
542
|
+
}
|
|
543
|
+
function formatNumber(value, digitCount, localeName, notation, style, currency, minimumFractionDigits = 0) {
|
|
544
|
+
const parsed = toNumber(value);
|
|
545
|
+
if (!Number.isFinite(parsed))
|
|
546
|
+
return "NaN";
|
|
547
|
+
return new Intl.NumberFormat(localeName, {
|
|
548
|
+
maximumFractionDigits: digitCount,
|
|
549
|
+
minimumFractionDigits,
|
|
550
|
+
notation,
|
|
551
|
+
style,
|
|
552
|
+
currency
|
|
553
|
+
}).format(parsed);
|
|
554
|
+
}
|
|
555
|
+
function freezeDeep(value) {
|
|
556
|
+
for (const child of Object.values(value)) {
|
|
557
|
+
if (child && typeof child === "object")
|
|
558
|
+
freezeDeep(child);
|
|
559
|
+
}
|
|
560
|
+
return Object.freeze(value);
|
|
561
|
+
}
|
|
562
|
+
var slexkitStd = freezeDeep({
|
|
563
|
+
math: {
|
|
564
|
+
clamp(value, min, max) {
|
|
565
|
+
const lower = finiteNumber(min);
|
|
566
|
+
const upper = finiteNumber(max);
|
|
567
|
+
const parsed = finiteNumber(value);
|
|
568
|
+
return Math.min(Math.max(parsed, Math.min(lower, upper)), Math.max(lower, upper));
|
|
569
|
+
},
|
|
570
|
+
round(value, digitValue = 0) {
|
|
571
|
+
const factor = 10 ** digits(digitValue, 0);
|
|
572
|
+
return Math.round(finiteNumber(value) * factor) / factor;
|
|
573
|
+
},
|
|
574
|
+
safeDivide(numerator, denominator, fallback = 0) {
|
|
575
|
+
const divisor = toNumber(denominator);
|
|
576
|
+
if (!Number.isFinite(divisor) || divisor === 0)
|
|
577
|
+
return finiteNumber(fallback);
|
|
578
|
+
const quotient = toNumber(numerator) / divisor;
|
|
579
|
+
return Number.isFinite(quotient) ? quotient : finiteNumber(fallback);
|
|
580
|
+
},
|
|
581
|
+
percent(part, total, digitValue = 1) {
|
|
582
|
+
const ratio = slexkitStd.math.safeDivide(part, total, NaN) * 100;
|
|
583
|
+
return slexkitStd.math.round(ratio, digitValue);
|
|
584
|
+
},
|
|
585
|
+
lerp(start, end, t) {
|
|
586
|
+
const a = finiteNumber(start);
|
|
587
|
+
return a + (finiteNumber(end) - a) * finiteNumber(t);
|
|
588
|
+
}
|
|
589
|
+
},
|
|
590
|
+
stats: {
|
|
591
|
+
sum(values) {
|
|
592
|
+
return finiteValues(values).reduce((total, value) => total + value, 0);
|
|
593
|
+
},
|
|
594
|
+
mean(values) {
|
|
595
|
+
const numbers = finiteValues(values);
|
|
596
|
+
return numbers.length ? slexkitStd.stats.sum(numbers) / numbers.length : NaN;
|
|
597
|
+
},
|
|
598
|
+
min(values) {
|
|
599
|
+
const numbers = finiteValues(values);
|
|
600
|
+
return numbers.length ? Math.min(...numbers) : NaN;
|
|
601
|
+
},
|
|
602
|
+
max(values) {
|
|
603
|
+
const numbers = finiteValues(values);
|
|
604
|
+
return numbers.length ? Math.max(...numbers) : NaN;
|
|
605
|
+
},
|
|
606
|
+
median(values) {
|
|
607
|
+
const numbers = finiteValues(values).sort((a, b) => a - b);
|
|
608
|
+
if (!numbers.length)
|
|
609
|
+
return NaN;
|
|
610
|
+
const middle = Math.floor(numbers.length / 2);
|
|
611
|
+
return numbers.length % 2 ? numbers[middle] : (numbers[middle - 1] + numbers[middle]) / 2;
|
|
612
|
+
}
|
|
613
|
+
},
|
|
614
|
+
format: {
|
|
615
|
+
fixed(value, digitValue = 2) {
|
|
616
|
+
return formatFixed(value, digits(digitValue, 2));
|
|
617
|
+
},
|
|
618
|
+
number(value, digitValue = 0, localeName = "en-US") {
|
|
619
|
+
return formatNumber(value, digits(digitValue, 0), locale(localeName));
|
|
620
|
+
},
|
|
621
|
+
compact(value, digitValue = 1, localeName = "en-US") {
|
|
622
|
+
return formatNumber(value, digits(digitValue, 1), locale(localeName), "compact");
|
|
623
|
+
},
|
|
624
|
+
percent(ratio, digitValue = 1) {
|
|
625
|
+
return `${formatFixed(finiteNumber(ratio) * 100, digits(digitValue, 1))}%`;
|
|
626
|
+
},
|
|
627
|
+
currency(value, currency = "USD", localeName = "en-US") {
|
|
628
|
+
return formatNumber(value, 2, locale(localeName), undefined, "currency", currencyCode(currency), 2);
|
|
629
|
+
}
|
|
630
|
+
},
|
|
631
|
+
units: {
|
|
632
|
+
withUnit(value, unit, digitValue = 2) {
|
|
633
|
+
const suffix = typeof unit === "string" ? unit : String(unit ?? "");
|
|
634
|
+
return `${formatFixed(value, digits(digitValue, 2))}${suffix ? ` ${suffix}` : ""}`;
|
|
635
|
+
},
|
|
636
|
+
bytes(value, digitValue = 1) {
|
|
637
|
+
const units = ["B", "KB", "MB", "GB", "TB", "PB"];
|
|
638
|
+
let amount = Math.abs(finiteNumber(value));
|
|
639
|
+
let index = 0;
|
|
640
|
+
while (amount >= 1024 && index < units.length - 1) {
|
|
641
|
+
amount /= 1024;
|
|
642
|
+
index += 1;
|
|
643
|
+
}
|
|
644
|
+
const sign = finiteNumber(value) < 0 ? -1 : 1;
|
|
645
|
+
return `${formatFixed(amount * sign, digits(digitValue, 1))} ${units[index]}`;
|
|
646
|
+
},
|
|
647
|
+
duration(ms, digitValue = 1) {
|
|
648
|
+
const value = finiteNumber(ms);
|
|
649
|
+
const abs = Math.abs(value);
|
|
650
|
+
if (abs < 1000)
|
|
651
|
+
return `${formatFixed(value, 0)} ms`;
|
|
652
|
+
if (abs < 60000)
|
|
653
|
+
return `${formatFixed(value / 1000, digits(digitValue, 1))} s`;
|
|
654
|
+
if (abs < 3600000)
|
|
655
|
+
return `${formatFixed(value / 60000, digits(digitValue, 1))} min`;
|
|
656
|
+
return `${formatFixed(value / 3600000, digits(digitValue, 1))} h`;
|
|
657
|
+
},
|
|
658
|
+
si(value, unit = "", digitValue = 2) {
|
|
659
|
+
const units = ["", "k", "M", "G", "T", "P"];
|
|
660
|
+
let amount = Math.abs(finiteNumber(value));
|
|
661
|
+
let index = 0;
|
|
662
|
+
while (amount >= 1000 && index < units.length - 1) {
|
|
663
|
+
amount /= 1000;
|
|
664
|
+
index += 1;
|
|
665
|
+
}
|
|
666
|
+
const sign = finiteNumber(value) < 0 ? -1 : 1;
|
|
667
|
+
const suffix = typeof unit === "string" ? unit : String(unit ?? "");
|
|
668
|
+
return `${formatFixed(amount * sign, digits(digitValue, 2))} ${units[index]}${suffix}`.trim();
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
|
|
516
673
|
// src/engine/component-state.ts
|
|
517
674
|
var IDENTIFIER = /^[A-Za-z_$][\w$]*$/;
|
|
518
675
|
var componentStateProxies = new WeakMap;
|
|
@@ -532,6 +689,9 @@ function isWritableComponent(type) {
|
|
|
532
689
|
const mode = getComponentStateMode(type);
|
|
533
690
|
return mode === "value" || mode === "checked" || mode === "enabled";
|
|
534
691
|
}
|
|
692
|
+
function isReadableComponent(type) {
|
|
693
|
+
return getComponentStateMode(type) === "readable";
|
|
694
|
+
}
|
|
535
695
|
function isStatefulComponent(type) {
|
|
536
696
|
return getComponentStateMode(type) !== "none";
|
|
537
697
|
}
|
|
@@ -648,7 +808,10 @@ function createGProxy(g, components, componentTypes) {
|
|
|
648
808
|
function ensureComponentState(name, type, components, componentTypes) {
|
|
649
809
|
if (!components[name])
|
|
650
810
|
components[name] = {};
|
|
651
|
-
componentTypes[name]
|
|
811
|
+
const previousType = componentTypes[name] ?? "";
|
|
812
|
+
if (!(isWritableComponent(previousType) && isReadableComponent(type))) {
|
|
813
|
+
componentTypes[name] = type;
|
|
814
|
+
}
|
|
652
815
|
return components[name];
|
|
653
816
|
}
|
|
654
817
|
function syncReadableComponentProp(type, state, propName, value) {
|
|
@@ -676,6 +839,9 @@ function syncReadableComponentProp(type, state, propName, value) {
|
|
|
676
839
|
function syncComponentProps(type, name, props, components, componentTypes) {
|
|
677
840
|
if (!name || !isStatefulComponent(type))
|
|
678
841
|
return;
|
|
842
|
+
const previousType = componentTypes[name] ?? "";
|
|
843
|
+
if (isWritableComponent(previousType) && isReadableComponent(type))
|
|
844
|
+
return;
|
|
679
845
|
const state = ensureComponentState(name, type, components, componentTypes);
|
|
680
846
|
if (type === "input" && typeof props.type === "string") {
|
|
681
847
|
assignInputType(state, props.type);
|
|
@@ -740,8 +906,25 @@ function seedStaticComponentState(type, state, props) {
|
|
|
740
906
|
function warnDuplicateState(ns, name, currentType, currentPath, previous) {
|
|
741
907
|
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.`);
|
|
742
908
|
if (previous.type !== currentType) {
|
|
743
|
-
console.warn(`[SlexKit][${ns}] Component state '${name}' is used by multiple component types (${previous.type}, ${currentType});
|
|
909
|
+
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.`);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
function dynamicStateBinding(type, props) {
|
|
913
|
+
const mode = getComponentStateMode(type);
|
|
914
|
+
const value = mode === "checked" ? props.$checked ?? props.$value : mode === "enabled" ? props.$enabled : props.$value;
|
|
915
|
+
return typeof value === "string" ? value.trim() : undefined;
|
|
916
|
+
}
|
|
917
|
+
function isMirroredValueControlPair(previousType, currentType) {
|
|
918
|
+
return previousType === "input" && currentType === "slider" || previousType === "slider" && currentType === "input";
|
|
919
|
+
}
|
|
920
|
+
function shouldWarnDuplicateState(currentType, currentBinding, previous) {
|
|
921
|
+
if (isReadableComponent(previous.type) && isReadableComponent(currentType)) {
|
|
922
|
+
return false;
|
|
923
|
+
}
|
|
924
|
+
if (currentBinding && previous.stateBinding === currentBinding && isMirroredValueControlPair(previous.type, currentType)) {
|
|
925
|
+
return false;
|
|
744
926
|
}
|
|
927
|
+
return true;
|
|
745
928
|
}
|
|
746
929
|
function warnForState(ns, name, path) {
|
|
747
930
|
console.warn(`[SlexKit][${ns}] Component state '${name}' is used with $for at ${path}; repeated items share one namespace-level instance state.`);
|
|
@@ -756,11 +939,15 @@ function prepareComponentStatesInner(layout, components, componentTypes, ns, see
|
|
|
756
939
|
const props = val;
|
|
757
940
|
const path = parentPath ? `${parentPath}.${key}` : key;
|
|
758
941
|
if (name && isStatefulComponent(type)) {
|
|
942
|
+
const stateBinding = dynamicStateBinding(type, props);
|
|
759
943
|
const previous = seen.get(name);
|
|
760
|
-
if (previous)
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
944
|
+
if (previous) {
|
|
945
|
+
if (shouldWarnDuplicateState(type, stateBinding, previous)) {
|
|
946
|
+
warnDuplicateState(ns, name, type, path, previous);
|
|
947
|
+
}
|
|
948
|
+
} else {
|
|
949
|
+
seen.set(name, { type, path, stateBinding });
|
|
950
|
+
}
|
|
764
951
|
if (props.$for && isWritableComponent(type))
|
|
765
952
|
warnForState(ns, name, path);
|
|
766
953
|
const state = ensureComponentState(name, type, components, componentTypes);
|
|
@@ -773,9 +960,15 @@ function prepareComponentStates(layout, components, componentTypes, ns) {
|
|
|
773
960
|
prepareComponentStatesInner(layout, components, componentTypes, ns, new Map);
|
|
774
961
|
}
|
|
775
962
|
function buildComponentEvalContext(g, components, componentTypes, api, forCtx) {
|
|
776
|
-
const ctx = {
|
|
963
|
+
const ctx = {
|
|
964
|
+
g: createGProxy(g, components, componentTypes),
|
|
965
|
+
std: slexkitStd
|
|
966
|
+
};
|
|
967
|
+
const gKeys = new Set(Object.keys(rawRecord(g)));
|
|
777
968
|
for (const name of Object.keys(rawRecord(components))) {
|
|
778
|
-
if (
|
|
969
|
+
if (name === "std" || name === "g" || name === "api")
|
|
970
|
+
continue;
|
|
971
|
+
if (IDENTIFIER.test(name) && !gKeys.has(name)) {
|
|
779
972
|
ctx[name] = publicComponentState(name, components[name], componentTypes);
|
|
780
973
|
}
|
|
781
974
|
}
|
|
@@ -783,6 +976,8 @@ function buildComponentEvalContext(g, components, componentTypes, api, forCtx) {
|
|
|
783
976
|
ctx.api = api;
|
|
784
977
|
if (forCtx) {
|
|
785
978
|
for (const k of Object.keys(forCtx)) {
|
|
979
|
+
if (k === "std")
|
|
980
|
+
continue;
|
|
786
981
|
Object.defineProperty(ctx, k, {
|
|
787
982
|
get: () => {
|
|
788
983
|
const current = forCtx[k];
|
|
@@ -953,6 +1148,7 @@ function renderIfNode(fullKey, props, container, g, components, componentTypes,
|
|
|
953
1148
|
};
|
|
954
1149
|
currentEl = renderWithFallback(renderer, innerProps, name, {
|
|
955
1150
|
g,
|
|
1151
|
+
std: slexkitStd,
|
|
956
1152
|
api,
|
|
957
1153
|
dir: options.dir,
|
|
958
1154
|
labels: options.labels,
|
|
@@ -1031,6 +1227,7 @@ function renderAndMountSlot(item, index, keyVal, indexSignal, revisionSignal, re
|
|
|
1031
1227
|
};
|
|
1032
1228
|
el = renderWithFallback(renderer, innerProps, name, {
|
|
1033
1229
|
g,
|
|
1230
|
+
std: slexkitStd,
|
|
1034
1231
|
api,
|
|
1035
1232
|
dir: options.dir,
|
|
1036
1233
|
labels: options.labels,
|
|
@@ -1180,6 +1377,7 @@ function renderNormalNode(fullKey, props, container, g, components, componentTyp
|
|
|
1180
1377
|
};
|
|
1181
1378
|
const el = renderWithFallback(renderer, nodeProps, name, {
|
|
1182
1379
|
g,
|
|
1380
|
+
std: slexkitStd,
|
|
1183
1381
|
api,
|
|
1184
1382
|
dir: options.dir,
|
|
1185
1383
|
labels: options.labels,
|
|
@@ -1533,7 +1731,7 @@ ${parseSource}
|
|
|
1533
1731
|
var parseSlexKitDsl = parseSlexSource;
|
|
1534
1732
|
|
|
1535
1733
|
// src/version.ts
|
|
1536
|
-
var SLEXKIT_VERSION = "0.
|
|
1734
|
+
var SLEXKIT_VERSION = "0.3.1";
|
|
1537
1735
|
var SLEX_PROTOCOL_VERSION = "0.1";
|
|
1538
1736
|
var SLEXKIT_COMPONENTS_VERSION = SLEXKIT_VERSION;
|
|
1539
1737
|
function getSlexKitInfo() {
|
|
@@ -1573,6 +1771,9 @@ function currentOrigin() {
|
|
|
1573
1771
|
return "http://localhost";
|
|
1574
1772
|
}
|
|
1575
1773
|
function resolveUrl(url) {
|
|
1774
|
+
try {
|
|
1775
|
+
return new URL(url);
|
|
1776
|
+
} catch {}
|
|
1576
1777
|
try {
|
|
1577
1778
|
return new URL(url, currentOrigin());
|
|
1578
1779
|
} catch {
|
|
@@ -2133,6 +2334,1325 @@ function createSecureRuntime(policy, hostAdapter) {
|
|
|
2133
2334
|
}
|
|
2134
2335
|
};
|
|
2135
2336
|
}
|
|
2337
|
+
// src/components/spec-helpers.ts
|
|
2338
|
+
var SINCE = "0.1.0";
|
|
2339
|
+
function docs(type) {
|
|
2340
|
+
return {
|
|
2341
|
+
href: `/docs/components/${type}`,
|
|
2342
|
+
anchors: {
|
|
2343
|
+
api: `${type}-api-reference`,
|
|
2344
|
+
examples: `${type}-canonical-example`
|
|
2345
|
+
}
|
|
2346
|
+
};
|
|
2347
|
+
}
|
|
2348
|
+
function isSlexExpression(value) {
|
|
2349
|
+
return typeof value.namespace === "string" && typeof value.layout === "object" && value.layout !== null;
|
|
2350
|
+
}
|
|
2351
|
+
function example(type, propsOrSource, title = "Basic usage") {
|
|
2352
|
+
return {
|
|
2353
|
+
id: "basic",
|
|
2354
|
+
title,
|
|
2355
|
+
source: isSlexExpression(propsOrSource) ? {
|
|
2356
|
+
slex: propsOrSource.slex ?? SLEX_PROTOCOL_VERSION,
|
|
2357
|
+
...propsOrSource
|
|
2358
|
+
} : {
|
|
2359
|
+
slex: SLEX_PROTOCOL_VERSION,
|
|
2360
|
+
namespace: `spec_${type.replaceAll("-", "_")}_basic`,
|
|
2361
|
+
layout: {
|
|
2362
|
+
[`${type}:demo`]: propsOrSource
|
|
2363
|
+
}
|
|
2364
|
+
}
|
|
2365
|
+
};
|
|
2366
|
+
}
|
|
2367
|
+
var semanticTones = ["info", "success", "warning", "danger", "muted"];
|
|
2368
|
+
var inputState = ["input", "slider", "select", "tabs", "radio-group"];
|
|
2369
|
+
var checkedState = ["checkbox"];
|
|
2370
|
+
var enabledState = ["switch"];
|
|
2371
|
+
var readableState = ["stat", "text", "progress", "badge", "callout", "code-block", "divider", "link", "table", "section"];
|
|
2372
|
+
function stateFor(type) {
|
|
2373
|
+
if (inputState.includes(type))
|
|
2374
|
+
return "value";
|
|
2375
|
+
if (checkedState.includes(type))
|
|
2376
|
+
return "checked";
|
|
2377
|
+
if (enabledState.includes(type))
|
|
2378
|
+
return "enabled";
|
|
2379
|
+
if (readableState.includes(type))
|
|
2380
|
+
return "readable";
|
|
2381
|
+
return "none";
|
|
2382
|
+
}
|
|
2383
|
+
function component(spec) {
|
|
2384
|
+
return {
|
|
2385
|
+
status: "ready",
|
|
2386
|
+
since: SINCE,
|
|
2387
|
+
docs: docs(spec.type),
|
|
2388
|
+
state: spec.state ?? stateFor(spec.type),
|
|
2389
|
+
...spec
|
|
2390
|
+
};
|
|
2391
|
+
}
|
|
2392
|
+
var childContent = {
|
|
2393
|
+
allowed: true,
|
|
2394
|
+
description: "Nested component fields are rendered as child content in field order."
|
|
2395
|
+
};
|
|
2396
|
+
var noChildren = {
|
|
2397
|
+
allowed: false,
|
|
2398
|
+
description: "This component does not render nested child components."
|
|
2399
|
+
};
|
|
2400
|
+
|
|
2401
|
+
// src/components/entries/accordion.spec.ts
|
|
2402
|
+
var accordionSpec = component({
|
|
2403
|
+
type: "accordion",
|
|
2404
|
+
category: "Disclosure",
|
|
2405
|
+
title: "Accordion",
|
|
2406
|
+
summary: "Expandable grouped panels.",
|
|
2407
|
+
description: "Use accordion to reveal one or more sections from a compact list of panels.",
|
|
2408
|
+
props: {
|
|
2409
|
+
value: { type: "string | string[]", dynamic: true, description: "Current expanded item value; use an array when multiple is true." },
|
|
2410
|
+
multiple: { type: "boolean", default: false, description: "Allow multiple items to be expanded at the same time." },
|
|
2411
|
+
items: { type: "array", description: "Panel definitions with value, label, content, and optional icon." },
|
|
2412
|
+
"items[].icon": { type: "string", description: "Icon name shown before an item trigger label." },
|
|
2413
|
+
onchange: { type: "write-expression", description: "Write expression invoked when expanded items change." }
|
|
2414
|
+
},
|
|
2415
|
+
children: noChildren,
|
|
2416
|
+
examples: [example("accordion", {
|
|
2417
|
+
namespace: "doc_accordion_typical",
|
|
2418
|
+
layout: {
|
|
2419
|
+
"accordion:faq": {
|
|
2420
|
+
multiple: true,
|
|
2421
|
+
value: [
|
|
2422
|
+
"install"
|
|
2423
|
+
],
|
|
2424
|
+
items: [
|
|
2425
|
+
{
|
|
2426
|
+
value: "install",
|
|
2427
|
+
label: "Install",
|
|
2428
|
+
icon: "download-simple",
|
|
2429
|
+
content: "Prepare dependencies."
|
|
2430
|
+
},
|
|
2431
|
+
{
|
|
2432
|
+
value: "review",
|
|
2433
|
+
label: "Review",
|
|
2434
|
+
icon: "check-circle",
|
|
2435
|
+
content: "Check the result."
|
|
2436
|
+
},
|
|
2437
|
+
{
|
|
2438
|
+
value: "ship",
|
|
2439
|
+
label: "Ship",
|
|
2440
|
+
icon: "rocket-launch",
|
|
2441
|
+
content: "Publish the change."
|
|
2442
|
+
}
|
|
2443
|
+
]
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
})]
|
|
2447
|
+
});
|
|
2448
|
+
|
|
2449
|
+
// src/components/entries/badge.spec.ts
|
|
2450
|
+
var badgeSpec = component({
|
|
2451
|
+
type: "badge",
|
|
2452
|
+
category: "Content",
|
|
2453
|
+
title: "Badge",
|
|
2454
|
+
summary: "Compact label for status or classification.",
|
|
2455
|
+
description: "Use badge to annotate nearby content with a short semantic label.",
|
|
2456
|
+
props: {
|
|
2457
|
+
label: { type: "string", dynamic: true, description: "Badge text." },
|
|
2458
|
+
text: { type: "string", dynamic: true, description: "Alias for label." },
|
|
2459
|
+
content: { type: "string", dynamic: true, description: "Alias for label." },
|
|
2460
|
+
icon: { type: "string", description: "Icon name shown before the badge label." },
|
|
2461
|
+
tone: { type: "string", values: semanticTones, default: "info", description: "Semantic tone applied to the badge." },
|
|
2462
|
+
variant: { type: "string", values: semanticTones, description: "Alias for tone." }
|
|
2463
|
+
},
|
|
2464
|
+
children: noChildren,
|
|
2465
|
+
examples: [example("badge", {
|
|
2466
|
+
namespace: "doc_badge_typical",
|
|
2467
|
+
layout: {
|
|
2468
|
+
"row:badges": {
|
|
2469
|
+
"badge:ready": {
|
|
2470
|
+
label: "ready",
|
|
2471
|
+
icon: "check-circle",
|
|
2472
|
+
tone: "success"
|
|
2473
|
+
},
|
|
2474
|
+
"badge:pending": {
|
|
2475
|
+
label: "pending",
|
|
2476
|
+
tone: "warning"
|
|
2477
|
+
},
|
|
2478
|
+
"badge:info": {
|
|
2479
|
+
label: "info",
|
|
2480
|
+
tone: "info"
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
})]
|
|
2485
|
+
});
|
|
2486
|
+
|
|
2487
|
+
// src/components/entries/button.spec.ts
|
|
2488
|
+
var buttonSpec = component({
|
|
2489
|
+
type: "button",
|
|
2490
|
+
category: "Action",
|
|
2491
|
+
title: "Button",
|
|
2492
|
+
summary: "Action trigger.",
|
|
2493
|
+
description: "Use button for explicit actions in interactive SlexKit layouts.",
|
|
2494
|
+
props: {
|
|
2495
|
+
label: { type: "string", dynamic: true, description: "Visible button text and accessible name." },
|
|
2496
|
+
icon: { type: "string", description: "Icon name shown before the label." },
|
|
2497
|
+
iconOnly: { type: "boolean", default: false, description: "Show only the icon while retaining label as the accessible name." },
|
|
2498
|
+
variant: { type: "string", values: ["primary", "secondary", "danger", "ghost"], default: "primary", description: "Semantic action variant." },
|
|
2499
|
+
disabled: { type: "boolean", default: false, dynamic: true, description: "Disable the action." },
|
|
2500
|
+
href: { type: "string", dynamic: true, description: "Render the button surface as a link to this URL." },
|
|
2501
|
+
target: { type: "string", description: "Link target used when href is present." },
|
|
2502
|
+
title: { type: "string", dynamic: true, description: "Tooltip and accessible-label fallback." },
|
|
2503
|
+
selected: { type: "boolean", dynamic: true, description: "Render the icon in its selected visual state." },
|
|
2504
|
+
active: { type: "boolean", dynamic: true, description: "Render the icon in its active visual state." },
|
|
2505
|
+
pressed: { type: "boolean", dynamic: true, description: "Expose pressed state and render the selected icon style." },
|
|
2506
|
+
onclick: { type: "write-expression", description: "Write expression invoked when the button is clicked." }
|
|
2507
|
+
},
|
|
2508
|
+
children: noChildren,
|
|
2509
|
+
examples: [example("button", {
|
|
2510
|
+
namespace: "doc_button_typical",
|
|
2511
|
+
layout: {
|
|
2512
|
+
"row:actions": {
|
|
2513
|
+
"button:save": {
|
|
2514
|
+
label: "Save",
|
|
2515
|
+
icon: "floppy-disk",
|
|
2516
|
+
variant: "primary"
|
|
2517
|
+
},
|
|
2518
|
+
"button:cancel": {
|
|
2519
|
+
label: "Cancel",
|
|
2520
|
+
variant: "secondary"
|
|
2521
|
+
},
|
|
2522
|
+
"button:delete": {
|
|
2523
|
+
label: "Delete",
|
|
2524
|
+
variant: "danger"
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2528
|
+
})]
|
|
2529
|
+
});
|
|
2530
|
+
|
|
2531
|
+
// src/components/entries/callout.spec.ts
|
|
2532
|
+
var calloutSpec = component({
|
|
2533
|
+
type: "callout",
|
|
2534
|
+
category: "Content",
|
|
2535
|
+
title: "Callout",
|
|
2536
|
+
summary: "Highlighted contextual message.",
|
|
2537
|
+
description: "Use callout for notes, warnings, and other contextual blocks.",
|
|
2538
|
+
props: {
|
|
2539
|
+
title: { type: "string", dynamic: true, description: "Callout title." },
|
|
2540
|
+
heading: { type: "string", dynamic: true, description: "Alias for title." },
|
|
2541
|
+
label: { type: "string", dynamic: true, description: "Alias for title." },
|
|
2542
|
+
icon: { type: "string", description: "Icon name shown before the title." },
|
|
2543
|
+
text: { type: "string", dynamic: true, description: "Callout body text." },
|
|
2544
|
+
message: { type: "string", dynamic: true, description: "Alias for text." },
|
|
2545
|
+
content: { type: "string", dynamic: true, description: "Alias for text." },
|
|
2546
|
+
tone: { type: "string", values: ["info", "success", "warning", "danger"], default: "info", description: "Semantic tone for the callout." }
|
|
2547
|
+
},
|
|
2548
|
+
children: childContent,
|
|
2549
|
+
examples: [example("callout", {
|
|
2550
|
+
namespace: "doc_callout_typical",
|
|
2551
|
+
layout: {
|
|
2552
|
+
"callout:notice": {
|
|
2553
|
+
tone: "info",
|
|
2554
|
+
title: "Notice",
|
|
2555
|
+
icon: "info",
|
|
2556
|
+
text: "Use callout for information that should stand out."
|
|
2557
|
+
}
|
|
2558
|
+
}
|
|
2559
|
+
})]
|
|
2560
|
+
});
|
|
2561
|
+
|
|
2562
|
+
// src/components/entries/card.spec.ts
|
|
2563
|
+
var cardSpec = component({
|
|
2564
|
+
type: "card",
|
|
2565
|
+
category: "Layout",
|
|
2566
|
+
title: "Card",
|
|
2567
|
+
summary: "Bordered grouping container.",
|
|
2568
|
+
description: "Use card to group related content on a bounded surface.",
|
|
2569
|
+
props: {
|
|
2570
|
+
title: { type: "string", dynamic: true, description: "Card title." },
|
|
2571
|
+
icon: { type: "string", description: "Icon name shown before the title." },
|
|
2572
|
+
tone: { type: "string", values: semanticTones, description: "Optional semantic tone for the card surface." }
|
|
2573
|
+
},
|
|
2574
|
+
children: childContent,
|
|
2575
|
+
examples: [example("card", {
|
|
2576
|
+
namespace: "doc_card_typical",
|
|
2577
|
+
layout: {
|
|
2578
|
+
"card:metrics": {
|
|
2579
|
+
title: "Metrics",
|
|
2580
|
+
icon: "chart-bar",
|
|
2581
|
+
"grid:items": {
|
|
2582
|
+
columns: 2,
|
|
2583
|
+
"stat:requests": {
|
|
2584
|
+
label: "Requests",
|
|
2585
|
+
value: "1.2k",
|
|
2586
|
+
unit: "/min"
|
|
2587
|
+
},
|
|
2588
|
+
"stat:latency": {
|
|
2589
|
+
label: "Latency",
|
|
2590
|
+
value: "42",
|
|
2591
|
+
unit: "ms"
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
})]
|
|
2597
|
+
});
|
|
2598
|
+
|
|
2599
|
+
// src/components/entries/checkbox.spec.ts
|
|
2600
|
+
var checkboxSpec = component({
|
|
2601
|
+
type: "checkbox",
|
|
2602
|
+
category: "Input",
|
|
2603
|
+
title: "Checkbox",
|
|
2604
|
+
summary: "Boolean checkbox input.",
|
|
2605
|
+
description: "Use checkbox for binary choices that can be toggled independently.",
|
|
2606
|
+
props: {
|
|
2607
|
+
checked: { type: "boolean", default: false, dynamic: true, description: "Checked state." },
|
|
2608
|
+
label: { type: "string", dynamic: true, description: "Checkbox label." },
|
|
2609
|
+
icon: { type: "string", description: "Icon name shown before the visible label." },
|
|
2610
|
+
disabled: { type: "boolean", default: false, dynamic: true, description: "Disable the checkbox." },
|
|
2611
|
+
haptic: { type: "boolean", default: true, description: "Enable vibration feedback on supported devices." },
|
|
2612
|
+
haptics: { type: "boolean", default: true, description: "Alias for haptic." },
|
|
2613
|
+
onchange: { type: "write-expression", description: "Write expression invoked when checked state changes." }
|
|
2614
|
+
},
|
|
2615
|
+
children: noChildren,
|
|
2616
|
+
examples: [example("checkbox", {
|
|
2617
|
+
namespace: "doc_checkbox_typical",
|
|
2618
|
+
layout: {
|
|
2619
|
+
"checkbox:agree": {
|
|
2620
|
+
checked: true,
|
|
2621
|
+
label: "I agree",
|
|
2622
|
+
icon: "handshake"
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2625
|
+
})]
|
|
2626
|
+
});
|
|
2627
|
+
|
|
2628
|
+
// src/components/entries/code-block.spec.ts
|
|
2629
|
+
var codeBlockSpec = component({
|
|
2630
|
+
type: "code-block",
|
|
2631
|
+
category: "Content",
|
|
2632
|
+
title: "Code Block",
|
|
2633
|
+
summary: "Formatted code or log block.",
|
|
2634
|
+
description: "Use code-block for static code samples, configuration snippets, and logs.",
|
|
2635
|
+
props: {
|
|
2636
|
+
code: { type: "string", dynamic: true, description: "Code text content." },
|
|
2637
|
+
source: { type: "string", dynamic: true, description: "Alias for code." },
|
|
2638
|
+
content: { type: "string", dynamic: true, description: "Alias for code." },
|
|
2639
|
+
language: { type: "string", description: "Language label." },
|
|
2640
|
+
title: { type: "string", description: "Code block title." },
|
|
2641
|
+
icon: { type: "string", description: "Icon name shown before the title." },
|
|
2642
|
+
lineNumbers: { type: "boolean", default: true, description: "Show line numbers." }
|
|
2643
|
+
},
|
|
2644
|
+
children: noChildren,
|
|
2645
|
+
examples: [example("code-block", {
|
|
2646
|
+
namespace: "doc_code_block_typical",
|
|
2647
|
+
layout: {
|
|
2648
|
+
"code-block:config": {
|
|
2649
|
+
title: "Config",
|
|
2650
|
+
icon: "code",
|
|
2651
|
+
language: "js",
|
|
2652
|
+
code: "export const enabled = true;"
|
|
2653
|
+
}
|
|
2654
|
+
}
|
|
2655
|
+
})]
|
|
2656
|
+
});
|
|
2657
|
+
|
|
2658
|
+
// src/components/entries/collapsible.spec.ts
|
|
2659
|
+
var collapsibleSpec = component({
|
|
2660
|
+
type: "collapsible",
|
|
2661
|
+
category: "Disclosure",
|
|
2662
|
+
title: "Collapsible",
|
|
2663
|
+
summary: "Single expandable region.",
|
|
2664
|
+
description: "Use collapsible to show or hide one related content region.",
|
|
2665
|
+
props: {
|
|
2666
|
+
open: { type: "boolean", default: false, dynamic: true, description: "Expanded state." },
|
|
2667
|
+
trigger: { type: "string", dynamic: true, description: "Trigger button text." },
|
|
2668
|
+
icon: { type: "string", description: "Icon name shown before trigger text." },
|
|
2669
|
+
content: { type: "string", dynamic: true, description: "Static body content." },
|
|
2670
|
+
onchange: { type: "write-expression", description: "Write expression invoked when open state changes." }
|
|
2671
|
+
},
|
|
2672
|
+
children: childContent,
|
|
2673
|
+
examples: [example("collapsible", {
|
|
2674
|
+
namespace: "doc_collapsible_typical",
|
|
2675
|
+
layout: {
|
|
2676
|
+
"collapsible:more": {
|
|
2677
|
+
open: true,
|
|
2678
|
+
trigger: "Details",
|
|
2679
|
+
icon: "caret-circle-down",
|
|
2680
|
+
content: "This secondary content can be collapsed."
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
})]
|
|
2684
|
+
});
|
|
2685
|
+
|
|
2686
|
+
// src/components/entries/column.spec.ts
|
|
2687
|
+
var columnSpec = component({
|
|
2688
|
+
type: "column",
|
|
2689
|
+
category: "Layout",
|
|
2690
|
+
title: "Column",
|
|
2691
|
+
summary: "Vertical layout container.",
|
|
2692
|
+
description: "Use column to stack child components vertically in field order.",
|
|
2693
|
+
props: {},
|
|
2694
|
+
children: childContent,
|
|
2695
|
+
examples: [example("column", {
|
|
2696
|
+
namespace: "doc_column_typical",
|
|
2697
|
+
layout: {
|
|
2698
|
+
"column:form": {
|
|
2699
|
+
"input:name": {
|
|
2700
|
+
placeholder: "Name"
|
|
2701
|
+
},
|
|
2702
|
+
"input:email": {
|
|
2703
|
+
placeholder: "Email"
|
|
2704
|
+
},
|
|
2705
|
+
"button:save": {
|
|
2706
|
+
label: "Save"
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
})]
|
|
2711
|
+
});
|
|
2712
|
+
|
|
2713
|
+
// src/components/entries/divider.spec.ts
|
|
2714
|
+
var dividerSpec = component({
|
|
2715
|
+
type: "divider",
|
|
2716
|
+
category: "Content",
|
|
2717
|
+
title: "Divider",
|
|
2718
|
+
summary: "Visual separator.",
|
|
2719
|
+
description: "Use divider to separate related regions, optionally with a label.",
|
|
2720
|
+
props: {
|
|
2721
|
+
label: { type: "string", dynamic: true, description: "Text shown in the divider." },
|
|
2722
|
+
icon: { type: "string", description: "Icon name shown before the label." }
|
|
2723
|
+
},
|
|
2724
|
+
children: noChildren,
|
|
2725
|
+
examples: [example("divider", {
|
|
2726
|
+
namespace: "doc_divider_typical",
|
|
2727
|
+
layout: {
|
|
2728
|
+
"column:content": {
|
|
2729
|
+
"text:top": {
|
|
2730
|
+
text: "Above"
|
|
2731
|
+
},
|
|
2732
|
+
"divider:line": {
|
|
2733
|
+
label: "Divider",
|
|
2734
|
+
icon: "flag"
|
|
2735
|
+
},
|
|
2736
|
+
"text:bottom": {
|
|
2737
|
+
text: "Below"
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2741
|
+
})]
|
|
2742
|
+
});
|
|
2743
|
+
|
|
2744
|
+
// src/components/entries/formula.spec.ts
|
|
2745
|
+
var formulaSpec = component({
|
|
2746
|
+
type: "formula",
|
|
2747
|
+
category: "Display",
|
|
2748
|
+
title: "Formula",
|
|
2749
|
+
summary: "Reactive KaTeX formula display.",
|
|
2750
|
+
description: "Use formula to render SlexKit state and computed values through KaTeX.",
|
|
2751
|
+
props: {
|
|
2752
|
+
tex: { type: "string", dynamic: true, description: "KaTeX source to render." },
|
|
2753
|
+
formula: { type: "string", dynamic: true, description: "Alias for tex." },
|
|
2754
|
+
value: { type: "string", dynamic: true, description: "Alias for tex." },
|
|
2755
|
+
displayMode: { type: "boolean", default: true, description: "Render as display math when true; inline math when false." },
|
|
2756
|
+
display: { type: "boolean", default: true, description: "Alias for displayMode." },
|
|
2757
|
+
block: { type: "boolean", default: true, description: "Alias for displayMode." }
|
|
2758
|
+
},
|
|
2759
|
+
children: noChildren,
|
|
2760
|
+
examples: [example("formula", {
|
|
2761
|
+
namespace: "doc_formula_typical",
|
|
2762
|
+
g: {
|
|
2763
|
+
r: 1e4,
|
|
2764
|
+
c: 100,
|
|
2765
|
+
fc: 159.15
|
|
2766
|
+
},
|
|
2767
|
+
layout: {
|
|
2768
|
+
"formula:cutoff": {
|
|
2769
|
+
$tex: "'f_c = \\\\frac{1}{2\\\\pi RC} = ' + g.fc + '\\\\text{ Hz}'"
|
|
2770
|
+
}
|
|
2771
|
+
}
|
|
2772
|
+
})]
|
|
2773
|
+
});
|
|
2774
|
+
|
|
2775
|
+
// src/components/entries/grid.spec.ts
|
|
2776
|
+
var gridSpec = component({
|
|
2777
|
+
type: "grid",
|
|
2778
|
+
category: "Layout",
|
|
2779
|
+
title: "Grid",
|
|
2780
|
+
summary: "Responsive grid container.",
|
|
2781
|
+
description: "Use grid to arrange child components in responsive columns.",
|
|
2782
|
+
props: {
|
|
2783
|
+
columns: { type: "number", default: 1, dynamic: true, description: "Base column count." },
|
|
2784
|
+
smColumns: { type: "number", dynamic: true, description: "Column count at the small breakpoint." },
|
|
2785
|
+
mdColumns: { type: "number", dynamic: true, description: "Column count at the medium breakpoint." },
|
|
2786
|
+
lgColumns: { type: "number", dynamic: true, description: "Column count at the large breakpoint." },
|
|
2787
|
+
xlColumns: { type: "number", dynamic: true, description: "Column count at the extra-large breakpoint." },
|
|
2788
|
+
gap: { type: "string", dynamic: true, description: "Spacing between grid items." }
|
|
2789
|
+
},
|
|
2790
|
+
children: childContent,
|
|
2791
|
+
examples: [example("grid", {
|
|
2792
|
+
namespace: "doc_grid_typical",
|
|
2793
|
+
layout: {
|
|
2794
|
+
"grid:stats": {
|
|
2795
|
+
columns: 1,
|
|
2796
|
+
mdColumns: 3,
|
|
2797
|
+
"stat:a": {
|
|
2798
|
+
label: "Requests",
|
|
2799
|
+
value: "1.2k"
|
|
2800
|
+
},
|
|
2801
|
+
"stat:b": {
|
|
2802
|
+
label: "Success",
|
|
2803
|
+
value: "98%"
|
|
2804
|
+
},
|
|
2805
|
+
"stat:c": {
|
|
2806
|
+
label: "Errors",
|
|
2807
|
+
value: "3"
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
}
|
|
2811
|
+
})]
|
|
2812
|
+
});
|
|
2813
|
+
|
|
2814
|
+
// src/components/entries/icon.spec.ts
|
|
2815
|
+
var iconSpec = component({
|
|
2816
|
+
type: "icon",
|
|
2817
|
+
category: "Component",
|
|
2818
|
+
title: "Icon",
|
|
2819
|
+
summary: "Shared icon field capability.",
|
|
2820
|
+
description: "Icon is a shared field capability, not an independent icon component type.",
|
|
2821
|
+
props: {
|
|
2822
|
+
icon: { type: "string", description: "Icon name resolved through the global icon manager." },
|
|
2823
|
+
iconOnly: { type: "boolean", description: "Render only the icon while retaining an accessible label where supported." },
|
|
2824
|
+
"items[].icon": { type: "string", description: "Accordion item trigger icon." },
|
|
2825
|
+
"options[].icon": { type: "string", description: "Select or radio option icon." },
|
|
2826
|
+
"columns[].icon": { type: "string", description: "Table column header icon." },
|
|
2827
|
+
"tabs[].icon": { type: "string", description: "Tab trigger icon." },
|
|
2828
|
+
"tabs[].iconOnly": { type: "boolean", description: "Tab trigger icon-only mode." }
|
|
2829
|
+
},
|
|
2830
|
+
children: noChildren,
|
|
2831
|
+
examples: [example("button", { label: "Settings", icon: "gear-six", iconOnly: true, variant: "ghost" }, "Icon field usage")]
|
|
2832
|
+
});
|
|
2833
|
+
|
|
2834
|
+
// src/components/entries/input.spec.ts
|
|
2835
|
+
var inputSpec = component({
|
|
2836
|
+
type: "input",
|
|
2837
|
+
category: "Input",
|
|
2838
|
+
title: "Input",
|
|
2839
|
+
summary: "Text or engineering-value input.",
|
|
2840
|
+
description: "Use input for editable text and engineering numeric values.",
|
|
2841
|
+
props: {
|
|
2842
|
+
value: { type: "string", dynamic: true, description: "Current input value." },
|
|
2843
|
+
label: { type: "string", dynamic: true, description: "Input label." },
|
|
2844
|
+
unit: { type: "string", dynamic: true, description: "Trailing unit text." },
|
|
2845
|
+
description: { type: "string", dynamic: true, description: "Assistive description below the input." },
|
|
2846
|
+
help: { type: "string", dynamic: true, description: "Alias for description." },
|
|
2847
|
+
hint: { type: "string", dynamic: true, description: "Alias for description." },
|
|
2848
|
+
error: { type: "string", dynamic: true, description: "Error text shown below the input and linked with aria-describedby." },
|
|
2849
|
+
errorMessage: { type: "string", dynamic: true, description: "Alias for error." },
|
|
2850
|
+
invalid: { type: "boolean", default: false, dynamic: true, description: "Mark the input as invalid with aria-invalid and error styling." },
|
|
2851
|
+
placeholder: { type: "string", description: "Placeholder text for empty values." },
|
|
2852
|
+
type: { type: "string", default: "text", description: "Input value kind; use engineering for parsed engineering values." },
|
|
2853
|
+
disabled: { type: "boolean", default: false, dynamic: true, description: "Disable editing." },
|
|
2854
|
+
readonly: { type: "boolean", default: false, dynamic: true, description: "Make the input read-only." },
|
|
2855
|
+
readOnly: { type: "boolean", default: false, dynamic: true, description: "Alias for readonly." },
|
|
2856
|
+
required: { type: "boolean", default: false, dynamic: true, description: "Mark the input as required." },
|
|
2857
|
+
id: { type: "string", description: "Native input id; defaults to a stable id derived from the component name." },
|
|
2858
|
+
name: { type: "string", description: "Native input name attribute." },
|
|
2859
|
+
min: { type: "string | number", dynamic: true, description: "Minimum value used by numeric input controls." },
|
|
2860
|
+
max: { type: "string | number", dynamic: true, description: "Maximum value used by numeric input controls." },
|
|
2861
|
+
step: { type: "string | number", dynamic: true, description: "Step size used by numeric input controls." },
|
|
2862
|
+
onchange: { type: "write-expression", description: "Write expression invoked when the value changes." }
|
|
2863
|
+
},
|
|
2864
|
+
children: noChildren,
|
|
2865
|
+
examples: [example("input", {
|
|
2866
|
+
namespace: "doc_input_typical",
|
|
2867
|
+
layout: {
|
|
2868
|
+
"input:name": {
|
|
2869
|
+
label: "Project",
|
|
2870
|
+
value: "SlexKit",
|
|
2871
|
+
placeholder: "Enter name",
|
|
2872
|
+
description: "Visible labels keep form fields scannable."
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2875
|
+
})]
|
|
2876
|
+
});
|
|
2877
|
+
|
|
2878
|
+
// src/components/entries/link.spec.ts
|
|
2879
|
+
var linkSpec = component({
|
|
2880
|
+
type: "link",
|
|
2881
|
+
category: "Content",
|
|
2882
|
+
title: "Link",
|
|
2883
|
+
summary: "Inline navigation link.",
|
|
2884
|
+
description: "Use link to navigate to another page or resource.",
|
|
2885
|
+
props: {
|
|
2886
|
+
href: { type: "string", description: "Target URL." },
|
|
2887
|
+
text: { type: "string", dynamic: true, description: "Visible link text." },
|
|
2888
|
+
label: { type: "string", dynamic: true, description: "Alias for text." },
|
|
2889
|
+
content: { type: "string", dynamic: true, description: "Alias for text." },
|
|
2890
|
+
icon: { type: "string", description: "Icon name shown before link text." },
|
|
2891
|
+
target: { type: "string", description: "Native link target attribute." },
|
|
2892
|
+
variant: { type: "string", values: ["default", "muted"], default: "default", description: "Link visual variant." }
|
|
2893
|
+
},
|
|
2894
|
+
children: noChildren,
|
|
2895
|
+
examples: [example("link", {
|
|
2896
|
+
namespace: "doc_link_typical",
|
|
2897
|
+
layout: {
|
|
2898
|
+
"column:links": {
|
|
2899
|
+
"link:docs": {
|
|
2900
|
+
href: "/components",
|
|
2901
|
+
icon: "arrow-square-out",
|
|
2902
|
+
text: "View components"
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
})]
|
|
2907
|
+
});
|
|
2908
|
+
|
|
2909
|
+
// src/components/entries/playground.spec.ts
|
|
2910
|
+
var playgroundSpec = component({
|
|
2911
|
+
type: "playground",
|
|
2912
|
+
category: "Tooling",
|
|
2913
|
+
title: "Playground",
|
|
2914
|
+
summary: "Interactive source preview.",
|
|
2915
|
+
description: "Use playground in documentation surfaces to render editable SlexKit or Markdown examples.",
|
|
2916
|
+
props: {
|
|
2917
|
+
source: { type: "object | string", description: "SlexKit or Markdown source to preview." },
|
|
2918
|
+
sourceType: { type: "string", values: ["slex", "markdown", "auto-markdown"], default: "slex", description: "Source parser mode." },
|
|
2919
|
+
title: { type: "string", description: "Playground title." },
|
|
2920
|
+
previewAlign: { type: "string", values: ["center", "start"], default: "center", description: "Vertical preview alignment in render mode." },
|
|
2921
|
+
alignPreview: { type: "string", values: ["center", "start"], description: "Alias for previewAlign." },
|
|
2922
|
+
previewPlacement: { type: "string", values: ["center", "start"], description: "Alias for previewAlign." },
|
|
2923
|
+
previewMinHeight: { type: "string", description: "Minimum preview area height." },
|
|
2924
|
+
previewMaxWidth: { type: "string", description: "Maximum preview content width." },
|
|
2925
|
+
themeToggle: { type: "boolean", default: false, description: "Show the theme toggle action." },
|
|
2926
|
+
showThemeToggle: { type: "boolean", default: false, description: "Alias for themeToggle." },
|
|
2927
|
+
enableThemeToggle: { type: "boolean", default: false, description: "Alias for themeToggle." },
|
|
2928
|
+
themeLabel: { type: "string", description: "Accessible label for the theme toggle action." },
|
|
2929
|
+
themeToggleLabel: { type: "string", description: "Alias for themeLabel." },
|
|
2930
|
+
sourceTypeLabel: { type: "string", description: "Accessible label for the source type selector." },
|
|
2931
|
+
copyLabel: { type: "string", description: "Accessible label for the copy source action." },
|
|
2932
|
+
openWebLabel: { type: "string", description: "Accessible label for opening the source in the standalone playground." },
|
|
2933
|
+
webUrl: { type: "string", description: "Standalone playground URL used by the open action." },
|
|
2934
|
+
playgroundUrl: { type: "string", description: "Alias for webUrl." }
|
|
2935
|
+
},
|
|
2936
|
+
children: noChildren,
|
|
2937
|
+
examples: [example("playground", {
|
|
2938
|
+
namespace: "doc_playground_typical",
|
|
2939
|
+
layout: {
|
|
2940
|
+
"playground:demo": {
|
|
2941
|
+
title: "Stat Playground",
|
|
2942
|
+
previewMinHeight: "180px",
|
|
2943
|
+
source: {
|
|
2944
|
+
namespace: "inner_stat_demo",
|
|
2945
|
+
layout: {
|
|
2946
|
+
"stat:value": {
|
|
2947
|
+
label: "Requests",
|
|
2948
|
+
value: "1.2k",
|
|
2949
|
+
unit: "/min"
|
|
2950
|
+
}
|
|
2951
|
+
}
|
|
2952
|
+
}
|
|
2953
|
+
}
|
|
2954
|
+
}
|
|
2955
|
+
})]
|
|
2956
|
+
});
|
|
2957
|
+
|
|
2958
|
+
// src/components/entries/progress.spec.ts
|
|
2959
|
+
var progressSpec = component({
|
|
2960
|
+
type: "progress",
|
|
2961
|
+
category: "Feedback",
|
|
2962
|
+
title: "Progress",
|
|
2963
|
+
summary: "Progress bar.",
|
|
2964
|
+
description: "Use progress to show completion as a percentage.",
|
|
2965
|
+
props: {
|
|
2966
|
+
value: { type: "number", default: 0, dynamic: true, description: "Progress percentage from 0 to 100." },
|
|
2967
|
+
label: { type: "string", dynamic: true, description: "Progress label." },
|
|
2968
|
+
icon: { type: "string", description: "Icon name shown before the label." },
|
|
2969
|
+
indeterminate: { type: "boolean", default: false, dynamic: true, description: "Render an indeterminate progress state without aria-valuenow." }
|
|
2970
|
+
},
|
|
2971
|
+
children: noChildren,
|
|
2972
|
+
examples: [example("progress", {
|
|
2973
|
+
namespace: "doc_progress_typical",
|
|
2974
|
+
layout: {
|
|
2975
|
+
"progress:build": {
|
|
2976
|
+
label: "Build progress",
|
|
2977
|
+
icon: "gear-six",
|
|
2978
|
+
value: 64
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2981
|
+
})]
|
|
2982
|
+
});
|
|
2983
|
+
|
|
2984
|
+
// src/components/entries/radio-group.spec.ts
|
|
2985
|
+
var radioGroupSpec = component({
|
|
2986
|
+
type: "radio-group",
|
|
2987
|
+
category: "Input",
|
|
2988
|
+
title: "Radio Group",
|
|
2989
|
+
summary: "Single-choice option group.",
|
|
2990
|
+
description: "Use radio-group for one-of-many choices.",
|
|
2991
|
+
props: {
|
|
2992
|
+
value: { type: "string", dynamic: true, description: "Current selected value." },
|
|
2993
|
+
label: { type: "string", dynamic: true, description: "Group label." },
|
|
2994
|
+
icon: { type: "string", description: "Icon name shown before the group label." },
|
|
2995
|
+
options: { type: "array", description: "Options with label, value, and optional icon." },
|
|
2996
|
+
"options[].icon": { type: "string", description: "Icon name shown before a single option label." },
|
|
2997
|
+
disabled: { type: "boolean", default: false, dynamic: true, description: "Disable every radio option in the group." },
|
|
2998
|
+
orientation: { type: "string", values: ["vertical", "horizontal"], default: "vertical", description: "Radio option layout direction." },
|
|
2999
|
+
haptic: { type: "boolean", default: true, description: "Enable vibration feedback on supported devices." },
|
|
3000
|
+
haptics: { type: "boolean", default: true, description: "Alias for haptic." },
|
|
3001
|
+
name: { type: "string", description: "Native radio group name shared by options." },
|
|
3002
|
+
onchange: { type: "write-expression", description: "Write expression invoked when selection changes." }
|
|
3003
|
+
},
|
|
3004
|
+
children: noChildren,
|
|
3005
|
+
examples: [example("radio-group", {
|
|
3006
|
+
namespace: "doc_radio_group_typical",
|
|
3007
|
+
layout: {
|
|
3008
|
+
"radio-group:mode": {
|
|
3009
|
+
label: "Mode",
|
|
3010
|
+
icon: "sliders-horizontal",
|
|
3011
|
+
value: "auto",
|
|
3012
|
+
options: [
|
|
3013
|
+
{
|
|
3014
|
+
label: "Auto",
|
|
3015
|
+
value: "auto",
|
|
3016
|
+
icon: "sparkle"
|
|
3017
|
+
},
|
|
3018
|
+
{
|
|
3019
|
+
label: "Manual",
|
|
3020
|
+
value: "manual",
|
|
3021
|
+
icon: "wrench"
|
|
3022
|
+
}
|
|
3023
|
+
]
|
|
3024
|
+
}
|
|
3025
|
+
}
|
|
3026
|
+
})]
|
|
3027
|
+
});
|
|
3028
|
+
|
|
3029
|
+
// src/components/entries/row.spec.ts
|
|
3030
|
+
var rowSpec = component({
|
|
3031
|
+
type: "row",
|
|
3032
|
+
category: "Layout",
|
|
3033
|
+
title: "Row",
|
|
3034
|
+
summary: "Horizontal layout container.",
|
|
3035
|
+
description: "Use row to place child components horizontally in field order.",
|
|
3036
|
+
props: {
|
|
3037
|
+
justify: { type: "string", values: ["start", "center", "end", "space-between", "space-around"], default: "start", description: "Main-axis distribution." },
|
|
3038
|
+
align: { type: "string", values: ["start", "center", "end", "baseline", "stretch"], default: "center", description: "Cross-axis alignment." },
|
|
3039
|
+
gap: { type: "string", dynamic: true, description: "Spacing between children." }
|
|
3040
|
+
},
|
|
3041
|
+
children: childContent,
|
|
3042
|
+
examples: [example("row", {
|
|
3043
|
+
namespace: "doc_row_typical",
|
|
3044
|
+
layout: {
|
|
3045
|
+
"row:toolbar": {
|
|
3046
|
+
justify: "space-between",
|
|
3047
|
+
"text:title": {
|
|
3048
|
+
text: "Runtime status"
|
|
3049
|
+
},
|
|
3050
|
+
"button:refresh": {
|
|
3051
|
+
label: "Refresh"
|
|
3052
|
+
}
|
|
3053
|
+
}
|
|
3054
|
+
}
|
|
3055
|
+
})]
|
|
3056
|
+
});
|
|
3057
|
+
|
|
3058
|
+
// src/components/entries/section.spec.ts
|
|
3059
|
+
var sectionSpec = component({
|
|
3060
|
+
type: "section",
|
|
3061
|
+
category: "Content",
|
|
3062
|
+
title: "Section",
|
|
3063
|
+
summary: "Page section with optional heading chrome.",
|
|
3064
|
+
description: "Use section to group page content with title, subtitle, and optional action link.",
|
|
3065
|
+
props: {
|
|
3066
|
+
title: { type: "string", dynamic: true, description: "Section title." },
|
|
3067
|
+
icon: { type: "string", description: "Icon name shown before the title." },
|
|
3068
|
+
eyebrow: { type: "string", dynamic: true, description: "Small label above the title." },
|
|
3069
|
+
subtitle: { type: "string", dynamic: true, description: "Subtitle text below the title." },
|
|
3070
|
+
actionLabel: { type: "string", dynamic: true, description: "Optional action link label." },
|
|
3071
|
+
actionHref: { type: "string", description: "Optional action link target." }
|
|
3072
|
+
},
|
|
3073
|
+
children: childContent,
|
|
3074
|
+
examples: [example("section", {
|
|
3075
|
+
namespace: "doc_section_typical",
|
|
3076
|
+
layout: {
|
|
3077
|
+
"section:overview": {
|
|
3078
|
+
eyebrow: "Dashboard",
|
|
3079
|
+
title: "Runtime overview",
|
|
3080
|
+
icon: "chart-bar",
|
|
3081
|
+
subtitle: "This section groups the most important state.",
|
|
3082
|
+
"stat:latency": {
|
|
3083
|
+
label: "Latency",
|
|
3084
|
+
value: "42",
|
|
3085
|
+
unit: "ms"
|
|
3086
|
+
}
|
|
3087
|
+
}
|
|
3088
|
+
}
|
|
3089
|
+
})]
|
|
3090
|
+
});
|
|
3091
|
+
|
|
3092
|
+
// src/components/entries/select.spec.ts
|
|
3093
|
+
var selectSpec = component({
|
|
3094
|
+
type: "select",
|
|
3095
|
+
category: "Input",
|
|
3096
|
+
title: "Select",
|
|
3097
|
+
summary: "Dropdown selection input.",
|
|
3098
|
+
description: "Use select for compact single-choice selection from an option list.",
|
|
3099
|
+
props: {
|
|
3100
|
+
label: { type: "string", dynamic: true, description: "Select label." },
|
|
3101
|
+
icon: { type: "string", description: "Icon name shown before the top label." },
|
|
3102
|
+
value: { type: "string", dynamic: true, description: "Current selected value." },
|
|
3103
|
+
options: { type: "array", description: "Options with label, value, and optional icon." },
|
|
3104
|
+
"options[].icon": { type: "string", description: "Icon name shown before an option label." },
|
|
3105
|
+
placeholder: { type: "string", description: "Placeholder shown when no value is selected." },
|
|
3106
|
+
disabled: { type: "boolean", default: false, dynamic: true, description: "Disable the select trigger and native select." },
|
|
3107
|
+
required: { type: "boolean", default: false, dynamic: true, description: "Require a non-placeholder value in the native select." },
|
|
3108
|
+
variant: { type: "string", values: ["default", "toolbar"], default: "default", description: "Select surface variant." },
|
|
3109
|
+
onchange: { type: "write-expression", description: "Write expression invoked when selection changes." }
|
|
3110
|
+
},
|
|
3111
|
+
children: noChildren,
|
|
3112
|
+
examples: [example("select", {
|
|
3113
|
+
namespace: "doc_select_typical",
|
|
3114
|
+
layout: {
|
|
3115
|
+
"select:env": {
|
|
3116
|
+
label: "Environment",
|
|
3117
|
+
icon: "server",
|
|
3118
|
+
value: "prod",
|
|
3119
|
+
options: [
|
|
3120
|
+
{
|
|
3121
|
+
label: "Development",
|
|
3122
|
+
value: "dev",
|
|
3123
|
+
icon: "code"
|
|
3124
|
+
},
|
|
3125
|
+
{
|
|
3126
|
+
label: "Production",
|
|
3127
|
+
value: "prod",
|
|
3128
|
+
icon: "rocket-launch"
|
|
3129
|
+
}
|
|
3130
|
+
]
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
})]
|
|
3134
|
+
});
|
|
3135
|
+
|
|
3136
|
+
// src/components/entries/slider.spec.ts
|
|
3137
|
+
var sliderSpec = component({
|
|
3138
|
+
type: "slider",
|
|
3139
|
+
category: "Input",
|
|
3140
|
+
title: "Slider",
|
|
3141
|
+
summary: "Numeric range input.",
|
|
3142
|
+
description: "Use slider for bounded numeric adjustments such as volume, brightness, or thresholds.",
|
|
3143
|
+
props: {
|
|
3144
|
+
label: { type: "string", dynamic: true, description: "Slider label." },
|
|
3145
|
+
icon: { type: "string", description: "Icon name shown before the label." },
|
|
3146
|
+
value: { type: "number", default: 0, dynamic: true, description: "Current numeric value." },
|
|
3147
|
+
min: { type: "number", default: 0, dynamic: true, description: "Minimum value." },
|
|
3148
|
+
max: { type: "number", default: 100, dynamic: true, description: "Maximum value." },
|
|
3149
|
+
step: { type: "number", default: 1, dynamic: true, description: "Step interval." },
|
|
3150
|
+
unit: { type: "string", dynamic: true, description: "Unit shown after the value." },
|
|
3151
|
+
disabled: { type: "boolean", default: false, dynamic: true, description: "Disable the range input." },
|
|
3152
|
+
orientation: { type: "string", values: ["horizontal", "vertical"], default: "horizontal", description: "Slider orientation metadata used for styling." },
|
|
3153
|
+
haptic: { type: "boolean", default: true, description: "Enable vibration feedback on supported devices." },
|
|
3154
|
+
haptics: { type: "boolean", default: true, description: "Alias for haptic." },
|
|
3155
|
+
onchange: { type: "write-expression", description: "Write expression invoked when the value changes." }
|
|
3156
|
+
},
|
|
3157
|
+
children: noChildren,
|
|
3158
|
+
examples: [example("slider", {
|
|
3159
|
+
namespace: "doc_slider_typical",
|
|
3160
|
+
layout: {
|
|
3161
|
+
"slider:volume": {
|
|
3162
|
+
label: "Volume",
|
|
3163
|
+
icon: "speaker-high",
|
|
3164
|
+
value: 42,
|
|
3165
|
+
min: 0,
|
|
3166
|
+
max: 100,
|
|
3167
|
+
step: 1,
|
|
3168
|
+
unit: "%"
|
|
3169
|
+
}
|
|
3170
|
+
}
|
|
3171
|
+
})]
|
|
3172
|
+
});
|
|
3173
|
+
|
|
3174
|
+
// src/components/entries/stat.spec.ts
|
|
3175
|
+
var statSpec = component({
|
|
3176
|
+
type: "stat",
|
|
3177
|
+
category: "Display",
|
|
3178
|
+
title: "Stat",
|
|
3179
|
+
summary: "Metric display.",
|
|
3180
|
+
description: "Use stat to present a labeled metric value with optional unit and semantic tone.",
|
|
3181
|
+
props: {
|
|
3182
|
+
label: { type: "string", dynamic: true, description: "Metric label." },
|
|
3183
|
+
icon: { type: "string", description: "Icon name shown before the label." },
|
|
3184
|
+
value: { type: "string | number", dynamic: true, description: "Metric value." },
|
|
3185
|
+
unit: { type: "string", dynamic: true, description: "Unit shown after the value." },
|
|
3186
|
+
tone: { type: "string", values: semanticTones, description: "Optional semantic tone." },
|
|
3187
|
+
animateInitial: { type: "boolean", default: false, description: "Animate the initial rendered value." }
|
|
3188
|
+
},
|
|
3189
|
+
children: noChildren,
|
|
3190
|
+
examples: [example("stat", {
|
|
3191
|
+
namespace: "doc_stat_typical",
|
|
3192
|
+
layout: {
|
|
3193
|
+
"grid:stats": {
|
|
3194
|
+
columns: 2,
|
|
3195
|
+
"stat:requests": {
|
|
3196
|
+
label: "Requests",
|
|
3197
|
+
icon: "activity",
|
|
3198
|
+
value: "1.2k",
|
|
3199
|
+
unit: "/min"
|
|
3200
|
+
},
|
|
3201
|
+
"stat:success": {
|
|
3202
|
+
label: "Success",
|
|
3203
|
+
icon: "check-circle",
|
|
3204
|
+
value: "98.4",
|
|
3205
|
+
unit: "%",
|
|
3206
|
+
tone: "success"
|
|
3207
|
+
}
|
|
3208
|
+
}
|
|
3209
|
+
}
|
|
3210
|
+
})]
|
|
3211
|
+
});
|
|
3212
|
+
|
|
3213
|
+
// src/components/entries/submit.spec.ts
|
|
3214
|
+
var submitSpec = component({
|
|
3215
|
+
type: "submit",
|
|
3216
|
+
category: "Action",
|
|
3217
|
+
title: "Submit",
|
|
3218
|
+
summary: "ToolHost submit and ignore controls.",
|
|
3219
|
+
description: "Use submit inside tool templates to return selected state fields to the host.",
|
|
3220
|
+
props: {
|
|
3221
|
+
submitLabel: { type: "string", default: "Submit", description: "Submit button text." },
|
|
3222
|
+
ignoreLabel: { type: "string", default: "Ignore", description: "Ignore button text." },
|
|
3223
|
+
returnKeys: { type: "string[]", description: "State field paths returned to ToolHost." },
|
|
3224
|
+
disabled: { type: "boolean", default: false, dynamic: true, description: "Disable submit action." }
|
|
3225
|
+
},
|
|
3226
|
+
children: noChildren,
|
|
3227
|
+
examples: [example("submit", {
|
|
3228
|
+
namespace: "doc_submit_typical",
|
|
3229
|
+
layout: {
|
|
3230
|
+
"column:tool": {
|
|
3231
|
+
"input:title": {
|
|
3232
|
+
value: "Release note",
|
|
3233
|
+
placeholder: "Title"
|
|
3234
|
+
},
|
|
3235
|
+
"submit:done": {
|
|
3236
|
+
submitLabel: "Submit",
|
|
3237
|
+
ignoreLabel: "Ignore",
|
|
3238
|
+
returnKeys: [
|
|
3239
|
+
"title"
|
|
3240
|
+
]
|
|
3241
|
+
}
|
|
3242
|
+
}
|
|
3243
|
+
}
|
|
3244
|
+
})]
|
|
3245
|
+
});
|
|
3246
|
+
|
|
3247
|
+
// src/components/entries/switch.spec.ts
|
|
3248
|
+
var switchSpec = component({
|
|
3249
|
+
type: "switch",
|
|
3250
|
+
category: "Input",
|
|
3251
|
+
title: "Switch",
|
|
3252
|
+
summary: "Boolean switch input.",
|
|
3253
|
+
description: "Use switch for an on/off setting.",
|
|
3254
|
+
props: {
|
|
3255
|
+
enabled: { type: "boolean", default: false, dynamic: true, description: "Enabled state." },
|
|
3256
|
+
label: { type: "string", dynamic: true, description: "Switch label." },
|
|
3257
|
+
icon: { type: "string", description: "Icon name shown before the visible label." },
|
|
3258
|
+
disabled: { type: "boolean", default: false, dynamic: true, description: "Disable the switch." },
|
|
3259
|
+
haptic: { type: "boolean", default: true, description: "Enable vibration feedback on supported devices." },
|
|
3260
|
+
haptics: { type: "boolean", default: true, description: "Alias for haptic." },
|
|
3261
|
+
onchange: { type: "write-expression", description: "Write expression invoked when enabled state changes." }
|
|
3262
|
+
},
|
|
3263
|
+
children: noChildren,
|
|
3264
|
+
examples: [example("switch", {
|
|
3265
|
+
namespace: "doc_switch_typical",
|
|
3266
|
+
layout: {
|
|
3267
|
+
"switch:feature": {
|
|
3268
|
+
enabled: true,
|
|
3269
|
+
label: "Enable sync",
|
|
3270
|
+
icon: "arrows-clockwise"
|
|
3271
|
+
}
|
|
3272
|
+
}
|
|
3273
|
+
})]
|
|
3274
|
+
});
|
|
3275
|
+
|
|
3276
|
+
// src/components/entries/table.spec.ts
|
|
3277
|
+
var tableSpec = component({
|
|
3278
|
+
type: "table",
|
|
3279
|
+
category: "Data",
|
|
3280
|
+
title: "Table",
|
|
3281
|
+
summary: "Simple data table.",
|
|
3282
|
+
description: "Use table to render rows of keyed data against a column definition.",
|
|
3283
|
+
props: {
|
|
3284
|
+
columns: { type: "array", description: "Column definitions with key, label, and optional icon." },
|
|
3285
|
+
"columns[].icon": { type: "string", description: "Icon name shown before a column label." },
|
|
3286
|
+
rows: { type: "array", description: "Row data objects keyed by column key." },
|
|
3287
|
+
items: { type: "array", description: "Alias for rows." }
|
|
3288
|
+
},
|
|
3289
|
+
children: noChildren,
|
|
3290
|
+
examples: [example("table", {
|
|
3291
|
+
namespace: "doc_table_typical",
|
|
3292
|
+
layout: {
|
|
3293
|
+
"table:routes": {
|
|
3294
|
+
columns: [
|
|
3295
|
+
{
|
|
3296
|
+
key: "name",
|
|
3297
|
+
label: "Name",
|
|
3298
|
+
icon: "text-t"
|
|
3299
|
+
},
|
|
3300
|
+
{
|
|
3301
|
+
key: "status",
|
|
3302
|
+
label: "Status",
|
|
3303
|
+
icon: "check-circle"
|
|
3304
|
+
}
|
|
3305
|
+
],
|
|
3306
|
+
rows: [
|
|
3307
|
+
{
|
|
3308
|
+
name: "Parse",
|
|
3309
|
+
status: "ready"
|
|
3310
|
+
},
|
|
3311
|
+
{
|
|
3312
|
+
name: "Publish",
|
|
3313
|
+
status: "pending"
|
|
3314
|
+
}
|
|
3315
|
+
]
|
|
3316
|
+
}
|
|
3317
|
+
}
|
|
3318
|
+
})]
|
|
3319
|
+
});
|
|
3320
|
+
|
|
3321
|
+
// src/components/entries/tabs.spec.ts
|
|
3322
|
+
var tabsSpec = component({
|
|
3323
|
+
type: "tabs",
|
|
3324
|
+
category: "Navigation",
|
|
3325
|
+
title: "Tabs",
|
|
3326
|
+
summary: "Tabbed view switcher.",
|
|
3327
|
+
description: "Use tabs to switch between named content panels.",
|
|
3328
|
+
props: {
|
|
3329
|
+
value: { type: "string", dynamic: true, description: "Current active tab value." },
|
|
3330
|
+
tabs: { type: "array", description: "Tab definitions with value, label, content, icon, and iconOnly." },
|
|
3331
|
+
"tabs[].icon": { type: "string", description: "Icon name shown before a tab trigger label." },
|
|
3332
|
+
"tabs[].iconOnly": { type: "boolean", description: "Show only the tab icon while retaining label as accessible text." },
|
|
3333
|
+
orientation: { type: "string", values: ["horizontal", "vertical"], default: "horizontal", description: "Tab list orientation." },
|
|
3334
|
+
onchange: { type: "write-expression", description: "Write expression invoked when the active tab changes." }
|
|
3335
|
+
},
|
|
3336
|
+
children: noChildren,
|
|
3337
|
+
examples: [example("tabs", {
|
|
3338
|
+
namespace: "doc_tabs_typical",
|
|
3339
|
+
layout: {
|
|
3340
|
+
"tabs:main": {
|
|
3341
|
+
value: "overview",
|
|
3342
|
+
tabs: [
|
|
3343
|
+
{
|
|
3344
|
+
value: "overview",
|
|
3345
|
+
label: "Overview"
|
|
3346
|
+
},
|
|
3347
|
+
{
|
|
3348
|
+
value: "settings",
|
|
3349
|
+
label: "Settings"
|
|
3350
|
+
}
|
|
3351
|
+
]
|
|
3352
|
+
}
|
|
3353
|
+
}
|
|
3354
|
+
})]
|
|
3355
|
+
});
|
|
3356
|
+
|
|
3357
|
+
// src/components/entries/text.spec.ts
|
|
3358
|
+
var textSpec = component({
|
|
3359
|
+
type: "text",
|
|
3360
|
+
category: "Display",
|
|
3361
|
+
title: "Text",
|
|
3362
|
+
summary: "Plain text display.",
|
|
3363
|
+
description: "Use text for short static or dynamic copy inside SlexKit layouts.",
|
|
3364
|
+
props: {
|
|
3365
|
+
text: { type: "string", dynamic: true, description: "Displayed text." },
|
|
3366
|
+
content: { type: "string", dynamic: true, description: "Alias for text." },
|
|
3367
|
+
label: { type: "string", dynamic: true, description: "Alias for text." },
|
|
3368
|
+
variant: { type: "string", values: ["default", "muted"], default: "default", description: "Text visual variant." },
|
|
3369
|
+
color: { type: "string", dynamic: true, description: "Optional CSS color for controlled previews." },
|
|
3370
|
+
size: { type: "string | number", dynamic: true, description: "Optional font size. Numbers are treated as px." },
|
|
3371
|
+
class: { type: "string", description: "Additional host-controlled CSS class." }
|
|
3372
|
+
},
|
|
3373
|
+
children: noChildren,
|
|
3374
|
+
examples: [example("text", {
|
|
3375
|
+
namespace: "doc_text_typical",
|
|
3376
|
+
layout: {
|
|
3377
|
+
"text:status": {
|
|
3378
|
+
text: "System is healthy"
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
})]
|
|
3382
|
+
});
|
|
3383
|
+
|
|
3384
|
+
// src/components/entries/toast.spec.ts
|
|
3385
|
+
var toastSpec = component({
|
|
3386
|
+
type: "toast",
|
|
3387
|
+
category: "Feedback",
|
|
3388
|
+
title: "Toast",
|
|
3389
|
+
summary: "Transient notification.",
|
|
3390
|
+
description: "Use toast to show an inline notification with semantic status.",
|
|
3391
|
+
props: {
|
|
3392
|
+
title: { type: "string", dynamic: true, description: "Toast title." },
|
|
3393
|
+
heading: { type: "string", dynamic: true, description: "Alias for title." },
|
|
3394
|
+
label: { type: "string", dynamic: true, description: "Alias for title." },
|
|
3395
|
+
icon: { type: "string", description: "Icon name shown at the left of the toast." },
|
|
3396
|
+
description: { type: "string", dynamic: true, description: "Toast body text." },
|
|
3397
|
+
text: { type: "string", dynamic: true, description: "Alias for description." },
|
|
3398
|
+
message: { type: "string", dynamic: true, description: "Alias for description." },
|
|
3399
|
+
content: { type: "string", dynamic: true, description: "Alias for description." },
|
|
3400
|
+
type: { type: "string", values: ["info", "success", "warning", "danger"], default: "info", description: "Semantic notification type." },
|
|
3401
|
+
tone: { type: "string", values: ["info", "success", "warning", "danger"], default: "info", description: "Alias for type." },
|
|
3402
|
+
duration: { type: "number", description: "Auto-hide delay in milliseconds." },
|
|
3403
|
+
dismissable: { type: "boolean", default: true, description: "Show a close button." },
|
|
3404
|
+
dismissible: { type: "boolean", default: true, description: "Alias for dismissable." },
|
|
3405
|
+
closeLabel: { type: "string", default: "Close notification", description: "Accessible close button label." },
|
|
3406
|
+
closeAriaLabel: { type: "string", description: "Alias for closeLabel." }
|
|
3407
|
+
},
|
|
3408
|
+
children: noChildren,
|
|
3409
|
+
examples: [example("toast", {
|
|
3410
|
+
namespace: "doc_toast_typical",
|
|
3411
|
+
layout: {
|
|
3412
|
+
"toast:saved": {
|
|
3413
|
+
type: "success",
|
|
3414
|
+
title: "Saved",
|
|
3415
|
+
icon: "check-circle",
|
|
3416
|
+
description: "Changes have been written."
|
|
3417
|
+
}
|
|
3418
|
+
}
|
|
3419
|
+
})]
|
|
3420
|
+
});
|
|
3421
|
+
|
|
3422
|
+
// src/components/entries/specs.ts
|
|
3423
|
+
var componentSpecs = [
|
|
3424
|
+
accordionSpec,
|
|
3425
|
+
badgeSpec,
|
|
3426
|
+
buttonSpec,
|
|
3427
|
+
calloutSpec,
|
|
3428
|
+
cardSpec,
|
|
3429
|
+
checkboxSpec,
|
|
3430
|
+
codeBlockSpec,
|
|
3431
|
+
collapsibleSpec,
|
|
3432
|
+
columnSpec,
|
|
3433
|
+
dividerSpec,
|
|
3434
|
+
formulaSpec,
|
|
3435
|
+
gridSpec,
|
|
3436
|
+
iconSpec,
|
|
3437
|
+
inputSpec,
|
|
3438
|
+
linkSpec,
|
|
3439
|
+
playgroundSpec,
|
|
3440
|
+
progressSpec,
|
|
3441
|
+
radioGroupSpec,
|
|
3442
|
+
rowSpec,
|
|
3443
|
+
sectionSpec,
|
|
3444
|
+
selectSpec,
|
|
3445
|
+
sliderSpec,
|
|
3446
|
+
statSpec,
|
|
3447
|
+
submitSpec,
|
|
3448
|
+
switchSpec,
|
|
3449
|
+
tableSpec,
|
|
3450
|
+
tabsSpec,
|
|
3451
|
+
textSpec,
|
|
3452
|
+
toastSpec
|
|
3453
|
+
];
|
|
3454
|
+
|
|
3455
|
+
// src/components/spec-registry.ts
|
|
3456
|
+
var publicComponentTypes = componentSpecs.map((spec) => spec.type);
|
|
3457
|
+
var componentSpecByType = new Map(componentSpecs.map((spec) => [spec.type, spec]));
|
|
3458
|
+
|
|
3459
|
+
// src/engine/capabilities.ts
|
|
3460
|
+
var slexkitStdlibDocs = [
|
|
3461
|
+
{
|
|
3462
|
+
name: "math",
|
|
3463
|
+
summary: "Small numeric helpers for common interactive calculations.",
|
|
3464
|
+
functions: [
|
|
3465
|
+
{ 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)" },
|
|
3466
|
+
{ 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)" },
|
|
3467
|
+
{ 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)" },
|
|
3468
|
+
{ 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)" },
|
|
3469
|
+
{ name: "std.math.lerp", signature: "lerp(start, end, t)", summary: "Linear interpolation.", pure: true, example: "std.math.lerp(0, 100, g.progress)" }
|
|
3470
|
+
]
|
|
3471
|
+
},
|
|
3472
|
+
{
|
|
3473
|
+
name: "stats",
|
|
3474
|
+
summary: "Finite-number aggregations for arrays.",
|
|
3475
|
+
functions: [
|
|
3476
|
+
{ name: "std.stats.sum", signature: "sum(values)", summary: "Sum finite numeric values. Empty arrays return 0.", pure: true, example: "std.stats.sum(g.samples)" },
|
|
3477
|
+
{ name: "std.stats.mean", signature: "mean(values)", summary: "Average finite numeric values. Empty arrays return NaN.", pure: true, example: "std.stats.mean(g.samples)" },
|
|
3478
|
+
{ name: "std.stats.min", signature: "min(values)", summary: "Minimum finite numeric value. Empty arrays return NaN.", pure: true, example: "std.stats.min(g.samples)" },
|
|
3479
|
+
{ name: "std.stats.max", signature: "max(values)", summary: "Maximum finite numeric value. Empty arrays return NaN.", pure: true, example: "std.stats.max(g.samples)" },
|
|
3480
|
+
{ name: "std.stats.median", signature: "median(values)", summary: "Median finite numeric value. Empty arrays return NaN.", pure: true, example: "std.stats.median(g.samples)" }
|
|
3481
|
+
]
|
|
3482
|
+
},
|
|
3483
|
+
{
|
|
3484
|
+
name: "format",
|
|
3485
|
+
summary: "Deterministic display formatting with en-US defaults.",
|
|
3486
|
+
functions: [
|
|
3487
|
+
{ 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)" },
|
|
3488
|
+
{ name: "std.format.number", signature: "number(value, digits = 0, locale = 'en-US')", summary: "Locale number formatting.", pure: true, example: "std.format.number(g.requests)" },
|
|
3489
|
+
{ name: "std.format.compact", signature: "compact(value, digits = 1, locale = 'en-US')", summary: "Compact number formatting.", pure: true, example: "std.format.compact(g.users)" },
|
|
3490
|
+
{ 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)" },
|
|
3491
|
+
{ 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')" }
|
|
3492
|
+
]
|
|
3493
|
+
},
|
|
3494
|
+
{
|
|
3495
|
+
name: "units",
|
|
3496
|
+
summary: "Small unit display helpers for common dashboards.",
|
|
3497
|
+
functions: [
|
|
3498
|
+
{ 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)" },
|
|
3499
|
+
{ 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)" },
|
|
3500
|
+
{ 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)" },
|
|
3501
|
+
{ 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)" }
|
|
3502
|
+
]
|
|
3503
|
+
}
|
|
3504
|
+
];
|
|
3505
|
+
var slexkitRuntimeCapabilities = [
|
|
3506
|
+
{ 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"] },
|
|
3507
|
+
{ 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"] },
|
|
3508
|
+
{ 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"] },
|
|
3509
|
+
{ 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)"] },
|
|
3510
|
+
{ name: "api.clearTimeout", policy: "timer", signature: "clearTimeout(id)", summary: "Clear a policy-gated timeout.", example: "api.clearTimeout(g.timeoutId)", secureDefault: "denied" },
|
|
3511
|
+
{ 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)"] },
|
|
3512
|
+
{ name: "api.clearInterval", policy: "timer", signature: "clearInterval(id)", summary: "Clear a policy-gated interval.", example: "api.clearInterval(g.intervalId)", secureDefault: "denied" },
|
|
3513
|
+
{ 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)"] },
|
|
3514
|
+
{ name: "api.cancelRaf", policy: "animation", signature: "cancelRaf(id)", summary: "Cancel a policy-gated animation frame.", example: "api.cancelRaf(g.rafId)", secureDefault: "denied" },
|
|
3515
|
+
{ name: "api.createCanvas", policy: "canvas", signature: "createCanvas(width, height)", summary: "Create a policy-counted canvas.", example: "var canvas = api.createCanvas(320, 180)", secureDefault: "denied" },
|
|
3516
|
+
{ 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" },
|
|
3517
|
+
{ name: "api.onDispose", policy: "lifecycle", signature: "onDispose(fn)", summary: "Register runtime cleanup.", example: "api.onDispose(function () { g.closed = true; })", secureDefault: "available" },
|
|
3518
|
+
{ name: "api.now", policy: "lifecycle", signature: "now()", summary: "Runtime clock.", example: "api.now()", secureDefault: "available" },
|
|
3519
|
+
{ name: "api.isTimeoutError", policy: "diagnostics", signature: "isTimeoutError(error)", summary: "Check timeout errors.", example: "api.isTimeoutError(error)", secureDefault: "available" },
|
|
3520
|
+
{ name: "api.isNetworkError", policy: "diagnostics", signature: "isNetworkError(error)", summary: "Check network errors.", example: "api.isNetworkError(error)", secureDefault: "available" },
|
|
3521
|
+
{ name: "api.isPolicyError", policy: "diagnostics", signature: "isPolicyError(error)", summary: "Check policy errors.", example: "api.isPolicyError(error)", secureDefault: "available" },
|
|
3522
|
+
{ name: "api.errorMessage", policy: "diagnostics", signature: "errorMessage(error)", summary: "Extract a displayable error message.", example: "api.errorMessage(error)", secureDefault: "available" }
|
|
3523
|
+
];
|
|
3524
|
+
var slexkitStdlibFunctionNames = slexkitStdlibDocs.flatMap((namespace) => namespace.functions.map((fn) => fn.name));
|
|
3525
|
+
var slexkitRuntimeCapabilityNames = slexkitRuntimeCapabilities.map((capability) => capability.name);
|
|
3526
|
+
|
|
3527
|
+
// src/engine/validation.ts
|
|
3528
|
+
var componentSpecByType2 = new Map(componentSpecs.map((spec) => [spec.type, spec]));
|
|
3529
|
+
var stdlibFunctions = new Set(slexkitStdlibFunctionNames);
|
|
3530
|
+
var stdlibNamespaces = new Set(slexkitStdlibFunctionNames.map((name) => name.split(".").slice(0, 2).join(".")));
|
|
3531
|
+
var apiMembers = new Set(slexkitRuntimeCapabilityNames);
|
|
3532
|
+
var directiveProps = new Set(["$if", "$for", "$key"]);
|
|
3533
|
+
var nativeSecureCapabilities = [
|
|
3534
|
+
{ value: "fetch", pattern: /(?<!\.)\bfetch\s*\(/ },
|
|
3535
|
+
{ value: "XMLHttpRequest", pattern: /\bXMLHttpRequest\b/ },
|
|
3536
|
+
{ value: "WebSocket", pattern: /\bWebSocket\b/ },
|
|
3537
|
+
{ value: "setTimeout", pattern: /(?<!\.)\bsetTimeout\s*\(/ },
|
|
3538
|
+
{ value: "requestAnimationFrame", pattern: /(?<!\.)\brequestAnimationFrame\s*\(/ }
|
|
3539
|
+
];
|
|
3540
|
+
function collectMemberUsage(source, root) {
|
|
3541
|
+
const matches = source.matchAll(new RegExp(`\\b${root}\\.([A-Za-z_$][\\w$]*)(?:\\.([A-Za-z_$][\\w$]*))?`, "g"));
|
|
3542
|
+
const found = new Set;
|
|
3543
|
+
for (const match of matches) {
|
|
3544
|
+
found.add([root, match[1], match[2]].filter(Boolean).join("."));
|
|
3545
|
+
}
|
|
3546
|
+
return [...found].sort();
|
|
3547
|
+
}
|
|
3548
|
+
function componentKeyType(key) {
|
|
3549
|
+
const colon = key.indexOf(":");
|
|
3550
|
+
return colon > 0 ? key.slice(0, colon) : null;
|
|
3551
|
+
}
|
|
3552
|
+
function isKnownProp(type, key) {
|
|
3553
|
+
if (directiveProps.has(key) || key.startsWith("on"))
|
|
3554
|
+
return true;
|
|
3555
|
+
const spec = componentSpecByType2.get(type);
|
|
3556
|
+
if (!spec)
|
|
3557
|
+
return true;
|
|
3558
|
+
const propName = key.startsWith("$") ? key.slice(1) : key;
|
|
3559
|
+
return propName in spec.props;
|
|
3560
|
+
}
|
|
3561
|
+
function walkComponents(value, warnings, usage, path = "") {
|
|
3562
|
+
if (!value || typeof value !== "object")
|
|
3563
|
+
return;
|
|
3564
|
+
for (const [key, child] of Object.entries(value)) {
|
|
3565
|
+
const childPath = path ? `${path}.${key}` : key;
|
|
3566
|
+
const type = componentKeyType(key);
|
|
3567
|
+
if (type) {
|
|
3568
|
+
usage.add(type);
|
|
3569
|
+
const spec = componentSpecByType2.get(type);
|
|
3570
|
+
if (!spec) {
|
|
3571
|
+
warnings.push({
|
|
3572
|
+
code: "unknown_component",
|
|
3573
|
+
message: `Unknown SlexKit component '${type}'.`,
|
|
3574
|
+
path: childPath,
|
|
3575
|
+
value: type
|
|
3576
|
+
});
|
|
3577
|
+
}
|
|
3578
|
+
if (child && typeof child === "object") {
|
|
3579
|
+
for (const propName of Object.keys(child)) {
|
|
3580
|
+
if (componentKeyType(propName))
|
|
3581
|
+
continue;
|
|
3582
|
+
if (!isKnownProp(type, propName)) {
|
|
3583
|
+
warnings.push({
|
|
3584
|
+
code: "unknown_prop",
|
|
3585
|
+
message: `Unknown prop '${propName}' on component '${type}'.`,
|
|
3586
|
+
path: `${childPath}.${propName}`,
|
|
3587
|
+
value: propName
|
|
3588
|
+
});
|
|
3589
|
+
}
|
|
3590
|
+
}
|
|
3591
|
+
}
|
|
3592
|
+
}
|
|
3593
|
+
walkComponents(child, warnings, usage, childPath);
|
|
3594
|
+
}
|
|
3595
|
+
}
|
|
3596
|
+
function sourceWarnings(source, mode) {
|
|
3597
|
+
const warnings = [];
|
|
3598
|
+
const stdlibUsage = collectMemberUsage(source, "std");
|
|
3599
|
+
const apiUsage = collectMemberUsage(source, "api");
|
|
3600
|
+
for (const name of stdlibUsage) {
|
|
3601
|
+
if (stdlibFunctions.has(name) || stdlibNamespaces.has(name))
|
|
3602
|
+
continue;
|
|
3603
|
+
warnings.push({
|
|
3604
|
+
code: "unknown_std_member",
|
|
3605
|
+
message: `Unknown SlexKit stdlib member '${name}'.`,
|
|
3606
|
+
value: name
|
|
3607
|
+
});
|
|
3608
|
+
}
|
|
3609
|
+
for (const name of apiUsage) {
|
|
3610
|
+
if (apiMembers.has(name))
|
|
3611
|
+
continue;
|
|
3612
|
+
warnings.push({
|
|
3613
|
+
code: "unknown_api_member",
|
|
3614
|
+
message: `Unknown SlexKit runtime API member '${name}'.`,
|
|
3615
|
+
value: name
|
|
3616
|
+
});
|
|
3617
|
+
}
|
|
3618
|
+
if (mode === "secure") {
|
|
3619
|
+
for (const capability of nativeSecureCapabilities) {
|
|
3620
|
+
if (capability.pattern.test(source)) {
|
|
3621
|
+
warnings.push({
|
|
3622
|
+
code: "native_secure_capability",
|
|
3623
|
+
message: `Native '${capability.value}' is not supported in secure mode. Use policy-gated api.* instead.`,
|
|
3624
|
+
value: capability.value
|
|
3625
|
+
});
|
|
3626
|
+
}
|
|
3627
|
+
}
|
|
3628
|
+
}
|
|
3629
|
+
return { warnings, stdlibUsage, apiUsage };
|
|
3630
|
+
}
|
|
3631
|
+
function validateSlexSource(source, options = {}) {
|
|
3632
|
+
const mode = options.mode ?? "trusted";
|
|
3633
|
+
const parsed = parseSlexSource(source);
|
|
3634
|
+
const usage = new Set;
|
|
3635
|
+
const { warnings, stdlibUsage, apiUsage } = sourceWarnings(source, mode);
|
|
3636
|
+
if (!parsed.ok) {
|
|
3637
|
+
return {
|
|
3638
|
+
ok: false,
|
|
3639
|
+
diagnostic: parsed.diagnostic,
|
|
3640
|
+
warnings,
|
|
3641
|
+
componentUsage: [],
|
|
3642
|
+
stdlibUsage,
|
|
3643
|
+
apiUsage
|
|
3644
|
+
};
|
|
3645
|
+
}
|
|
3646
|
+
walkComponents(parsed.value, warnings, usage);
|
|
3647
|
+
return {
|
|
3648
|
+
ok: true,
|
|
3649
|
+
value: parsed.value,
|
|
3650
|
+
warnings,
|
|
3651
|
+
componentUsage: [...usage].sort(),
|
|
3652
|
+
stdlibUsage,
|
|
3653
|
+
apiUsage
|
|
3654
|
+
};
|
|
3655
|
+
}
|
|
2136
3656
|
// src/engine/markdown-runtime.ts
|
|
2137
3657
|
var DEFAULT_POLICY = {};
|
|
2138
3658
|
function isRecord(value) {
|
|
@@ -2148,7 +3668,11 @@ function isRenderableSource(value) {
|
|
|
2148
3668
|
return Object.keys(value).some((key) => key.includes(":"));
|
|
2149
3669
|
}
|
|
2150
3670
|
function bareLayoutFromSource(value) {
|
|
2151
|
-
const
|
|
3671
|
+
const layout = { ...value };
|
|
3672
|
+
delete layout.slex;
|
|
3673
|
+
delete layout.namespace;
|
|
3674
|
+
delete layout.g;
|
|
3675
|
+
delete layout.layout;
|
|
2152
3676
|
return layout;
|
|
2153
3677
|
}
|
|
2154
3678
|
function isStateOnlySource(value) {
|
|
@@ -2258,7 +3782,7 @@ function createSlexKitMarkdownRuntimeHost(initialOptions = {}) {
|
|
|
2258
3782
|
const __target = { slex: "0.1", namespace: __artifactPrefix + "default", g: {}, layout: {} };
|
|
2259
3783
|
const __layouts = [];
|
|
2260
3784
|
for (const __entry of __sources) {
|
|
2261
|
-
const __script = __entry.kind === "json" ? JSON.parse(__entry.source) : (0, eval)(__entry.source);
|
|
3785
|
+
const __script = __entry.kind === "json" ? JSON.parse(__entry.source) : (0, eval)("(" + __entry.source + ")");
|
|
2262
3786
|
if (!__isRecord(__script)) continue;
|
|
2263
3787
|
if ("slex" in __script) __target.slex = String(__script.slex || "0.1");
|
|
2264
3788
|
if ("namespace" in __script) __target.namespace = __artifactPrefix + String(__script.namespace || "default");
|
|
@@ -2696,14 +4220,20 @@ function createSecureFrameTarget(input, container, mountOptions) {
|
|
|
2696
4220
|
iframe.title = options.title ?? "SlexKit secure artifact";
|
|
2697
4221
|
iframe.setAttribute("data-slexkit-secure-frame", "true");
|
|
2698
4222
|
iframe.setAttribute("referrerpolicy", "no-referrer");
|
|
4223
|
+
iframe.style.display = "block";
|
|
4224
|
+
iframe.style.width = "100%";
|
|
4225
|
+
iframe.style.border = "0";
|
|
4226
|
+
iframe.style.background = "transparent";
|
|
2699
4227
|
const runtimeUrl = options.runtimeUrl ?? options.runnerUrl ?? defaultRuntimeUrl;
|
|
2700
4228
|
if (runtimeUrl) {
|
|
2701
4229
|
const resolvedRuntimeUrl = resolveRuntimeUrl(runtimeUrl);
|
|
4230
|
+
const styleUrl = options.styleUrl === false ? "" : options.styleUrl ?? defaultSecureFrameStyleUrl(resolvedRuntimeUrl);
|
|
4231
|
+
const resolvedStyleUrl = styleUrl ? resolveRuntimeUrl(styleUrl) : undefined;
|
|
2702
4232
|
assertSandboxCloneable(input);
|
|
2703
4233
|
iframe.setAttribute("sandbox", secureSandboxAttribute(options));
|
|
2704
4234
|
container.replaceChildren(iframe);
|
|
2705
4235
|
const bridge = createSandboxBridge(input, container, iframe, mountOptions);
|
|
2706
|
-
iframe.srcdoc = secureRunnerSrcdoc(resolvedRuntimeUrl);
|
|
4236
|
+
iframe.srcdoc = secureRunnerSrcdoc(resolvedRuntimeUrl, resolvedStyleUrl);
|
|
2707
4237
|
return bridge;
|
|
2708
4238
|
}
|
|
2709
4239
|
if (frame) {
|
|
@@ -2763,23 +4293,38 @@ function cspSourceForRuntime(runtimeUrl) {
|
|
|
2763
4293
|
return "'self'";
|
|
2764
4294
|
}
|
|
2765
4295
|
}
|
|
4296
|
+
function defaultSecureFrameStyleUrl(runtimeUrl) {
|
|
4297
|
+
try {
|
|
4298
|
+
const url = new URL(runtimeUrl);
|
|
4299
|
+
if (url.protocol === "blob:" || url.protocol === "data:")
|
|
4300
|
+
return "";
|
|
4301
|
+
url.pathname = url.pathname.replace(/[^/]+$/, "slexkit.css");
|
|
4302
|
+
url.search = "";
|
|
4303
|
+
url.hash = "";
|
|
4304
|
+
return url.href;
|
|
4305
|
+
} catch {
|
|
4306
|
+
return "";
|
|
4307
|
+
}
|
|
4308
|
+
}
|
|
2766
4309
|
function escapeHtmlAttribute(value) {
|
|
2767
4310
|
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
2768
4311
|
}
|
|
2769
|
-
function secureRunnerSrcdoc(runtimeUrl) {
|
|
4312
|
+
function secureRunnerSrcdoc(runtimeUrl, styleUrl) {
|
|
2770
4313
|
const nonce = randomToken(12);
|
|
4314
|
+
const styleSource = styleUrl ? ` ${cspSourceForRuntime(styleUrl)}` : "";
|
|
2771
4315
|
const csp = [
|
|
2772
4316
|
"default-src 'none'",
|
|
2773
4317
|
`script-src 'nonce-${nonce}' 'unsafe-eval' ${cspSourceForRuntime(runtimeUrl)}`,
|
|
2774
4318
|
"connect-src 'none'",
|
|
2775
4319
|
"img-src data: blob:",
|
|
2776
|
-
|
|
4320
|
+
`style-src 'unsafe-inline'${styleSource}`,
|
|
2777
4321
|
"font-src data:",
|
|
2778
4322
|
"form-action 'none'",
|
|
2779
4323
|
"base-uri 'none'"
|
|
2780
4324
|
].join("; ");
|
|
2781
4325
|
const style = "html,body{margin:0;min-height:100%;overflow:hidden;}#slexkit-secure-root{min-height:100%;}";
|
|
2782
|
-
|
|
4326
|
+
const stylesheet = styleUrl ? `<link rel="stylesheet" href="${escapeHtmlAttribute(styleUrl)}">` : "";
|
|
4327
|
+
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>`;
|
|
2783
4328
|
}
|
|
2784
4329
|
function secureFrameLoadTimeout(options) {
|
|
2785
4330
|
const frame = options.frame;
|
|
@@ -3031,11 +4576,21 @@ function createSandboxBridge(input, container, iframe, options) {
|
|
|
3031
4576
|
if (data.type === "slot-size" && data.id === id && data.token === token && typeof data.slotId === "string") {
|
|
3032
4577
|
const slot = artifactSlots.find((item) => item.id === data.slotId);
|
|
3033
4578
|
if (slot && typeof data.height === "number" && Number.isFinite(data.height)) {
|
|
3034
|
-
|
|
4579
|
+
const height = Math.max(0, Math.ceil(data.height));
|
|
4580
|
+
slot.container.style.minHeight = `${height}px`;
|
|
4581
|
+
if (artifactSlots.length === 1) {
|
|
4582
|
+
iframe.style.height = `${Math.max(1, height)}px`;
|
|
4583
|
+
}
|
|
3035
4584
|
requestArtifactSlotSync();
|
|
3036
4585
|
}
|
|
3037
4586
|
return;
|
|
3038
4587
|
}
|
|
4588
|
+
if (data.type === "frame-size" && data.id === id && data.token === token) {
|
|
4589
|
+
if (artifactSlots.length <= 1 && typeof data.height === "number" && Number.isFinite(data.height)) {
|
|
4590
|
+
iframe.style.height = `${Math.max(1, Math.ceil(data.height))}px`;
|
|
4591
|
+
}
|
|
4592
|
+
return;
|
|
4593
|
+
}
|
|
3039
4594
|
if (data.type === "error" && (!data.id || data.id === id) && (!data.token || data.token === token)) {
|
|
3040
4595
|
const error = data;
|
|
3041
4596
|
failLoad(ready ? "mounted" : "ready", typeof error.error?.message === "string" ? error.error.message : undefined);
|
|
@@ -3452,6 +5007,7 @@ function startSlexKitSandboxRunner() {
|
|
|
3452
5007
|
let activeId;
|
|
3453
5008
|
let activeToken;
|
|
3454
5009
|
let heartbeatTimer;
|
|
5010
|
+
let rootResizeObserver;
|
|
3455
5011
|
let slotResizeObserver;
|
|
3456
5012
|
function clearHeartbeat() {
|
|
3457
5013
|
if (heartbeatTimer === undefined)
|
|
@@ -3477,6 +5033,8 @@ function startSlexKitSandboxRunner() {
|
|
|
3477
5033
|
if (id && activeId && id !== activeId)
|
|
3478
5034
|
return;
|
|
3479
5035
|
clearHeartbeat();
|
|
5036
|
+
rootResizeObserver?.disconnect();
|
|
5037
|
+
rootResizeObserver = undefined;
|
|
3480
5038
|
slotResizeObserver?.disconnect();
|
|
3481
5039
|
slotResizeObserver = undefined;
|
|
3482
5040
|
cleanup?.();
|
|
@@ -3510,6 +5068,36 @@ function startSlexKitSandboxRunner() {
|
|
|
3510
5068
|
height
|
|
3511
5069
|
});
|
|
3512
5070
|
}
|
|
5071
|
+
function reportFrameSize() {
|
|
5072
|
+
if (!activeId || !activeToken)
|
|
5073
|
+
return;
|
|
5074
|
+
const root = frameRoot();
|
|
5075
|
+
const rootRect = root.getBoundingClientRect();
|
|
5076
|
+
let contentBottom = rootRect.height;
|
|
5077
|
+
for (const child of root.querySelectorAll("*")) {
|
|
5078
|
+
const rect = child.getBoundingClientRect();
|
|
5079
|
+
contentBottom = Math.max(contentBottom, rect.bottom - rootRect.top);
|
|
5080
|
+
}
|
|
5081
|
+
const height = Math.max(root.scrollHeight, root.getBoundingClientRect().height, contentBottom, document.body?.scrollHeight ?? 0, document.documentElement?.scrollHeight ?? 0, 1);
|
|
5082
|
+
post({
|
|
5083
|
+
channel: "slexkit-secure",
|
|
5084
|
+
type: "frame-size",
|
|
5085
|
+
id: activeId,
|
|
5086
|
+
token: activeToken,
|
|
5087
|
+
height
|
|
5088
|
+
});
|
|
5089
|
+
}
|
|
5090
|
+
function observeFrameSize() {
|
|
5091
|
+
rootResizeObserver?.disconnect();
|
|
5092
|
+
rootResizeObserver = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => reportFrameSize()) : undefined;
|
|
5093
|
+
const root = frameRoot();
|
|
5094
|
+
rootResizeObserver?.observe(root);
|
|
5095
|
+
for (const child of root.children) {
|
|
5096
|
+
rootResizeObserver?.observe(child);
|
|
5097
|
+
}
|
|
5098
|
+
reportFrameSize();
|
|
5099
|
+
schedulingSnapshot.requestAnimationFrame?.(() => reportFrameSize());
|
|
5100
|
+
}
|
|
3513
5101
|
function applySlotRects(slots) {
|
|
3514
5102
|
const root = frameRoot();
|
|
3515
5103
|
const multiSlot = slots.length > 1;
|
|
@@ -3566,6 +5154,7 @@ function startSlexKitSandboxRunner() {
|
|
|
3566
5154
|
api: runtime.api
|
|
3567
5155
|
});
|
|
3568
5156
|
startHeartbeat(message.policy, message.id, message.token);
|
|
5157
|
+
observeFrameSize();
|
|
3569
5158
|
post({
|
|
3570
5159
|
channel: "slexkit-secure",
|
|
3571
5160
|
type: "mounted",
|
|
@@ -3645,6 +5234,7 @@ var formatSlexKitDiagnosticApi = formatSlexKitDiagnostic;
|
|
|
3645
5234
|
var mountSecureArtifactApi = mountSecureArtifact;
|
|
3646
5235
|
var parseSlexSourceApi = parseSlexSource;
|
|
3647
5236
|
var parseSlexKitDslApi = parseSlexKitDsl;
|
|
5237
|
+
var validateSlexSourceApi = validateSlexSource;
|
|
3648
5238
|
var createSecureRuntimeApi = createSecureRuntime;
|
|
3649
5239
|
var SlexKitRuntimeErrorApi = SlexKitRuntimeError;
|
|
3650
5240
|
var getSlexKitRuntimeUrlApi = getSlexKitRuntimeUrl;
|
|
@@ -3652,12 +5242,15 @@ var setSlexKitRuntimeUrlApi = setSlexKitRuntimeUrl;
|
|
|
3652
5242
|
var createSlexKitMarkdownRuntimeHostApi = createSlexKitMarkdownRuntimeHost;
|
|
3653
5243
|
var getSlexKitMarkdownRuntimeHostApi = getSlexKitMarkdownRuntimeHost;
|
|
3654
5244
|
var installSlexKitMarkdownRuntimeHostApi = installSlexKitMarkdownRuntimeHost;
|
|
5245
|
+
var slexkitStdApi = slexkitStd;
|
|
3655
5246
|
var attachComponentDisposerApi = attachComponentDisposer;
|
|
3656
5247
|
var configureComponentScopeApi = configureComponentScope;
|
|
3657
5248
|
var startSlexKitSandboxRunnerApi = startSlexKitSandboxRunner;
|
|
3658
5249
|
var getSlexKitInfoApi = getSlexKitInfo;
|
|
3659
5250
|
export {
|
|
5251
|
+
validateSlexSourceApi as validateSlexSource,
|
|
3660
5252
|
startSlexKitSandboxRunnerApi as startSlexKitSandboxRunner,
|
|
5253
|
+
slexkitStdApi as slexkitStd,
|
|
3661
5254
|
setSlexKitRuntimeUrlApi as setSlexKitRuntimeUrl,
|
|
3662
5255
|
registerApi as register,
|
|
3663
5256
|
parseSlexSourceApi as parseSlexSource,
|