slexkit 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +4 -3
  3. package/README.zh-CN.md +4 -3
  4. package/dist/ai/llms-components.txt +2 -1
  5. package/dist/ai/llms-full.txt +85 -42
  6. package/dist/ai/llms-runtime.txt +11 -12
  7. package/dist/ai/llms.txt +1 -1
  8. package/dist/ai/slexkit-ai-manifest.json +77 -73
  9. package/dist/base.css +0 -46
  10. package/dist/components/checkbox.js +1 -0
  11. package/dist/components/choice.css +2 -2
  12. package/dist/components/display.css +1 -1
  13. package/dist/components/index.js +56 -153
  14. package/dist/components/input.css +53 -119
  15. package/dist/components/input.js +33 -143
  16. package/dist/components/radio-group.js +1 -0
  17. package/dist/components/select.css +0 -6
  18. package/dist/components/slider.css +27 -18
  19. package/dist/components/specs.js +2 -1
  20. package/dist/components/switch.css +3 -3
  21. package/dist/components/switch.js +1 -0
  22. package/dist/components/text-input.css +21 -90
  23. package/dist/components/text.js +12 -0
  24. package/dist/components/tooling.css +0 -1
  25. package/dist/runtime.cjs +67 -28
  26. package/dist/runtime.js +67 -28
  27. package/dist/slexkit.cjs +123 -181
  28. package/dist/slexkit.css +54 -167
  29. package/dist/slexkit.js +123 -181
  30. package/dist/types/engine/types.d.ts +1 -1
  31. package/dist/types/version.d.ts +2 -2
  32. package/dist/umd/slexkit.tooling.umd.js +122 -181
  33. package/dist/umd/slexkit.umd.js +123 -181
  34. package/package.json +3 -2
  35. package/skills/slexkit-host-integration/SKILL.md +1 -1
  36. package/src/components/svelte/display/Text.svelte +14 -1
  37. package/src/components/svelte/input/Checkbox.svelte +1 -1
  38. package/src/components/svelte/input/Input.svelte +0 -110
  39. package/src/components/svelte/input/RadioGroup.svelte +1 -1
  40. package/src/components/svelte/input/Switch.svelte +1 -1
  41. package/src/styles/components/choice.css +2 -2
  42. package/src/styles/components/select.css +0 -6
  43. package/src/styles/components/slider.css +27 -18
  44. package/src/styles/components/switch.css +3 -3
  45. package/src/styles/components/text-input.css +21 -90
  46. package/src/styles/display.css +1 -1
  47. package/src/styles/layout.css +0 -46
  48. package/src/styles/tooling.css +0 -1
@@ -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={`Increase ${controlLabel}`}
183
- disabled={incrementDisabled}
184
- onclick={() => stepBy(1)}
185
- >+</button>
186
- <button
187
- class="slex-input-step"
188
- type="button"
189
- aria-label={`Decrease ${controlLabel}`}
190
- disabled={decrementDisabled}
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"
@@ -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"
@@ -154,8 +154,8 @@
154
154
  transform: none;
155
155
  }
156
156
 
157
- .slex-checkbox-field:has(.slex-checkbox:disabled),
158
- .slex-radio-field:has(.slex-radio:disabled) {
157
+ .slex-checkbox-field[data-disabled="true"],
158
+ .slex-radio-field[data-disabled="true"] {
159
159
  cursor: not-allowed;
160
160
  opacity: 0.65;
161
161
  }
@@ -110,7 +110,6 @@
110
110
  border: 0;
111
111
  overflow: hidden;
112
112
  clip: rect(0 0 0 0);
113
- clip-path: inset(50%);
114
113
  white-space: nowrap;
115
114
  }
116
115
 
@@ -169,11 +168,6 @@
169
168
  color: var(--accent-foreground);
170
169
  }
171
170
 
172
- .slex-select-menu:has(.slex-select-option:hover) .slex-select-option--active:not(:hover) {
173
- background: transparent;
174
- color: var(--popover-foreground, var(--foreground));
175
- }
176
-
177
171
  .slex-select-option--selected {
178
172
  font-weight: 500;
179
173
  }
