slexkit 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.slexkit.md +29 -0
- package/CHANGELOG.md +90 -0
- package/LICENSE +21 -0
- package/README.md +165 -0
- package/README.zh-CN.md +165 -0
- package/dist/ai/llms-authoring.txt +44 -0
- package/dist/ai/llms-components.txt +669 -0
- package/dist/ai/llms-full.txt +6586 -0
- package/dist/ai/llms-runtime.txt +1475 -0
- package/dist/ai/llms-toolhost.txt +295 -0
- package/dist/ai/llms.txt +69 -0
- package/dist/ai/slexkit-ai-manifest.json +2922 -0
- package/dist/base.css +621 -0
- package/dist/chunks/accordion-5f0nvjjm.js +376 -0
- package/dist/chunks/accordion-830dw78f.js +221 -0
- package/dist/chunks/accordion-cfjyxw93.js +630 -0
- package/dist/chunks/accordion-cw5r75jm.js +424 -0
- package/dist/chunks/accordion-ehnhpeca.js +492 -0
- package/dist/chunks/accordion-hzyrngd6.js +2377 -0
- package/dist/chunks/accordion-nw12ytps.js +6823 -0
- package/dist/components/accordion.js +163 -0
- package/dist/components/badge.js +80 -0
- package/dist/components/button.css +114 -0
- package/dist/components/button.js +16 -0
- package/dist/components/callout.js +154 -0
- package/dist/components/card.js +95 -0
- package/dist/components/checkbox.js +114 -0
- package/dist/components/choice.css +165 -0
- package/dist/components/code-block.js +264 -0
- package/dist/components/collapsible.js +111 -0
- package/dist/components/column.js +49 -0
- package/dist/components/content.css +474 -0
- package/dist/components/disclosure.css +162 -0
- package/dist/components/display.css +259 -0
- package/dist/components/divider.js +98 -0
- package/dist/components/feedback.css +219 -0
- package/dist/components/grid.js +67 -0
- package/dist/components/index.js +13364 -0
- package/dist/components/input.css +1247 -0
- package/dist/components/input.js +384 -0
- package/dist/components/link.js +77 -0
- package/dist/components/progress.js +111 -0
- package/dist/components/radio-group.js +189 -0
- package/dist/components/row.js +200 -0
- package/dist/components/section.js +161 -0
- package/dist/components/select.css +260 -0
- package/dist/components/select.js +16 -0
- package/dist/components/slider.css +125 -0
- package/dist/components/slider.js +175 -0
- package/dist/components/specs.js +1090 -0
- package/dist/components/stat.js +178 -0
- package/dist/components/submit.css +9 -0
- package/dist/components/submit.js +77 -0
- package/dist/components/switch.css +114 -0
- package/dist/components/switch.js +114 -0
- package/dist/components/table.js +157 -0
- package/dist/components/tabs.css +192 -0
- package/dist/components/tabs.js +17 -0
- package/dist/components/text-input.css +245 -0
- package/dist/components/text.js +50 -0
- package/dist/components/toast.js +240 -0
- package/dist/components/tooling.css +1009 -0
- package/dist/components/tooling.js +48951 -0
- package/dist/runtime.cjs +3728 -0
- package/dist/runtime.js +3686 -0
- package/dist/slexkit.cjs +18539 -0
- package/dist/slexkit.css +4776 -0
- package/dist/slexkit.js +18497 -0
- package/dist/tooling.js +59141 -0
- package/dist/types/components/accordion.d.ts +2 -0
- package/dist/types/components/badge.d.ts +2 -0
- package/dist/types/components/button.d.ts +2 -0
- package/dist/types/components/callout.d.ts +2 -0
- package/dist/types/components/card.d.ts +2 -0
- package/dist/types/components/checkbox.d.ts +2 -0
- package/dist/types/components/code-block.d.ts +2 -0
- package/dist/types/components/collapsible.d.ts +2 -0
- package/dist/types/components/column.d.ts +2 -0
- package/dist/types/components/divider.d.ts +2 -0
- package/dist/types/components/entries/accordion.d.ts +3 -0
- package/dist/types/components/entries/badge.d.ts +3 -0
- package/dist/types/components/entries/button.d.ts +3 -0
- package/dist/types/components/entries/callout.d.ts +3 -0
- package/dist/types/components/entries/card.d.ts +3 -0
- package/dist/types/components/entries/checkbox.d.ts +3 -0
- package/dist/types/components/entries/code-block.d.ts +3 -0
- package/dist/types/components/entries/collapsible.d.ts +3 -0
- package/dist/types/components/entries/column.d.ts +3 -0
- package/dist/types/components/entries/divider.d.ts +3 -0
- package/dist/types/components/entries/grid.d.ts +3 -0
- package/dist/types/components/entries/input.d.ts +3 -0
- package/dist/types/components/entries/link.d.ts +3 -0
- package/dist/types/components/entries/progress.d.ts +3 -0
- package/dist/types/components/entries/radio-group.d.ts +3 -0
- package/dist/types/components/entries/row.d.ts +3 -0
- package/dist/types/components/entries/section.d.ts +3 -0
- package/dist/types/components/entries/select.d.ts +3 -0
- package/dist/types/components/entries/slider.d.ts +3 -0
- package/dist/types/components/entries/specs.d.ts +1 -0
- package/dist/types/components/entries/stat.d.ts +3 -0
- package/dist/types/components/entries/submit.d.ts +3 -0
- package/dist/types/components/entries/switch.d.ts +3 -0
- package/dist/types/components/entries/table.d.ts +3 -0
- package/dist/types/components/entries/tabs.d.ts +3 -0
- package/dist/types/components/entries/text.d.ts +3 -0
- package/dist/types/components/entries/toast.d.ts +3 -0
- package/dist/types/components/entries/tooling.d.ts +1 -0
- package/dist/types/components/grid.d.ts +2 -0
- package/dist/types/components/index.d.ts +6 -0
- package/dist/types/components/input.d.ts +2 -0
- package/dist/types/components/link.d.ts +2 -0
- package/dist/types/components/progress.d.ts +2 -0
- package/dist/types/components/radio-group.d.ts +2 -0
- package/dist/types/components/row.d.ts +2 -0
- package/dist/types/components/section.d.ts +2 -0
- package/dist/types/components/select.d.ts +2 -0
- package/dist/types/components/slider.d.ts +2 -0
- package/dist/types/components/spec-helpers.d.ts +23 -0
- package/dist/types/components/spec-registry.d.ts +12 -0
- package/dist/types/components/spec-schema.d.ts +74 -0
- package/dist/types/components/specs.d.ts +2 -0
- package/dist/types/components/stat.d.ts +2 -0
- package/dist/types/components/submit.d.ts +2 -0
- package/dist/types/components/svelte/adapter.d.ts +3 -0
- package/dist/types/components/svelte/bindProps.d.ts +2 -0
- package/dist/types/components/svelte/helpers.d.ts +33 -0
- package/dist/types/components/svelte/layout/balancedTiles.d.ts +14 -0
- package/dist/types/components/svelte/types.d.ts +12 -0
- package/dist/types/components/switch.d.ts +2 -0
- package/dist/types/components/table.d.ts +2 -0
- package/dist/types/components/tabs.d.ts +2 -0
- package/dist/types/components/text.d.ts +2 -0
- package/dist/types/components/toast.d.ts +2 -0
- package/dist/types/components/tooling.d.ts +2 -0
- package/dist/types/components-svelte.d.ts +5 -0
- package/dist/types/engine/component-scope.d.ts +14 -0
- package/dist/types/engine/component-state.d.ts +9 -0
- package/dist/types/engine/diagnostics.d.ts +24 -0
- package/dist/types/engine/engineering.d.ts +11 -0
- package/dist/types/engine/eval.d.ts +5 -0
- package/dist/types/engine/index.d.ts +26 -0
- package/dist/types/engine/markdown-runtime.d.ts +33 -0
- package/dist/types/engine/merge.d.ts +1 -0
- package/dist/types/engine/reactive.d.ts +11 -0
- package/dist/types/engine/registry.d.ts +4 -0
- package/dist/types/engine/renderer.d.ts +6 -0
- package/dist/types/engine/sandbox-runner.d.ts +2 -0
- package/dist/types/engine/secure-runtime.d.ts +214 -0
- package/dist/types/engine/store.d.ts +12 -0
- package/dist/types/engine/types.d.ts +58 -0
- package/dist/types/icons/manager.d.ts +17 -0
- package/dist/types/icons/phosphor.d.ts +45 -0
- package/dist/types/index.d.ts +61 -0
- package/dist/types/runtime.d.ts +32 -0
- package/dist/types/toolhost/index.d.ts +78 -0
- package/dist/types/tooling-umd.d.ts +47 -0
- package/dist/types/version.d.ts +8 -0
- package/dist/umd/slexkit.tooling.umd.js +66553 -0
- package/dist/umd/slexkit.umd.js +18552 -0
- package/package.json +136 -0
- package/scripts/cli.mjs +47 -0
- package/skills/slexkit/SKILL.md +27 -0
- package/skills/slexkit-author/SKILL.md +50 -0
- package/skills/slexkit-host-integration/SKILL.md +33 -0
- package/skills/slexkit-secure-runtime/SKILL.md +31 -0
- package/skills/slexkit-toolhost/SKILL.md +38 -0
- package/skills/slexkit-update/SKILL.md +23 -0
- package/src/components/svelte/InlineIcon.svelte +66 -0
- package/src/components/svelte/adapter.ts +76 -0
- package/src/components/svelte/bindProps.ts +9 -0
- package/src/components/svelte/content/Badge.svelte +19 -0
- package/src/components/svelte/content/Callout.svelte +57 -0
- package/src/components/svelte/content/CodeBlock.svelte +130 -0
- package/src/components/svelte/content/Divider.svelte +21 -0
- package/src/components/svelte/content/Link.svelte +21 -0
- package/src/components/svelte/content/Section.svelte +24 -0
- package/src/components/svelte/content/Table.svelte +44 -0
- package/src/components/svelte/disclosure/Accordion.svelte +100 -0
- package/src/components/svelte/disclosure/Collapsible.svelte +45 -0
- package/src/components/svelte/display/Stat.svelte +102 -0
- package/src/components/svelte/display/Text.svelte +11 -0
- package/src/components/svelte/feedback/Progress.svelte +34 -0
- package/src/components/svelte/feedback/Toast.svelte +105 -0
- package/src/components/svelte/helpers.ts +148 -0
- package/src/components/svelte/input/Button.svelte +78 -0
- package/src/components/svelte/input/Checkbox.svelte +52 -0
- package/src/components/svelte/input/Input.svelte +202 -0
- package/src/components/svelte/input/RadioGroup.svelte +71 -0
- package/src/components/svelte/input/Select.svelte +220 -0
- package/src/components/svelte/input/Slider.svelte +96 -0
- package/src/components/svelte/input/Submit.svelte +32 -0
- package/src/components/svelte/input/Switch.svelte +53 -0
- package/src/components/svelte/input/Tabs.svelte +188 -0
- package/src/components/svelte/layout/Card.svelte +17 -0
- package/src/components/svelte/layout/Column.svelte +15 -0
- package/src/components/svelte/layout/Grid.svelte +26 -0
- package/src/components/svelte/layout/Row.svelte +105 -0
- package/src/components/svelte/layout/balancedTiles.ts +85 -0
- package/src/components/svelte/tooling/CodeMirror.svelte +91 -0
- package/src/components/svelte/tooling/Playground.svelte +765 -0
- package/src/components/svelte/tooling/PlaygroundMarkdown.svelte +26 -0
- package/src/components/svelte/tooling/PlaygroundSlexCode.svelte +76 -0
- package/src/components/svelte/types.ts +17 -0
- package/src/styles/animation.css +98 -0
- package/src/styles/components/button.css +114 -0
- package/src/styles/components/choice.css +165 -0
- package/src/styles/components/select.css +260 -0
- package/src/styles/components/slider.css +125 -0
- package/src/styles/components/submit.css +9 -0
- package/src/styles/components/switch.css +114 -0
- package/src/styles/components/tabs.css +192 -0
- package/src/styles/components/text-input.css +245 -0
- package/src/styles/content.css +474 -0
- package/src/styles/disclosure.css +162 -0
- package/src/styles/display.css +259 -0
- package/src/styles/entry.css +34 -0
- package/src/styles/feedback.css +219 -0
- package/src/styles/input.css +8 -0
- package/src/styles/layout.css +365 -0
- package/src/styles/theme.css +31 -0
- package/src/styles/tooling.css +1009 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import type { RenderContext } from "../../engine/types";
|
|
2
|
+
|
|
3
|
+
type Item = {
|
|
4
|
+
label?: string;
|
|
5
|
+
value?: string;
|
|
6
|
+
content?: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function formatDisplayNumber(value: number): string {
|
|
13
|
+
if (!Number.isFinite(value)) return String(value);
|
|
14
|
+
if (Object.is(value, -0)) return "0";
|
|
15
|
+
|
|
16
|
+
const rounded = Number(value.toPrecision(15));
|
|
17
|
+
if (Object.is(rounded, -0)) return "0";
|
|
18
|
+
|
|
19
|
+
return String(rounded);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function text(value: unknown, fallback = ""): string {
|
|
23
|
+
if (value === undefined || value === null) return fallback;
|
|
24
|
+
if (typeof value === "number") return formatDisplayNumber(value);
|
|
25
|
+
return String(value);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function label(ctx: RenderContext, key: string, value: unknown, fallback = ""): string {
|
|
29
|
+
return text(value ?? ctx.labels[key], fallback);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function bool(value: unknown): boolean {
|
|
33
|
+
return value === true || value === "true";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function list(value: unknown): Item[] {
|
|
37
|
+
return Array.isArray(value) ? (value as Item[]) : [];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function rows(value: unknown): unknown[] {
|
|
41
|
+
return Array.isArray(value) ? value : [];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function objects(value: unknown): Record<string, unknown>[] {
|
|
45
|
+
return Array.isArray(value)
|
|
46
|
+
? value.filter((item): item is Record<string, unknown> => !!item && typeof item === "object")
|
|
47
|
+
: [];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function itemLabel(item: unknown): string {
|
|
51
|
+
if (item && typeof item === "object") {
|
|
52
|
+
const record = item as Record<string, unknown>;
|
|
53
|
+
return text(record.label ?? record.title ?? record.value ?? record.id);
|
|
54
|
+
}
|
|
55
|
+
return text(item);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function itemDescription(item: unknown): string {
|
|
59
|
+
return item && typeof item === "object" ? text((item as Record<string, unknown>).description) : "";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function readColumns(value: unknown): string[] {
|
|
63
|
+
if (!Array.isArray(value)) return [];
|
|
64
|
+
return value.map((item) => {
|
|
65
|
+
if (typeof item === "string") return item;
|
|
66
|
+
if (item && typeof item === "object") return text((item as Record<string, unknown>).key);
|
|
67
|
+
return "";
|
|
68
|
+
}).filter(Boolean);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function readColumnLabel(column: unknown, fallback: string): string {
|
|
72
|
+
if (column && typeof column === "object") {
|
|
73
|
+
const record = column as Record<string, unknown>;
|
|
74
|
+
return text(record.label ?? record.key ?? fallback);
|
|
75
|
+
}
|
|
76
|
+
return text(column, fallback);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function readCell(row: unknown, column: string): string {
|
|
80
|
+
if (row && typeof row === "object") return text((row as Record<string, unknown>)[column]);
|
|
81
|
+
return text(row);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function catalogItems(value: unknown): Record<string, unknown>[] {
|
|
85
|
+
return objects(value).filter((item) => text(item.id ?? item.slug));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function catalogGroups(value: unknown): Array<{ label: string; items: Record<string, unknown>[] }> {
|
|
89
|
+
const groups: Array<{ label: string; items: Record<string, unknown>[] }> = [];
|
|
90
|
+
const byLabel = new Map<string, { label: string; items: Record<string, unknown>[] }>();
|
|
91
|
+
|
|
92
|
+
for (const item of catalogItems(value)) {
|
|
93
|
+
const label = text(item.category, "Component");
|
|
94
|
+
let group = byLabel.get(label);
|
|
95
|
+
if (!group) {
|
|
96
|
+
group = { label, items: [] };
|
|
97
|
+
groups.push(group);
|
|
98
|
+
byLabel.set(label, group);
|
|
99
|
+
}
|
|
100
|
+
group.items.push(item);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return groups;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function renderChildren(node: HTMLElement, ctx: RenderContext) {
|
|
107
|
+
if (ctx.children && Object.keys(ctx.children).length > 0) {
|
|
108
|
+
ctx.renderTree(ctx.children, node, ctx.forCtx);
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
destroy() {
|
|
112
|
+
node.replaceChildren();
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function emit(ctx: RenderContext, event: string, data?: unknown): void {
|
|
118
|
+
ctx.emit(event, data);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function formatScriptKey(key: string): string {
|
|
122
|
+
return /^[A-Za-z_$][\w$]*$/.test(key) ? key : JSON.stringify(key);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function formatScriptValue(value: unknown, indent = 0): string {
|
|
126
|
+
const pad = " ".repeat(indent);
|
|
127
|
+
const nextPad = " ".repeat(indent + 2);
|
|
128
|
+
if (value === null) return "null";
|
|
129
|
+
if (typeof value === "string") return JSON.stringify(value);
|
|
130
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
131
|
+
if (typeof value === "function") return value.toString();
|
|
132
|
+
if (Array.isArray(value)) {
|
|
133
|
+
if (value.length === 0) return "[]";
|
|
134
|
+
return `[\n${value.map((item) => `${nextPad}${formatScriptValue(item, indent + 2)}`).join(",\n")}\n${pad}]`;
|
|
135
|
+
}
|
|
136
|
+
if (value && typeof value === "object") {
|
|
137
|
+
const entries = Object.entries(value);
|
|
138
|
+
if (entries.length === 0) return "{}";
|
|
139
|
+
return `{\n${entries.map(([key, item]) => `${nextPad}${formatScriptKey(key)}: ${formatScriptValue(item, indent + 2)}`).join(",\n")}\n${pad}}`;
|
|
140
|
+
}
|
|
141
|
+
return "undefined";
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function stringifySource(source: unknown): string {
|
|
145
|
+
if (typeof source === "string") return source;
|
|
146
|
+
if (source && typeof source === "object") return formatScriptValue(source);
|
|
147
|
+
return '{\n slex: "0.1",\n namespace: "playground_empty",\n g: {},\n layout: {}\n}';
|
|
148
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { bindPropStore } from "../bindProps";
|
|
3
|
+
import { emit, label as runtimeLabel, text } from "../helpers";
|
|
4
|
+
import InlineIcon from "../InlineIcon.svelte";
|
|
5
|
+
import { resolveIconWeight } from "../../../icons/manager";
|
|
6
|
+
import type { PropValues, SvelteComponentProps } from "../types";
|
|
7
|
+
|
|
8
|
+
let { props, ctx, componentName }: SvelteComponentProps = $props();
|
|
9
|
+
let p = $state<PropValues>({});
|
|
10
|
+
$effect(() => bindPropStore(props, (next) => (p = next)));
|
|
11
|
+
|
|
12
|
+
function variant(): string {
|
|
13
|
+
const kind = text(p.type ?? p.variant ?? "primary");
|
|
14
|
+
if (kind === "destructive") return "danger";
|
|
15
|
+
if (kind === "default") return "primary";
|
|
16
|
+
return kind;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function selected(): boolean {
|
|
20
|
+
return resolveIconWeight({ selected: p.selected, active: p.active, pressed: p.pressed }) === "duotone";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function iconOnly(): boolean {
|
|
24
|
+
return !!(p.iconOnly || (p.icon && !p.label));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function label(): string {
|
|
28
|
+
return runtimeLabel(ctx, "button.label", p.label ?? p.title ?? p["aria-label"] ?? p.ariaLabel, componentName);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function click(event: MouseEvent): void {
|
|
32
|
+
if (p.disabled) return;
|
|
33
|
+
emit(ctx, "click", { type: "click", target: ctx.id, native: event });
|
|
34
|
+
}
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
{#if p.href}
|
|
38
|
+
<a
|
|
39
|
+
class={`slex-button slex-button--${variant()} ${iconOnly() ? "slex-button--icon" : ""} ${p.disabled ? "slex-button--disabled" : ""}`}
|
|
40
|
+
href={p.disabled ? undefined : text(p.href)}
|
|
41
|
+
target={text(p.target) || undefined}
|
|
42
|
+
rel={p.target === "_blank" ? "noreferrer" : undefined}
|
|
43
|
+
aria-disabled={p.disabled ? "true" : undefined}
|
|
44
|
+
data-selected={selected() ? "" : undefined}
|
|
45
|
+
aria-pressed={p.pressed === undefined ? undefined : selected()}
|
|
46
|
+
title={text(p.title ?? p.label)}
|
|
47
|
+
aria-label={text(p["aria-label"] ?? p.ariaLabel ?? p.title ?? p.label) || undefined}
|
|
48
|
+
>
|
|
49
|
+
{#if p.icon}
|
|
50
|
+
<InlineIcon name={p.icon} active={p.active} selected={p.selected} pressed={p.pressed} className="slex-button-icon" />
|
|
51
|
+
{/if}
|
|
52
|
+
{#if iconOnly()}
|
|
53
|
+
<span class="slex-sr-only">{label()}</span>
|
|
54
|
+
{:else}
|
|
55
|
+
{label()}
|
|
56
|
+
{/if}
|
|
57
|
+
</a>
|
|
58
|
+
{:else}
|
|
59
|
+
<button
|
|
60
|
+
type="button"
|
|
61
|
+
class={`slex-button slex-button--${variant()} ${iconOnly() ? "slex-button--icon" : ""}`}
|
|
62
|
+
disabled={!!p.disabled}
|
|
63
|
+
data-selected={selected() ? "" : undefined}
|
|
64
|
+
aria-pressed={p.pressed === undefined ? undefined : selected()}
|
|
65
|
+
title={text(p.title ?? p.label)}
|
|
66
|
+
aria-label={text(p["aria-label"] ?? p.ariaLabel ?? p.title ?? p.label) || undefined}
|
|
67
|
+
onclick={click}
|
|
68
|
+
>
|
|
69
|
+
{#if p.icon}
|
|
70
|
+
<InlineIcon name={p.icon} active={p.active} selected={p.selected} pressed={p.pressed} className="slex-button-icon" />
|
|
71
|
+
{/if}
|
|
72
|
+
{#if iconOnly()}
|
|
73
|
+
<span class="slex-sr-only">{label()}</span>
|
|
74
|
+
{:else}
|
|
75
|
+
{label()}
|
|
76
|
+
{/if}
|
|
77
|
+
</button>
|
|
78
|
+
{/if}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { bindPropStore } from "../bindProps";
|
|
3
|
+
import { emit, text } from "../helpers";
|
|
4
|
+
import InlineIcon from "../InlineIcon.svelte";
|
|
5
|
+
import type { PropValues, SvelteComponentProps } from "../types";
|
|
6
|
+
|
|
7
|
+
let { props, ctx }: SvelteComponentProps = $props();
|
|
8
|
+
let p = $state<PropValues>({});
|
|
9
|
+
let checked = $state(false);
|
|
10
|
+
let lastHapticAt = 0;
|
|
11
|
+
$effect(() => bindPropStore(props, (next) => {
|
|
12
|
+
p = next;
|
|
13
|
+
checked = !!(next.checked ?? next.value);
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
function toggle(event: Event): void {
|
|
17
|
+
if (p.disabled) return;
|
|
18
|
+
checked = (event.target as HTMLInputElement).checked;
|
|
19
|
+
emit(ctx, "change", checked);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function vibrate(duration = 8): void {
|
|
23
|
+
if (p.haptic === false || p.haptics === false || p.disabled) return;
|
|
24
|
+
const api = globalThis.navigator as (Navigator & { vibrate?: (pattern: number | number[]) => boolean }) | undefined;
|
|
25
|
+
api?.vibrate?.(duration);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function haptic(duration = 8): void {
|
|
29
|
+
const now = Date.now();
|
|
30
|
+
if (now - lastHapticAt < 80) return;
|
|
31
|
+
lastHapticAt = now;
|
|
32
|
+
vibrate(duration);
|
|
33
|
+
}
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<span class="slex-choice-event-layer" onpointerdown={() => haptic(8)} onclick={() => haptic(8)}>
|
|
37
|
+
<label class="slex-checkbox-field">
|
|
38
|
+
<input
|
|
39
|
+
type="checkbox"
|
|
40
|
+
class="slex-checkbox"
|
|
41
|
+
bind:checked
|
|
42
|
+
disabled={!!p.disabled}
|
|
43
|
+
data-state={checked ? "checked" : "unchecked"}
|
|
44
|
+
aria-label={text(p["aria-label"] ?? p.ariaLabel ?? p.label) || undefined}
|
|
45
|
+
onchange={toggle}
|
|
46
|
+
/>
|
|
47
|
+
<span class="slex-checkbox-label">
|
|
48
|
+
{#if p.icon}<InlineIcon name={p.icon} className="slex-checkbox-icon" />{/if}
|
|
49
|
+
<span>{text(p.label)}</span>
|
|
50
|
+
</span>
|
|
51
|
+
</label>
|
|
52
|
+
</span>
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
let nextInputId = 0;
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<script lang="ts">
|
|
6
|
+
import { bindPropStore } from "../bindProps";
|
|
7
|
+
import { bool, emit, text } from "../helpers";
|
|
8
|
+
import { parseEngineeringNumber } from "../../../engine/engineering";
|
|
9
|
+
import type { PropValues, SvelteComponentProps } from "../types";
|
|
10
|
+
|
|
11
|
+
const engineeringPrefixFactors: Record<string, number> = {
|
|
12
|
+
p: 1e-12,
|
|
13
|
+
n: 1e-9,
|
|
14
|
+
u: 1e-6,
|
|
15
|
+
"\u00b5": 1e-6,
|
|
16
|
+
"\u788c": 1e-6,
|
|
17
|
+
m: 1e-3,
|
|
18
|
+
k: 1e3,
|
|
19
|
+
K: 1e3,
|
|
20
|
+
M: 1e6,
|
|
21
|
+
meg: 1e6,
|
|
22
|
+
G: 1e9,
|
|
23
|
+
T: 1e12,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
let { componentName, props, ctx }: SvelteComponentProps = $props();
|
|
27
|
+
let p = $state<PropValues>({});
|
|
28
|
+
let value = $state("");
|
|
29
|
+
const fallbackId = `slex-input-${++nextInputId}`;
|
|
30
|
+
const labelText = $derived(text(p.label));
|
|
31
|
+
const unitText = $derived(text(p.unit));
|
|
32
|
+
const descriptionText = $derived(text(p.description ?? p.help ?? p.hint));
|
|
33
|
+
const errorText = $derived(text(p.error ?? p.errorMessage));
|
|
34
|
+
const disabled = $derived(bool(p.disabled));
|
|
35
|
+
const readonly = $derived(bool(p.readonly) || bool(p.readOnly));
|
|
36
|
+
const required = $derived(bool(p.required));
|
|
37
|
+
const invalid = $derived(bool(p.invalid) || !!errorText);
|
|
38
|
+
const steppable = $derived(isSteppableInput());
|
|
39
|
+
const controls = $derived(steppable && p.controls !== false && p.controls !== "false");
|
|
40
|
+
const componentId = $derived(safeId(componentName));
|
|
41
|
+
const inputId = $derived(text(p.id) || (componentId ? `slex-input-${componentId}` : fallbackId));
|
|
42
|
+
const descriptionId = $derived(`${inputId}-description`);
|
|
43
|
+
const errorId = $derived(`${inputId}-error`);
|
|
44
|
+
const explicitAriaLabel = $derived(text(p["aria-label"] ?? p.ariaLabel));
|
|
45
|
+
const computedAriaLabel = $derived(explicitAriaLabel || (labelText ? "" : text(p.placeholder)));
|
|
46
|
+
const describedBy = $derived([descriptionText ? descriptionId : "", errorText ? errorId : ""].filter(Boolean).join(" "));
|
|
47
|
+
const numericValue = $derived(readNumericValue());
|
|
48
|
+
const controlLabel = $derived(labelText || text(p.placeholder) || componentName || "input");
|
|
49
|
+
const decrementDisabled = $derived(!canStep(-1));
|
|
50
|
+
const incrementDisabled = $derived(!canStep(1));
|
|
51
|
+
$effect(() => bindPropStore(props, (next) => {
|
|
52
|
+
p = next;
|
|
53
|
+
value = text(next.value);
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
function safeId(value: string): string {
|
|
57
|
+
return value.trim().replace(/[^\w-]+/g, "-");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function inputType(): string {
|
|
61
|
+
return text(p.type, "text") === "engineering" ? "text" : text(p.type, "text");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function isSteppableInput(): boolean {
|
|
65
|
+
const kind = text(p.type, "text");
|
|
66
|
+
return kind === "number" ||
|
|
67
|
+
kind === "engineering" ||
|
|
68
|
+
p.min !== undefined ||
|
|
69
|
+
p.max !== undefined ||
|
|
70
|
+
p.step !== undefined;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function numericProp(input: unknown): number | undefined {
|
|
74
|
+
if (input === undefined || input === null || input === "") return undefined;
|
|
75
|
+
const next = Number(input);
|
|
76
|
+
return Number.isFinite(next) ? next : undefined;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function stepSize(): number {
|
|
80
|
+
const next = numericProp(p.step);
|
|
81
|
+
if (next !== undefined && next > 0) return next;
|
|
82
|
+
if (text(p.type, "text") !== "engineering") return 1;
|
|
83
|
+
const parsed = parseEngineeringNumber(value);
|
|
84
|
+
if (!parsed.valid || !parsed.prefix) return 1;
|
|
85
|
+
return engineeringPrefixFactors[parsed.prefix] ?? 1;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function readNumericValue(): number | null {
|
|
89
|
+
if (text(p.type, "text") === "engineering") {
|
|
90
|
+
const parsed = parseEngineeringNumber(value);
|
|
91
|
+
return parsed.valid && parsed.number !== null ? parsed.number : null;
|
|
92
|
+
}
|
|
93
|
+
const next = Number(value);
|
|
94
|
+
return Number.isFinite(next) ? next : null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function clamp(next: number): number {
|
|
98
|
+
const min = numericProp(p.min);
|
|
99
|
+
const max = numericProp(p.max);
|
|
100
|
+
let clamped = next;
|
|
101
|
+
if (min !== undefined) clamped = Math.max(min, clamped);
|
|
102
|
+
if (max !== undefined) clamped = Math.min(max, clamped);
|
|
103
|
+
return clamped;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function canStep(direction: -1 | 1): boolean {
|
|
107
|
+
if (!controls || disabled || readonly || numericValue === null) return false;
|
|
108
|
+
return clamp(numericValue + direction * stepSize()) !== numericValue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function formatSteppedValue(next: number): string {
|
|
112
|
+
if (text(p.type, "text") !== "engineering") return text(next);
|
|
113
|
+
const parsed = parseEngineeringNumber(value);
|
|
114
|
+
if (!parsed.valid) return text(next);
|
|
115
|
+
const factor = parsed.prefix ? engineeringPrefixFactors[parsed.prefix] : undefined;
|
|
116
|
+
const visibleNumber = factor ? next / factor : next;
|
|
117
|
+
return `${formatNumber(visibleNumber)}${parsed.prefix}${parsed.unit}`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function formatNumber(next: number): string {
|
|
121
|
+
return text(Number(next.toPrecision(12)));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function emitValue(nextValue: string): void {
|
|
125
|
+
value = nextValue;
|
|
126
|
+
emit(ctx, "change", text(p.type, "text") === "engineering" ? parseEngineeringNumber(value) : value);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function stepBy(direction: -1 | 1): void {
|
|
130
|
+
if (!canStep(direction) || numericValue === null) return;
|
|
131
|
+
emitValue(formatSteppedValue(clamp(numericValue + direction * stepSize())));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function update(event: Event): void {
|
|
135
|
+
if (disabled || readonly) return;
|
|
136
|
+
value = (event.target as HTMLInputElement).value;
|
|
137
|
+
emit(ctx, "change", text(p.type, "text") === "engineering" ? parseEngineeringNumber(value) : value);
|
|
138
|
+
}
|
|
139
|
+
</script>
|
|
140
|
+
|
|
141
|
+
<div
|
|
142
|
+
class="slex-input-field"
|
|
143
|
+
data-invalid={invalid ? "true" : undefined}
|
|
144
|
+
data-required={required ? "true" : undefined}
|
|
145
|
+
data-readonly={readonly ? "true" : undefined}
|
|
146
|
+
>
|
|
147
|
+
{#if labelText}
|
|
148
|
+
<label class="slex-input-label" for={inputId}>{labelText}</label>
|
|
149
|
+
{/if}
|
|
150
|
+
<div
|
|
151
|
+
class="slex-input-control"
|
|
152
|
+
data-has-unit={unitText ? "true" : undefined}
|
|
153
|
+
data-has-controls={controls ? "true" : undefined}
|
|
154
|
+
>
|
|
155
|
+
<input
|
|
156
|
+
id={inputId}
|
|
157
|
+
class="slex-input"
|
|
158
|
+
type={inputType()}
|
|
159
|
+
inputmode={text(p.type, "text") === "engineering" ? "decimal" : undefined}
|
|
160
|
+
bind:value
|
|
161
|
+
name={text(p.name) || undefined}
|
|
162
|
+
placeholder={text(p.placeholder)}
|
|
163
|
+
disabled={disabled}
|
|
164
|
+
readonly={readonly}
|
|
165
|
+
required={required}
|
|
166
|
+
min={p.min === undefined ? undefined : text(p.min)}
|
|
167
|
+
max={p.max === undefined ? undefined : text(p.max)}
|
|
168
|
+
step={p.step === undefined ? undefined : text(p.step)}
|
|
169
|
+
aria-label={computedAriaLabel || undefined}
|
|
170
|
+
aria-describedby={describedBy || undefined}
|
|
171
|
+
aria-invalid={invalid ? "true" : undefined}
|
|
172
|
+
oninput={update}
|
|
173
|
+
/>
|
|
174
|
+
{#if unitText}
|
|
175
|
+
<span class="slex-input-unit" aria-hidden="true">{unitText}</span>
|
|
176
|
+
{/if}
|
|
177
|
+
{#if controls}
|
|
178
|
+
<span class="slex-input-controls">
|
|
179
|
+
<button
|
|
180
|
+
class="slex-input-step"
|
|
181
|
+
type="button"
|
|
182
|
+
aria-label={`Decrease ${controlLabel}`}
|
|
183
|
+
disabled={decrementDisabled}
|
|
184
|
+
onclick={() => stepBy(-1)}
|
|
185
|
+
>-</button>
|
|
186
|
+
<button
|
|
187
|
+
class="slex-input-step"
|
|
188
|
+
type="button"
|
|
189
|
+
aria-label={`Increase ${controlLabel}`}
|
|
190
|
+
disabled={incrementDisabled}
|
|
191
|
+
onclick={() => stepBy(1)}
|
|
192
|
+
>+</button>
|
|
193
|
+
</span>
|
|
194
|
+
{/if}
|
|
195
|
+
</div>
|
|
196
|
+
{#if descriptionText}
|
|
197
|
+
<div id={descriptionId} class="slex-input-description">{descriptionText}</div>
|
|
198
|
+
{/if}
|
|
199
|
+
{#if errorText}
|
|
200
|
+
<div id={errorId} class="slex-input-error" role="alert">{errorText}</div>
|
|
201
|
+
{/if}
|
|
202
|
+
</div>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { bindPropStore } from "../bindProps";
|
|
3
|
+
import { emit, list, text } from "../helpers";
|
|
4
|
+
import InlineIcon from "../InlineIcon.svelte";
|
|
5
|
+
import type { PropValues, SvelteComponentProps } from "../types";
|
|
6
|
+
|
|
7
|
+
let { props, ctx }: SvelteComponentProps = $props();
|
|
8
|
+
let p = $state<PropValues>({});
|
|
9
|
+
let value = $state<unknown>(undefined);
|
|
10
|
+
const lastHapticAt = new Map<string, number>();
|
|
11
|
+
$effect(() => bindPropStore(props, (next) => {
|
|
12
|
+
p = next;
|
|
13
|
+
value = next.value;
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
function choose(next: unknown): void {
|
|
17
|
+
value = next;
|
|
18
|
+
emit(ctx, "change", next);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function vibrate(disabled: boolean, duration = 8): void {
|
|
22
|
+
if (disabled || p.haptic === false || p.haptics === false) return;
|
|
23
|
+
const api = globalThis.navigator as (Navigator & { vibrate?: (pattern: number | number[]) => boolean }) | undefined;
|
|
24
|
+
api?.vibrate?.(duration);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function haptic(key: string, disabled: boolean, duration = 8): void {
|
|
28
|
+
const now = Date.now();
|
|
29
|
+
if (now - (lastHapticAt.get(key) ?? 0) < 80) return;
|
|
30
|
+
lastHapticAt.set(key, now);
|
|
31
|
+
vibrate(disabled, duration);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function groupName(): string {
|
|
35
|
+
return text(p.name ?? ctx.id, ctx.id);
|
|
36
|
+
}
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<div class="slex-radio-group" data-scope="radio-group" data-part="root" data-orientation={text(p.orientation, "vertical")} role="radiogroup" aria-label={text(p["aria-label"] ?? p.ariaLabel ?? p.label) || undefined}>
|
|
40
|
+
{#if p.label || p.icon}
|
|
41
|
+
<div class="slex-radio-group-label">
|
|
42
|
+
{#if p.icon}<InlineIcon name={p.icon} className="slex-radio-group-icon" />{/if}
|
|
43
|
+
{#if p.label}<span>{text(p.label)}</span>{/if}
|
|
44
|
+
</div>
|
|
45
|
+
{/if}
|
|
46
|
+
<div class="slex-radio-group-list">
|
|
47
|
+
{#each list(p.options) as item}
|
|
48
|
+
{@const itemValue = item.value ?? item.label}
|
|
49
|
+
{@const disabled = !!item.disabled || !!p.disabled}
|
|
50
|
+
{@const hapticKey = text(itemValue)}
|
|
51
|
+
<span class="slex-choice-event-layer" onpointerdown={() => haptic(hapticKey, disabled, 8)} onclick={() => haptic(hapticKey, disabled, 8)}>
|
|
52
|
+
<label class="slex-radio-field">
|
|
53
|
+
<input
|
|
54
|
+
type="radio"
|
|
55
|
+
class="slex-radio"
|
|
56
|
+
name={groupName()}
|
|
57
|
+
value={text(itemValue)}
|
|
58
|
+
checked={itemValue === value}
|
|
59
|
+
disabled={disabled}
|
|
60
|
+
data-state={itemValue === value ? "checked" : "unchecked"}
|
|
61
|
+
onchange={() => choose(itemValue)}
|
|
62
|
+
/>
|
|
63
|
+
<span class="slex-radio-label">
|
|
64
|
+
{#if item.icon}<InlineIcon name={item.icon} selected={itemValue === value} className="slex-radio-icon" />{/if}
|
|
65
|
+
<span>{text(item.label)}</span>
|
|
66
|
+
</span>
|
|
67
|
+
</label>
|
|
68
|
+
</span>
|
|
69
|
+
{/each}
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|