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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "slexkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Zero-build, Markdown-friendly reactive UI runtime for AI output.",
|
|
5
5
|
"author": "SlexKit contributors",
|
|
6
6
|
"type": "module",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"module": "./dist/slexkit.js",
|
|
17
17
|
"types": "./dist/types/index.d.ts",
|
|
18
18
|
"bin": {
|
|
19
|
-
"slex": "
|
|
19
|
+
"slex": "scripts/cli.mjs"
|
|
20
20
|
},
|
|
21
21
|
"workspaces": [
|
|
22
22
|
"packages/*"
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
],
|
|
82
82
|
"scripts": {
|
|
83
83
|
"dev": "bun run site/server.ts",
|
|
84
|
-
"build": "bun run build:core && bun run --filter @slexkit/streamdown build && bun run --filter @slexkit/
|
|
84
|
+
"build": "bun run build:core && bun run --filter @slexkit/streamdown build && bun run --filter @slexkit/mcp build",
|
|
85
85
|
"build:core": "bun run scripts/build-core.ts",
|
|
86
86
|
"ai:docs": "bun run scripts/generate-ai-docs.ts",
|
|
87
87
|
"version:sync": "bun run scripts/sync-version.ts",
|
|
@@ -92,6 +92,7 @@
|
|
|
92
92
|
"copy-runtime": "node scripts/cli.mjs copy-runtime",
|
|
93
93
|
"preview": "bun run site/server.ts",
|
|
94
94
|
"smoke:release": "node scripts/release-smoke.mjs",
|
|
95
|
+
"smoke:registry": "node scripts/registry-smoke.mjs",
|
|
95
96
|
"test": "bun test --conditions browser --isolate --preload ./tests/setup.ts tests packages",
|
|
96
97
|
"test:watch": "bun test --conditions browser --isolate --watch --preload ./tests/setup.ts tests packages",
|
|
97
98
|
"lint": "eslint src/",
|
|
@@ -119,6 +120,7 @@
|
|
|
119
120
|
"flowbite": "^4.0.2",
|
|
120
121
|
"flowbite-svelte": "^1.33.1",
|
|
121
122
|
"katex": "^0.17.0",
|
|
123
|
+
"marked": "^18.0.5",
|
|
122
124
|
"svelte": "^5.55.9",
|
|
123
125
|
"svelte-highlight": "^7.9.0"
|
|
124
126
|
},
|
|
@@ -21,7 +21,7 @@ Use this skill as `/host` when adding SlexKit to a host application or documenta
|
|
|
21
21
|
|
|
22
22
|
- Vanilla host: `createSlexKitMarkdownRuntimeHost`.
|
|
23
23
|
- React/Streamdown: `@slexkit/streamdown`.
|
|
24
|
-
- Obsidian:
|
|
24
|
+
- Obsidian: install the official SlexKit plugin from Obsidian Community Plugins; release repo `slexkit/obsidian-slexkit`.
|
|
25
25
|
- Custom host: detect the `slex` code fence, pass source plus artifact id to the runtime host, dispose when the block is removed.
|
|
26
26
|
|
|
27
27
|
## Rules
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import katex from "katex";
|
|
3
|
+
import { bindPropStore } from "../bindProps";
|
|
4
|
+
import { bool, text } from "../helpers";
|
|
5
|
+
import type { PropValues, SvelteComponentProps } from "../types";
|
|
6
|
+
|
|
7
|
+
let { props }: SvelteComponentProps = $props();
|
|
8
|
+
let p = $state<PropValues>({});
|
|
9
|
+
$effect(() => bindPropStore(props, (next) => (p = next)));
|
|
10
|
+
|
|
11
|
+
const tex = $derived(text(p.tex ?? p.formula ?? p.value));
|
|
12
|
+
const displayMode = $derived(p.displayMode === undefined && p.display === undefined && p.block === undefined
|
|
13
|
+
? true
|
|
14
|
+
: bool(p.displayMode ?? p.display ?? p.block));
|
|
15
|
+
const rendered = $derived(renderFormula(tex, displayMode));
|
|
16
|
+
|
|
17
|
+
function renderFormula(source: string, display: boolean): string {
|
|
18
|
+
return katex.renderToString(source || "\\,", {
|
|
19
|
+
displayMode: display,
|
|
20
|
+
throwOnError: false,
|
|
21
|
+
strict: "ignore",
|
|
22
|
+
output: "htmlAndMathml",
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<div class="slex-formula" data-display={displayMode ? "block" : "inline"}>{@html rendered}</div>
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
{#each rows(p.rows ?? p.items) as row}
|
|
32
32
|
<tr>
|
|
33
33
|
{#if readColumns(p.columns).length}
|
|
34
|
-
{#each readColumns(p.columns) as column}<td>{readCell(row, column)}</td>{/each}
|
|
34
|
+
{#each readColumns(p.columns) as column, index}<td>{readCell(row, column, index)}</td>{/each}
|
|
35
35
|
{:else if Array.isArray(row)}
|
|
36
36
|
{#each row as cell}<td>{text(cell)}</td>{/each}
|
|
37
37
|
{:else}
|
|
@@ -6,6 +6,19 @@
|
|
|
6
6
|
let { props }: SvelteComponentProps = $props();
|
|
7
7
|
let p = $state<PropValues>({});
|
|
8
8
|
$effect(() => bindPropStore(props, (next) => (p = next)));
|
|
9
|
+
|
|
10
|
+
const color = $derived(p.color == null || p.color === "" ? undefined : text(p.color));
|
|
11
|
+
const size = $derived(cssLength(p.size));
|
|
12
|
+
|
|
13
|
+
function cssLength(value: unknown): string | undefined {
|
|
14
|
+
if (value == null || value === "") return undefined;
|
|
15
|
+
const rendered = text(value);
|
|
16
|
+
return /^-?\d+(\.\d+)?$/.test(rendered) ? `${rendered}px` : rendered;
|
|
17
|
+
}
|
|
9
18
|
</script>
|
|
10
19
|
|
|
11
|
-
<div
|
|
20
|
+
<div
|
|
21
|
+
class={`slex-text${p.variant ? ` slex-text--${text(p.variant)}` : ""}${p.class ? ` ${text(p.class)}` : ""}`}
|
|
22
|
+
style:color={color}
|
|
23
|
+
style:font-size={size}
|
|
24
|
+
>{text(p.content ?? p.text ?? p.label)}</div>
|
|
@@ -76,7 +76,8 @@ export function readColumnLabel(column: unknown, fallback: string): string {
|
|
|
76
76
|
return text(column, fallback);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
export function readCell(row: unknown, column: string): string {
|
|
79
|
+
export function readCell(row: unknown, column: string, index?: number): string {
|
|
80
|
+
if (Array.isArray(row)) return text(typeof index === "number" ? row[index] : undefined);
|
|
80
81
|
if (row && typeof row === "object") return text((row as Record<string, unknown>)[column]);
|
|
81
82
|
return text(row);
|
|
82
83
|
}
|
|
@@ -105,6 +106,7 @@ export function catalogGroups(value: unknown): Array<{ label: string; items: Rec
|
|
|
105
106
|
|
|
106
107
|
export function renderChildren(node: HTMLElement, ctx: RenderContext) {
|
|
107
108
|
if (ctx.children && Object.keys(ctx.children).length > 0) {
|
|
109
|
+
node.replaceChildren();
|
|
108
110
|
ctx.renderTree(ctx.children, node, ctx.forCtx);
|
|
109
111
|
}
|
|
110
112
|
return {
|
|
@@ -118,6 +120,59 @@ export function emit(ctx: RenderContext, event: string, data?: unknown): void {
|
|
|
118
120
|
ctx.emit(event, data);
|
|
119
121
|
}
|
|
120
122
|
|
|
123
|
+
export type ScheduledFrame = {
|
|
124
|
+
kind: "api" | "native" | "microtask";
|
|
125
|
+
id?: unknown;
|
|
126
|
+
canceled: boolean;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
export function scheduleFrame(ctx: RenderContext, fn: (time?: number) => void): ScheduledFrame {
|
|
130
|
+
const handle: ScheduledFrame = { kind: "microtask", canceled: false };
|
|
131
|
+
const run = (time?: number) => {
|
|
132
|
+
if (!handle.canceled) fn(time);
|
|
133
|
+
};
|
|
134
|
+
const apiRaf = ctx.api?.raf;
|
|
135
|
+
if (typeof apiRaf === "function") {
|
|
136
|
+
try {
|
|
137
|
+
handle.kind = "api";
|
|
138
|
+
handle.id = apiRaf(run);
|
|
139
|
+
return handle;
|
|
140
|
+
} catch {
|
|
141
|
+
handle.kind = "microtask";
|
|
142
|
+
handle.id = undefined;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const ownerWindow = ctx.document.defaultView;
|
|
147
|
+
if (ownerWindow && typeof ownerWindow.requestAnimationFrame === "function") {
|
|
148
|
+
try {
|
|
149
|
+
handle.kind = "native";
|
|
150
|
+
handle.id = ownerWindow.requestAnimationFrame(run);
|
|
151
|
+
return handle;
|
|
152
|
+
} catch {
|
|
153
|
+
handle.kind = "microtask";
|
|
154
|
+
handle.id = undefined;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
queueMicrotask(() => run());
|
|
159
|
+
return handle;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function cancelScheduledFrame(ctx: RenderContext, handle: ScheduledFrame | undefined): void {
|
|
163
|
+
if (!handle || handle.canceled) return;
|
|
164
|
+
handle.canceled = true;
|
|
165
|
+
if (handle.kind === "api" && typeof ctx.api?.cancelRaf === "function") {
|
|
166
|
+
try {
|
|
167
|
+
ctx.api.cancelRaf(handle.id);
|
|
168
|
+
} catch {
|
|
169
|
+
// The runtime may already be disposed; cancellation is best effort.
|
|
170
|
+
}
|
|
171
|
+
} else if (handle.kind === "native" && typeof handle.id === "number") {
|
|
172
|
+
ctx.document.defaultView?.cancelAnimationFrame(handle.id);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
121
176
|
function formatScriptKey(key: string): string {
|
|
122
177
|
return /^[A-Za-z_$][\w$]*$/.test(key) ? key : JSON.stringify(key);
|
|
123
178
|
}
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
</script>
|
|
35
35
|
|
|
36
36
|
<span class="slex-choice-event-layer" onpointerdown={() => haptic(8)} onclick={() => haptic(8)}>
|
|
37
|
-
<label class="slex-checkbox-field">
|
|
37
|
+
<label class="slex-checkbox-field" data-disabled={p.disabled ? "true" : undefined}>
|
|
38
38
|
<input
|
|
39
39
|
type="checkbox"
|
|
40
40
|
class="slex-checkbox"
|
|
@@ -8,21 +8,6 @@
|
|
|
8
8
|
import { parseEngineeringNumber } from "../../../engine/engineering";
|
|
9
9
|
import type { PropValues, SvelteComponentProps } from "../types";
|
|
10
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
11
|
let { componentName, props, ctx }: SvelteComponentProps = $props();
|
|
27
12
|
let p = $state<PropValues>({});
|
|
28
13
|
let value = $state("");
|
|
@@ -35,8 +20,6 @@
|
|
|
35
20
|
const readonly = $derived(bool(p.readonly) || bool(p.readOnly));
|
|
36
21
|
const required = $derived(bool(p.required));
|
|
37
22
|
const invalid = $derived(bool(p.invalid) || !!errorText);
|
|
38
|
-
const steppable = $derived(isSteppableInput());
|
|
39
|
-
const controls = $derived(steppable && p.controls !== false && p.controls !== "false");
|
|
40
23
|
const componentId = $derived(safeId(componentName));
|
|
41
24
|
const inputId = $derived(text(p.id) || (componentId ? `slex-input-${componentId}` : fallbackId));
|
|
42
25
|
const descriptionId = $derived(`${inputId}-description`);
|
|
@@ -44,10 +27,6 @@
|
|
|
44
27
|
const explicitAriaLabel = $derived(text(p["aria-label"] ?? p.ariaLabel));
|
|
45
28
|
const computedAriaLabel = $derived(explicitAriaLabel || (labelText ? "" : text(p.placeholder)));
|
|
46
29
|
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
30
|
$effect(() => bindPropStore(props, (next) => {
|
|
52
31
|
p = next;
|
|
53
32
|
value = text(next.value);
|
|
@@ -61,76 +40,6 @@
|
|
|
61
40
|
return text(p.type, "text") === "engineering" ? "text" : text(p.type, "text");
|
|
62
41
|
}
|
|
63
42
|
|
|
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
43
|
function update(event: Event): void {
|
|
135
44
|
if (disabled || readonly) return;
|
|
136
45
|
value = (event.target as HTMLInputElement).value;
|
|
@@ -150,7 +59,6 @@
|
|
|
150
59
|
<div
|
|
151
60
|
class="slex-input-control"
|
|
152
61
|
data-has-unit={unitText ? "true" : undefined}
|
|
153
|
-
data-has-controls={controls ? "true" : undefined}
|
|
154
62
|
>
|
|
155
63
|
<input
|
|
156
64
|
id={inputId}
|
|
@@ -174,24 +82,6 @@
|
|
|
174
82
|
{#if unitText}
|
|
175
83
|
<span class="slex-input-unit" aria-hidden="true">{unitText}</span>
|
|
176
84
|
{/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
85
|
</div>
|
|
196
86
|
{#if descriptionText}
|
|
197
87
|
<div id={descriptionId} class="slex-input-description">{descriptionText}</div>
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
{@const disabled = !!item.disabled || !!p.disabled}
|
|
50
50
|
{@const hapticKey = text(itemValue)}
|
|
51
51
|
<span class="slex-choice-event-layer" onpointerdown={() => haptic(hapticKey, disabled, 8)} onclick={() => haptic(hapticKey, disabled, 8)}>
|
|
52
|
-
<label class="slex-radio-field">
|
|
52
|
+
<label class="slex-radio-field" data-disabled={disabled ? "true" : undefined}>
|
|
53
53
|
<input
|
|
54
54
|
type="radio"
|
|
55
55
|
class="slex-radio"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onMount } from "svelte";
|
|
3
3
|
import { bindPropStore } from "../bindProps";
|
|
4
|
-
import { emit, label, list, text } from "../helpers";
|
|
4
|
+
import { emit, label, list, scheduleFrame, text } from "../helpers";
|
|
5
5
|
import InlineIcon from "../InlineIcon.svelte";
|
|
6
6
|
import type { PropValues, SvelteComponentProps } from "../types";
|
|
7
7
|
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
function close(focusTrigger = true): void {
|
|
98
98
|
open = false;
|
|
99
99
|
activeIndex = selectedIndex();
|
|
100
|
-
if (focusTrigger)
|
|
100
|
+
if (focusTrigger) scheduleFrame(ctx, () => triggerEl?.focus());
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
function toggle(): void {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
let lastHapticAt = 0;
|
|
11
11
|
$effect(() => bindPropStore(props, (next) => {
|
|
12
12
|
p = next;
|
|
13
|
-
enabled = !!next.enabled;
|
|
13
|
+
enabled = !!(next.enabled ?? next.checked ?? next.value);
|
|
14
14
|
}));
|
|
15
15
|
|
|
16
16
|
function toggle(event: Event): void {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
</script>
|
|
35
35
|
|
|
36
36
|
<span class="slex-switch-event-layer" onpointerdown={() => haptic(8)} onclick={() => haptic(8)}>
|
|
37
|
-
<label class="slex-switch" data-state={enabled ? "on" : "off"}>
|
|
37
|
+
<label class="slex-switch" data-state={enabled ? "on" : "off"} data-disabled={p.disabled ? "true" : undefined}>
|
|
38
38
|
<input
|
|
39
39
|
type="checkbox"
|
|
40
40
|
role="switch"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import FlowbiteTabs from "../../../../node_modules/flowbite-svelte/dist/tabs/Tabs.svelte";
|
|
3
3
|
import FlowbiteTabItem from "../../../../node_modules/flowbite-svelte/dist/tabs/TabItem.svelte";
|
|
4
4
|
import { bindPropStore } from "../bindProps";
|
|
5
|
-
import { emit, list, text } from "../helpers";
|
|
5
|
+
import { cancelScheduledFrame, emit, list, scheduleFrame, text, type ScheduledFrame } from "../helpers";
|
|
6
6
|
import InlineIcon from "../InlineIcon.svelte";
|
|
7
7
|
import type { PropValues, SvelteComponentProps } from "../types";
|
|
8
8
|
|
|
@@ -55,14 +55,14 @@
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
function annotateTabs(node: HTMLElement) {
|
|
58
|
-
let
|
|
58
|
+
let frame: ScheduledFrame | undefined;
|
|
59
59
|
let resizeObserver: ResizeObserver | undefined;
|
|
60
60
|
let indicatorReady = false;
|
|
61
61
|
|
|
62
62
|
function scheduleIndicatorUpdate(list: HTMLElement, selectedTrigger: HTMLElement | undefined) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
cancelScheduledFrame(ctx, frame);
|
|
64
|
+
frame = scheduleFrame(ctx, () => {
|
|
65
|
+
frame = undefined;
|
|
66
66
|
if (!selectedTrigger) {
|
|
67
67
|
list.style.setProperty("--slex-tabs-indicator-opacity", "0");
|
|
68
68
|
return;
|
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
list.style.setProperty("--slex-tabs-indicator-opacity", "1");
|
|
97
97
|
if (!indicatorReady) {
|
|
98
98
|
indicatorReady = true;
|
|
99
|
-
|
|
99
|
+
scheduleFrame(ctx, () => {
|
|
100
100
|
list.dataset.indicatorReady = "true";
|
|
101
101
|
});
|
|
102
102
|
}
|
|
@@ -137,7 +137,7 @@
|
|
|
137
137
|
apply();
|
|
138
138
|
},
|
|
139
139
|
destroy() {
|
|
140
|
-
|
|
140
|
+
cancelScheduledFrame(ctx, frame);
|
|
141
141
|
resizeObserver?.disconnect();
|
|
142
142
|
},
|
|
143
143
|
};
|
|
@@ -15,10 +15,92 @@
|
|
|
15
15
|
inlineKatex: KatexRenderer,
|
|
16
16
|
blockKatex: KatexRenderer,
|
|
17
17
|
};
|
|
18
|
+
const options = { headerIds: false };
|
|
19
|
+
|
|
20
|
+
function stripFrontmatter(value: string) {
|
|
21
|
+
const raw = String(value ?? "").replace(/^\uFEFF/, "").replace(/\r\n/g, "\n");
|
|
22
|
+
if (!raw.startsWith("---")) return raw;
|
|
23
|
+
|
|
24
|
+
const end = raw.indexOf("\n---", 3);
|
|
25
|
+
if (end === -1) return raw;
|
|
26
|
+
|
|
27
|
+
const closeEnd = raw.indexOf("\n", end + 1);
|
|
28
|
+
return raw.slice(closeEnd === -1 ? raw.length : closeEnd + 1).trimStart();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function escapeHtml(value: string) {
|
|
32
|
+
return String(value)
|
|
33
|
+
.replace(/&/g, "&")
|
|
34
|
+
.replace(/</g, "<")
|
|
35
|
+
.replace(/>/g, ">")
|
|
36
|
+
.replace(/"/g, """);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function stripInlineMarkdown(value: string) {
|
|
40
|
+
return String(value)
|
|
41
|
+
.replace(/\s+\{#[A-Za-z0-9_-]+\}\s*$/, "")
|
|
42
|
+
.replace(/`([^`]+)`/g, "$1")
|
|
43
|
+
.replace(/\*\*([^*]+)\*\*/g, "$1")
|
|
44
|
+
.replace(/\*([^*]+)\*/g, "$1")
|
|
45
|
+
.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
|
|
46
|
+
.replace(/<[^>]+>/g, "")
|
|
47
|
+
.replace(/\s+#+\s*$/, "")
|
|
48
|
+
.trim();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function slugText(value: string) {
|
|
52
|
+
const slug = String(value)
|
|
53
|
+
.toLowerCase()
|
|
54
|
+
.replace(/[`"'\u2018\u2019\u201c\u201d]/g, "")
|
|
55
|
+
.replace(/[^a-z0-9\u4e00-\u9fa5]+/g, "-")
|
|
56
|
+
.replace(/^-+|-+$/g, "");
|
|
57
|
+
return slug || "section";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function createHeadingIdGenerator() {
|
|
61
|
+
const counts = new Map<string, number>();
|
|
62
|
+
return (rawTitle: string) => {
|
|
63
|
+
const explicit = rawTitle.match(/\s+\{#([A-Za-z0-9_-]+)\}\s*$/)?.[1] ?? "";
|
|
64
|
+
const base = explicit || slugText(stripInlineMarkdown(rawTitle));
|
|
65
|
+
const count = counts.get(base) ?? 0;
|
|
66
|
+
counts.set(base, count + 1);
|
|
67
|
+
return count ? `${base}-${count + 1}` : base;
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function normalizeHeadingAnchors(markdown: string) {
|
|
72
|
+
const nextId = createHeadingIdGenerator();
|
|
73
|
+
let fence: { char: string; length: number } | null = null;
|
|
74
|
+
|
|
75
|
+
return String(markdown ?? "")
|
|
76
|
+
.split(/\n/)
|
|
77
|
+
.map((line) => {
|
|
78
|
+
const marker = String(line).match(/^[ \t]{0,3}(`{3,}|~{3,})/);
|
|
79
|
+
if (fence) {
|
|
80
|
+
if (marker && marker[1][0] === fence.char && marker[1].length >= fence.length) fence = null;
|
|
81
|
+
return line;
|
|
82
|
+
}
|
|
83
|
+
if (marker) {
|
|
84
|
+
fence = { char: marker[1][0], length: marker[1].length };
|
|
85
|
+
return line;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const heading = String(line).match(/^(#{1,6})[ \t]+(.+)$/);
|
|
89
|
+
if (!heading) return line;
|
|
90
|
+
const rawTitle = heading[2].replace(/\s+#+\s*$/, "");
|
|
91
|
+
const renderedTitle = rawTitle.replace(/\s+\{#[A-Za-z0-9_-]+\}\s*$/, "").trim();
|
|
92
|
+
return `<span id="${escapeHtml(nextId(rawTitle))}" class="slex-doc-heading-anchor"></span>\n${heading[1]} ${renderedTitle}`;
|
|
93
|
+
})
|
|
94
|
+
.join("\n");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function previewMarkdown(value: string) {
|
|
98
|
+
return normalizeHeadingAnchors(stripFrontmatter(value));
|
|
99
|
+
}
|
|
18
100
|
</script>
|
|
19
101
|
|
|
20
|
-
<div class="slex-doc-streamdown">
|
|
21
|
-
<SvelteMarkdown source={content} {extensions} {renderers}>
|
|
102
|
+
<div class="slex-doc-prose slex-doc-streamdown">
|
|
103
|
+
<SvelteMarkdown source={previewMarkdown(content)} {extensions} {renderers} {options}>
|
|
22
104
|
{#snippet code({ lang, text })}
|
|
23
105
|
<PlaygroundSlexCode {lang} {text} {domain} />
|
|
24
106
|
{/snippet}
|