@@ -33,53 +33,62 @@
33
33
  .slex-slider {
34
34
  box-sizing: border-box;
35
35
  width: 100%;
36
- height: 0.5rem;
36
+ height: 1rem;
37
+ padding: 0;
38
+ border: 0;
37
39
  border-radius: 999px;
38
40
  -webkit-appearance: none;
39
41
  appearance: none;
40
- background: linear-gradient(
41
- to right,
42
- var(--primary) 0%,
43
- var(--primary) var(--slex-slider-progress, 0%),
44
- var(--secondary) var(--slex-slider-progress, 0%),
45
- var(--secondary) 100%
46
- );
42
+ background: transparent;
47
43
  accent-color: var(--primary);
48
44
  cursor: pointer;
45
+ overflow: visible;
49
46
  transition: box-shadow 150ms ease, filter 150ms ease;
50
47
  }
51
48
 
52
- .slex-slider:hover {
53
- box-shadow: 0 0 0 4px color-mix(in oklab, var(--primary) 8%, transparent);
54
- }
55
-
56
- .slex-slider:active {
57
- box-shadow: 0 0 0 5px color-mix(in oklab, var(--primary) 12%, transparent);
58
- }
59
-
60
49
  .slex-slider:focus-visible {
61
50
  outline: 2px solid var(--ring);
62
51
  outline-offset: 4px;
63
52
  }
64
53
 
65
54
  .slex-slider::-webkit-slider-thumb {
55
+ box-sizing: border-box;
66
56
  width: 1rem;
67
57
  height: 1rem;
58
+ margin-top: -0.25rem;
68
59
  border: 2px solid var(--primary);
69
60
  border-radius: 999px;
70
- background: var(--background) !important;
61
+ background-color: var(--background);
62
+ background-clip: padding-box;
71
63
  box-shadow: 0 1px 2px color-mix(in oklab, var(--foreground) 14%, transparent);
72
64
  -webkit-appearance: none;
73
65
  appearance: none;
74
66
  transition: border-color 150ms ease, background-color 150ms ease, box-shadow 150ms ease, transform 120ms ease;
75
67
  }
76
68
 
69
+ .slex-slider::-webkit-slider-runnable-track {
70
+ box-sizing: border-box;
71
+ width: 100%;
72
+ height: 0.5rem;
73
+ border: 0;
74
+ border-radius: 999px;
75
+ background: linear-gradient(
76
+ to right,
77
+ var(--primary) 0%,
78
+ var(--primary) var(--slex-slider-progress, 0%),
79
+ var(--secondary) var(--slex-slider-progress, 0%),
80
+ var(--secondary) 100%
81
+ );
82
+ }
83
+
77
84
  .slex-slider::-moz-range-thumb {
85
+ box-sizing: border-box;
78
86
  width: 1rem;
79
87
  height: 1rem;
80
88
  border: 2px solid var(--primary);
81
89
  border-radius: 999px;
82
- background: var(--background) !important;
90
+ background-color: var(--background);
91
+ background-clip: padding-box;
83
92
  box-shadow: 0 1px 2px color-mix(in oklab, var(--foreground) 14%, transparent);
84
93
  transition: border-color 150ms ease, background-color 150ms ease, box-shadow 150ms ease, transform 120ms ease;
85
94
  }
@@ -104,11 +104,11 @@
104
104
  opacity: 0.55;
105
105
  }
106
106
 
107
- .slex-switch:has(.slex-switch-input:disabled) {
107
+ .slex-switch[data-disabled="true"] {
108
108
  cursor: not-allowed;
109
109
  }
110
110
 
111
- .slex-switch:has(.slex-switch-input:disabled):hover .slex-switch-control,
112
- .slex-switch:has(.slex-switch-input:disabled):hover .slex-switch-control::after {
111
+ .slex-switch[data-disabled="true"]:hover .slex-switch-control,
112
+ .slex-switch[data-disabled="true"]:hover .slex-switch-control::after {
113
113
  box-shadow: none;
114
114
  }
@@ -45,16 +45,18 @@
45
45
  border: 1px solid var(--input);
46
46
  border-radius: var(--radius);
47
47
  background: var(--background);
48
+ background-clip: padding-box;
48
49
  color: var(--foreground);
49
50
  font-family: inherit;
50
51
  font-size: 0.875rem;
51
52
  line-height: 1.5;
52
53
  outline: none;
54
+ -webkit-appearance: none;
55
+ appearance: none;
53
56
  transition: border-color 150ms ease, box-shadow 150ms ease;
54
57
  }
55
58
 
56
- .slex-input-control[data-has-unit="true"] .slex-input,
57
- .slex-input-control[data-has-controls="true"] .slex-input {
59
+ .slex-input-control[data-has-unit="true"] .slex-input {
58
60
  border-top-right-radius: 0;
59
61
  border-bottom-right-radius: 0;
60
62
  }
@@ -78,79 +80,16 @@
78
80
  transition: border-color 150ms ease, box-shadow 150ms ease;
79
81
  }
80
82
 
81
- .slex-input-control[data-has-controls="true"] .slex-input-unit {
82
- border-radius: 0;
83
- }
84
-
85
- .slex-input-controls {
86
- box-sizing: border-box;
87
- display: inline-grid;
88
- grid-template-rows: repeat(2, minmax(0, 1fr));
89
- align-items: stretch;
90
- flex: 0 0 auto;
91
- width: 1.875rem;
92
- min-width: 1.875rem;
93
- min-height: 2.5625rem;
94
- overflow: hidden;
95
- border: 1px solid var(--input);
96
- border-left: 0;
97
- border-radius: 0 var(--radius) var(--radius) 0;
98
- background: var(--background);
99
- transition: border-color 150ms ease, box-shadow 150ms ease;
100
- }
101
-
102
- .slex-input-step {
103
- box-sizing: border-box;
104
- display: inline-flex;
105
- align-items: center;
106
- justify-content: center;
107
- width: 100%;
108
- min-width: 0;
109
- min-height: 0;
110
- padding: 0;
111
- border: 0;
112
- border-top: 1px solid var(--input);
113
- border-radius: 0;
114
- background: transparent;
115
- color: var(--foreground);
116
- font: inherit;
117
- font-size: 0.75rem;
118
- font-weight: 600;
119
- line-height: 1;
120
- cursor: pointer;
121
- transition: border-color 150ms ease, background 150ms ease, box-shadow 150ms ease;
122
- }
123
-
124
- .slex-input-step:first-child {
125
- border-top: 0;
126
- }
127
-
128
- .slex-input-step:last-child {
129
- border-radius: 0;
130
- }
131
-
132
- .slex-input-step:hover:not(:disabled) {
133
- background: color-mix(in oklab, var(--muted) 52%, var(--background));
134
- }
135
-
136
- .slex-input-step:focus-visible {
137
- z-index: 1;
138
- outline: none;
139
- background: color-mix(in oklab, var(--muted) 58%, var(--background));
140
- box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--ring) 34%, transparent);
141
- }
142
-
143
- .slex-input-step:disabled {
144
- opacity: 0.45;
145
- cursor: not-allowed;
146
- }
147
-
148
83
  .slex-input::placeholder {
149
84
  color: var(--muted-foreground);
150
85
  }
151
86
 
152
87
  .slex-input:focus {
153
88
  border-color: var(--ring);
89
+ box-shadow: 0 0 0 2px color-mix(in oklab, var(--ring) 18%, transparent);
90
+ }
91
+
92
+ .slex-input-control[data-has-unit="true"] .slex-input:focus {
154
93
  box-shadow: none;
155
94
  }
156
95
 
@@ -175,6 +114,10 @@
175
114
 
176
115
  .slex-input-field[data-invalid="true"] .slex-input:focus {
177
116
  border-color: var(--destructive);
117
+ box-shadow: 0 0 0 2px color-mix(in oklab, var(--destructive) 18%, transparent);
118
+ }
119
+
120
+ .slex-input-field[data-invalid="true"] .slex-input-control[data-has-unit="true"] .slex-input:focus {
178
121
  box-shadow: none;
179
122
  }
180
123
 
@@ -182,13 +125,12 @@
182
125
  box-shadow: 0 0 0 2px color-mix(in oklab, var(--ring) 16%, transparent);
183
126
  }
184
127
 
185
- .slex-input-control:focus-within .slex-input,
186
- .slex-input-control:focus-within .slex-input-unit,
187
- .slex-input-control:focus-within .slex-input-controls {
188
- border-color: var(--ring);
128
+ .slex-input-control:not([data-has-unit]):focus-within {
129
+ box-shadow: none;
189
130
  }
190
131
 
191
- .slex-input-control:focus-within .slex-input-step {
132
+ .slex-input-control:focus-within .slex-input,
133
+ .slex-input-control:focus-within .slex-input-unit {
192
134
  border-color: var(--ring);
193
135
  }
194
136
 
@@ -197,22 +139,16 @@
197
139
  color: color-mix(in oklab, var(--destructive) 84%, var(--muted-foreground));
198
140
  }
199
141
 
200
- .slex-input-field[data-invalid="true"] .slex-input-step {
201
- border-color: color-mix(in oklab, var(--destructive) 72%, var(--input));
202
- }
203
-
204
- .slex-input-field[data-invalid="true"] .slex-input-controls {
205
- border-color: color-mix(in oklab, var(--destructive) 72%, var(--input));
206
- }
207
-
208
142
  .slex-input-field[data-invalid="true"] .slex-input-control:focus-within {
209
143
  box-shadow: 0 0 0 2px color-mix(in oklab, var(--destructive) 18%, transparent);
210
144
  }
211
145
 
146
+ .slex-input-field[data-invalid="true"] .slex-input-control:not([data-has-unit]):focus-within {
147
+ box-shadow: none;
148
+ }
149
+
212
150
  .slex-input-field[data-invalid="true"] .slex-input-control:focus-within .slex-input,
213
- .slex-input-field[data-invalid="true"] .slex-input-control:focus-within .slex-input-unit,
214
- .slex-input-field[data-invalid="true"] .slex-input-control:focus-within .slex-input-controls,
215
- .slex-input-field[data-invalid="true"] .slex-input-control:focus-within .slex-input-step {
151
+ .slex-input-field[data-invalid="true"] .slex-input-control:focus-within .slex-input-unit {
216
152
  border-color: var(--destructive);
217
153
  }
218
154
 
@@ -226,11 +162,6 @@
226
162
  cursor: not-allowed;
227
163
  }
228
164
 
229
- .slex-input[disabled] ~ .slex-input-controls,
230
- .slex-input[readonly] ~ .slex-input-controls {
231
- opacity: 0.6;
232
- }
233
-
234
165
  .slex-input-description {
235
166
  color: var(--muted-foreground);
236
167
  font-size: 0.75rem;
@@ -79,7 +79,7 @@
79
79
  display: block;
80
80
  }
81
81
 
82
- .slex-stat-character[data-stat-change] {
82
+ .slex-stat-character[data-stat-kind="digit"][data-stat-change] {
83
83
  overflow: hidden;
84
84
  }
85
85
 
@@ -32,52 +32,6 @@
32
32
  box-sizing: border-box;
33
33
  }
34
34
 
35
- body[data-mobile-nav-open] {
36
- overflow: hidden;
37
- overscroll-behavior: contain;
38
- }
39
-
40
- #mobileNav {
41
- --mobile-nav-backdrop-opacity: 0;
42
- --mobile-nav-panel-translate: -100%;
43
- pointer-events: none;
44
- }
45
-
46
- #mobileNav[data-open="true"] {
47
- --mobile-nav-backdrop-opacity: 1;
48
- --mobile-nav-panel-translate: 0px;
49
- pointer-events: auto;
50
- }
51
-
52
- #mobileNav [data-mobile-nav-backdrop] {
53
- opacity: var(--mobile-nav-backdrop-opacity);
54
- touch-action: pan-y;
55
- transition: opacity 180ms ease;
56
- }
57
-
58
- #mobileNav [data-mobile-nav-panel] {
59
- transform: translateX(var(--mobile-nav-panel-translate));
60
- touch-action: pan-y;
61
- transition: transform 220ms cubic-bezier(0.22, 1, 0.36, 1);
62
- will-change: transform;
63
- }
64
-
65
- #mobileNav[data-dragging="true"] [data-mobile-nav-backdrop],
66
- #mobileNav[data-dragging="true"] [data-mobile-nav-panel] {
67
- transition: none;
68
- }
69
-
70
- @media (prefers-reduced-motion: reduce) {
71
- #mobileNav [data-mobile-nav-backdrop],
72
- #mobileNav [data-mobile-nav-panel] {
73
- transition: none;
74
- }
75
- }
76
-
77
- .slexkit-for-wrapper {
78
- display: contents;
79
- }
80
-
81
35
  .slexkit-source-toolbar {
82
36
  display: flex;
83
37
  align-items: center;
@@ -572,7 +572,6 @@
572
572
  overflow: hidden;
573
573
  clip: rect(0 0 0 0);
574
574
  white-space: nowrap;
575
- clip-path: inset(50%);
576
575
  }
577
576
 
578
577
  .slex-playground-error {