@urbicon-ui/blocks 6.38.0 → 6.39.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.
@@ -0,0 +1,582 @@
1
+ <script lang="ts">
2
+ import {
3
+ Button,
4
+ Card,
5
+ Checkbox,
6
+ Input,
7
+ RadioGroup,
8
+ RadioItem,
9
+ Separator,
10
+ Skeleton,
11
+ Slider,
12
+ Textarea
13
+ } from '../../../primitives';
14
+ import { resolveIcon } from '../../../icons';
15
+ import DangerCircleIconDefault from '../../../icons/DangerCircleIcon.svelte';
16
+ import type { Snippet } from 'svelte';
17
+ import StreamingMarkdown from '../StreamingMarkdown/StreamingMarkdown.svelte';
18
+ import { checkImageUrl, checkLinkUrl } from '../markdown/url-policy.js';
19
+ import type { A2uiActionEvent } from './a2ui.types';
20
+ import { A2UI_REGISTRY, A2UI_SVG_PATH_RE } from './a2ui-registry';
21
+ import type { A2uiRenderContext, A2uiRenderNode } from './a2ui-render';
22
+ import { bindingPointer, toInputString, toStringArray } from './a2ui-render';
23
+
24
+ // Recursive dispatcher: one instance renders one node of the assembled A2UI
25
+ // render tree, mapping the trusted catalog subset onto real Urbicon
26
+ // primitives. Children come pre-expanded on `node.children` (templates, scope,
27
+ // bounds all resolved upstream in `buildRenderTree`) and are rendered through
28
+ // the `renderChild` snippet (a self-referencing snippet A2UIView threads down)
29
+ // with stable keys, so a keystroke-triggered rebuild never remounts a focused
30
+ // input. Recursing via a snippet — not a self-import — sidesteps the
31
+ // circular-component-type resolution that self-imports trip in svelte-check.
32
+ // The single `context` prop threads resolved classes, the data-binding
33
+ // resolver, two-way write-back and the action sink (house pattern of
34
+ // `md-context.ts`, not a Svelte context).
35
+ let {
36
+ node,
37
+ context,
38
+ renderChild
39
+ }: {
40
+ node: A2uiRenderNode;
41
+ context: A2uiRenderContext;
42
+ renderChild: Snippet<[A2uiRenderNode, A2uiRenderContext]>;
43
+ } = $props();
44
+
45
+ const DangerIcon = resolveIcon('danger', DangerCircleIconDefault);
46
+
47
+ // Local fallback state for inputs whose `value` is a literal (no data-model
48
+ // path to write back into). Bound inputs bypass these entirely.
49
+ let localText = $state<string | undefined>(undefined);
50
+ let localBool = $state<boolean | undefined>(undefined);
51
+ let localNum = $state<number | undefined>(undefined);
52
+ let localList = $state<string[] | undefined>(undefined);
53
+
54
+ const instance = $derived(node.instance);
55
+ const nodeProps = $derived(instance?.props);
56
+ const component = $derived(instance?.component ?? '');
57
+ const spec = $derived(component ? A2UI_REGISTRY[component] : undefined);
58
+
59
+ // A component is a visible fault when it is unknown/unsupported or is missing
60
+ // a required prop (the processor already emitted the reportable issue).
61
+ const missingRequired = $derived.by(() => {
62
+ if (!spec || !instance) return false;
63
+ for (const [key, propSpec] of Object.entries(spec.props)) {
64
+ if (propSpec.required && !instance.props.has(key)) return true;
65
+ }
66
+ return false;
67
+ });
68
+
69
+ function raw(key: string): unknown {
70
+ return nodeProps?.get(key);
71
+ }
72
+ function enumOr(key: string, fallback: string): string {
73
+ const value = nodeProps?.get(key);
74
+ return typeof value === 'string' ? value : fallback;
75
+ }
76
+ function numOr(value: unknown, fallback: number): number {
77
+ return typeof value === 'number' && Number.isFinite(value) ? value : fallback;
78
+ }
79
+ function text(value: unknown): string {
80
+ if (typeof value === 'string') return value;
81
+ if (typeof value === 'number' && Number.isFinite(value)) return String(value);
82
+ return '';
83
+ }
84
+ function resolved(key: string): unknown {
85
+ return context.resolve(nodeProps?.get(key), node.scopePrefix).value;
86
+ }
87
+
88
+ const ariaLabel = $derived.by(() => {
89
+ const a11y = nodeProps?.get('accessibility');
90
+ if (a11y !== null && typeof a11y === 'object') {
91
+ const label = (a11y as Record<string, unknown>).label;
92
+ if (typeof label === 'string' && label.length > 0) return label;
93
+ }
94
+ return undefined;
95
+ });
96
+
97
+ const weightStyle = $derived(node.weight != null ? `flex-grow:${node.weight}` : undefined);
98
+
99
+ // Child contexts: block containers reset the inline flag; a Button label sets
100
+ // it (Text then renders as a plain inline span). `context` is a plain object,
101
+ // not a $state proxy, so spreading it is safe.
102
+ const blockCtx = $derived(context.inline ? { ...context, inline: false } : context);
103
+ const inlineCtx = $derived({ ...context, inline: true });
104
+
105
+ // ── Enum → complete class-string lookups (kept as literals for Tailwind's scanner) ──
106
+ const JUSTIFY: Record<string, string> = {
107
+ start: 'justify-start',
108
+ center: 'justify-center',
109
+ end: 'justify-end',
110
+ spaceBetween: 'justify-between',
111
+ spaceAround: 'justify-around',
112
+ spaceEvenly: 'justify-evenly',
113
+ stretch: 'justify-stretch'
114
+ };
115
+ const ALIGN: Record<string, string> = {
116
+ start: 'items-start',
117
+ center: 'items-center',
118
+ end: 'items-end',
119
+ stretch: 'items-stretch'
120
+ };
121
+ const DIRECTION: Record<string, string> = { vertical: 'flex-col', horizontal: 'flex-row' };
122
+ const FIT: Record<string, string> = {
123
+ contain: 'object-contain',
124
+ cover: 'object-cover',
125
+ fill: 'object-fill',
126
+ none: 'object-none',
127
+ scaleDown: 'object-scale-down'
128
+ };
129
+ const IMAGE_SIZE: Record<string, string> = {
130
+ icon: 'h-6 w-6',
131
+ avatar: 'h-10 w-10 rounded-full',
132
+ smallFeature: 'h-24 w-auto',
133
+ mediumFeature: 'h-40 w-auto',
134
+ largeFeature: 'h-64 w-auto',
135
+ header: 'h-48 w-full'
136
+ };
137
+ const HEADING_SIZE: Record<string, string> = {
138
+ h1: 'text-2xl',
139
+ h2: 'text-xl',
140
+ h3: 'text-lg',
141
+ h4: 'text-base',
142
+ h5: 'text-sm'
143
+ };
144
+ const HEADING_TAG: Record<string, string> = {
145
+ h1: 'h1',
146
+ h2: 'h2',
147
+ h3: 'h3',
148
+ h4: 'h4',
149
+ h5: 'h5'
150
+ };
151
+ const INPUT_TYPE: Record<string, string> = {
152
+ shortText: 'text',
153
+ number: 'number',
154
+ obscured: 'password'
155
+ };
156
+ const BUTTON_STYLE: Record<
157
+ string,
158
+ { variant: 'filled' | 'text'; intent: 'primary' | 'neutral' }
159
+ > = {
160
+ default: { variant: 'filled', intent: 'neutral' },
161
+ primary: { variant: 'filled', intent: 'primary' },
162
+ borderless: { variant: 'text', intent: 'neutral' }
163
+ };
164
+
165
+ // ── Icon resolution ──────────────────────────────────────────────────────
166
+ const iconName = $derived.by<string | undefined>(() => {
167
+ const value = raw('name');
168
+ if (typeof value === 'string') return value;
169
+ if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
170
+ const object = value as Record<string, unknown>;
171
+ if (typeof object.svgPath === 'string') return undefined; // handled below
172
+ if (typeof object.path === 'string') {
173
+ const r = context.resolve(value, node.scopePrefix).value;
174
+ return typeof r === 'string' ? r : undefined;
175
+ }
176
+ }
177
+ return undefined;
178
+ });
179
+ const iconSvgPath = $derived.by<string | undefined>(() => {
180
+ const value = raw('name');
181
+ if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
182
+ const svgPath = (value as Record<string, unknown>).svgPath;
183
+ // Re-apply the grammar guard before inlining — defense in depth.
184
+ if (typeof svgPath === 'string' && A2UI_SVG_PATH_RE.test(svgPath)) return svgPath;
185
+ }
186
+ return undefined;
187
+ });
188
+ const IconComp = $derived((iconName && context.icons[iconName]) || context.fallbackIcon);
189
+
190
+ // ── Action ────────────────────────────────────────────────────────────────
191
+ const actionEvent = $derived.by(() => {
192
+ const action = raw('action');
193
+ if (action === null || typeof action !== 'object') return undefined;
194
+ const event = (action as Record<string, unknown>).event;
195
+ if (event === null || typeof event !== 'object') return undefined;
196
+ const name = (event as Record<string, unknown>).name;
197
+ return typeof name === 'string' ? (event as Record<string, unknown>) : undefined;
198
+ });
199
+
200
+ // A local `openUrl` function-call action: the only client-side action the
201
+ // subset supports. Every other functionCall is unsupported (the validator
202
+ // already warns) and leaves the button inert.
203
+ const openUrlArg = $derived.by<string | undefined>(() => {
204
+ const action = raw('action');
205
+ if (action === null || typeof action !== 'object') return undefined;
206
+ const fc = (action as Record<string, unknown>).functionCall;
207
+ if (fc === null || typeof fc !== 'object') return undefined;
208
+ const call = (fc as Record<string, unknown>).call;
209
+ if (call !== 'openUrl') return undefined;
210
+ const args = (fc as Record<string, unknown>).args;
211
+ const url =
212
+ args !== null && typeof args === 'object' ? (args as Record<string, unknown>).url : undefined;
213
+ const resolvedUrl = context.resolve(url, node.scopePrefix).value;
214
+ return typeof resolvedUrl === 'string' ? resolvedUrl : undefined;
215
+ });
216
+ const buttonUsable = $derived(actionEvent !== undefined || openUrlArg !== undefined);
217
+
218
+ function dispatchButton() {
219
+ if (actionEvent !== undefined) {
220
+ dispatchAction();
221
+ return;
222
+ }
223
+ if (openUrlArg !== undefined) {
224
+ // Gate the URL through the same policy as markdown links before opening.
225
+ const check = checkLinkUrl(openUrlArg, context.urlPolicy);
226
+ if (check.ok) window.open(check.href, '_blank', 'noopener,noreferrer');
227
+ }
228
+ }
229
+
230
+ function dispatchAction() {
231
+ const event = actionEvent;
232
+ if (!event || !instance) return;
233
+ // Build context on a plain object; prototype keys are skipped (never used as
234
+ // a key, so no pollution) — the processor already flagged them.
235
+ const resolvedContext: Record<string, unknown> = {};
236
+ const rawContext = event.context;
237
+ if (rawContext !== null && typeof rawContext === 'object' && !Array.isArray(rawContext)) {
238
+ for (const key of Object.keys(rawContext as Record<string, unknown>)) {
239
+ if (key === '__proto__' || key === 'constructor' || key === 'prototype') continue;
240
+ resolvedContext[key] = context.resolve(
241
+ (rawContext as Record<string, unknown>)[key],
242
+ node.scopePrefix
243
+ ).value;
244
+ }
245
+ }
246
+ const payload: A2uiActionEvent = {
247
+ name: event.name as string,
248
+ surfaceId: context.surfaceId,
249
+ sourceComponentId: instance.id,
250
+ timestamp: new Date().toISOString(),
251
+ context: resolvedContext
252
+ };
253
+ context.onAction?.(payload);
254
+ }
255
+
256
+ // ── Two-way inputs ──────────────────────────────────────────────────────
257
+ function valuePointer(): string | undefined {
258
+ return bindingPointer(raw('value'), node.scopePrefix);
259
+ }
260
+
261
+ const textValue = $derived.by(() => {
262
+ const pointer = valuePointer();
263
+ if (pointer) return toInputString(resolved('value'));
264
+ return localText ?? toInputString(raw('value'));
265
+ });
266
+ function onTextInput(event: Event) {
267
+ const target = event.currentTarget as HTMLInputElement | HTMLTextAreaElement;
268
+ const pointer = valuePointer();
269
+ if (pointer) context.write(pointer, target.value);
270
+ else localText = target.value;
271
+ }
272
+
273
+ const boolValue = $derived.by(() => {
274
+ const pointer = valuePointer();
275
+ if (pointer) return Boolean(resolved('value'));
276
+ return localBool ?? (typeof raw('value') === 'boolean' ? (raw('value') as boolean) : false);
277
+ });
278
+ function onBoolChange(checked: boolean) {
279
+ const pointer = valuePointer();
280
+ if (pointer) context.write(pointer, checked);
281
+ else localBool = checked;
282
+ }
283
+
284
+ const sliderMin = $derived(numOr(raw('min'), 0));
285
+ const sliderMax = $derived(numOr(raw('max'), 100));
286
+ const sliderValue = $derived.by(() => {
287
+ const pointer = valuePointer();
288
+ if (pointer) {
289
+ const value = resolved('value');
290
+ return typeof value === 'number' && Number.isFinite(value) ? value : sliderMin;
291
+ }
292
+ return localNum ?? (typeof raw('value') === 'number' ? (raw('value') as number) : sliderMin);
293
+ });
294
+ function onSliderChange(value: number | [number, number]) {
295
+ const scalar = Array.isArray(value) ? value[0] : value;
296
+ const pointer = valuePointer();
297
+ if (pointer) context.write(pointer, scalar);
298
+ else localNum = scalar;
299
+ }
300
+
301
+ const choiceSelected = $derived.by(() => {
302
+ const pointer = valuePointer();
303
+ if (pointer) return toStringArray(resolved('value'));
304
+ return localList ?? toStringArray(raw('value'));
305
+ });
306
+ const choiceOptions = $derived.by(() => {
307
+ const options = raw('options');
308
+ if (!Array.isArray(options)) return [] as Array<{ value: string; label: string }>;
309
+ // Dedupe by value (first wins): the validator warns on duplicates, but the
310
+ // keyed `{#each}` below is on option.value — two equal keys throw Svelte's
311
+ // `each_key_duplicate` (a hard crash that would break the "never throw"
312
+ // contract for an untrusted payload).
313
+ const seen = new Set<string>();
314
+ const out: Array<{ value: string; label: string }> = [];
315
+ for (const option of options) {
316
+ if (option === null || typeof option !== 'object' || typeof option.value !== 'string')
317
+ continue;
318
+ if (seen.has(option.value)) continue;
319
+ seen.add(option.value);
320
+ out.push({
321
+ value: option.value,
322
+ label: text(context.resolve(option.label, node.scopePrefix).value)
323
+ });
324
+ }
325
+ return out;
326
+ });
327
+ const choiceLabel = $derived(text(resolved('label')));
328
+ function writeChoice(next: string[]) {
329
+ const pointer = valuePointer();
330
+ if (pointer) context.write(pointer, next);
331
+ else localList = next;
332
+ }
333
+ function onRadioChange(value: string) {
334
+ writeChoice([value]);
335
+ }
336
+ function onOptionToggle(value: string, checked: boolean) {
337
+ const current = choiceSelected;
338
+ if (checked) writeChoice(current.includes(value) ? current : [...current, value]);
339
+ else writeChoice(current.filter((item) => item !== value));
340
+ }
341
+
342
+ const buttonStyle = $derived(BUTTON_STYLE[enumOr('variant', 'default')] ?? BUTTON_STYLE.default);
343
+ </script>
344
+
345
+ {#snippet faultChip(label: string)}
346
+ <span class={context.classes.errorChip}>
347
+ <DangerIcon size={14} class={context.classes.errorIcon} />
348
+ {label}
349
+ </span>
350
+ {/snippet}
351
+
352
+ {#if instance === null}
353
+ {#if context.streaming}
354
+ <span class={context.classes.pending}>
355
+ <Skeleton variant="text" width="6rem" />
356
+ <span class="sr-only">{context.labels.pending}</span>
357
+ </span>
358
+ {:else}
359
+ {@render faultChip(`${context.labels.unsupported}: ${node.id}`)}
360
+ {/if}
361
+ {:else if !spec || missingRequired}
362
+ {@render faultChip(`${context.labels.unsupported}: ${component || node.id}`)}
363
+ {:else if component === 'Column'}
364
+ <!-- A plain <div> is role=generic, which forbids aria-label (axe
365
+ aria-prohibited-attr, ignored by SR); a labelled group needs role="group". -->
366
+ <div
367
+ class={[
368
+ context.classes.column,
369
+ JUSTIFY[enumOr('justify', 'start')],
370
+ ALIGN[enumOr('align', 'stretch')]
371
+ ]}
372
+ style={weightStyle}
373
+ role={ariaLabel ? 'group' : undefined}
374
+ aria-label={ariaLabel}
375
+ >
376
+ {#each node.children as child (child.key)}
377
+ {@render renderChild(child, blockCtx)}
378
+ {/each}
379
+ </div>
380
+ {:else if component === 'Row'}
381
+ <div
382
+ class={[
383
+ context.classes.row,
384
+ JUSTIFY[enumOr('justify', 'start')],
385
+ ALIGN[enumOr('align', 'stretch')]
386
+ ]}
387
+ style={weightStyle}
388
+ role={ariaLabel ? 'group' : undefined}
389
+ aria-label={ariaLabel}
390
+ >
391
+ {#each node.children as child (child.key)}
392
+ {@render renderChild(child, blockCtx)}
393
+ {/each}
394
+ </div>
395
+ {:else if component === 'List'}
396
+ <ul
397
+ class={[
398
+ context.classes.list,
399
+ DIRECTION[enumOr('direction', 'vertical')],
400
+ ALIGN[enumOr('align', 'stretch')]
401
+ ]}
402
+ style={weightStyle}
403
+ aria-label={ariaLabel}
404
+ >
405
+ {#each node.children as child (child.key)}
406
+ <li class={context.classes.listItem}>
407
+ {@render renderChild(child, blockCtx)}
408
+ </li>
409
+ {/each}
410
+ </ul>
411
+ {:else if component === 'Card'}
412
+ <Card style={weightStyle} role={ariaLabel ? 'group' : undefined} aria-label={ariaLabel}>
413
+ {#if node.children[0]}
414
+ {@render renderChild(node.children[0], blockCtx)}
415
+ {/if}
416
+ </Card>
417
+ {:else if component === 'Divider'}
418
+ <Separator
419
+ orientation={enumOr('axis', 'horizontal') === 'vertical' ? 'vertical' : 'horizontal'}
420
+ />
421
+ {:else if component === 'Text'}
422
+ {@const value = text(resolved('text'))}
423
+ {@const variant = enumOr('variant', 'body')}
424
+ {#if context.inline}
425
+ <span class={context.classes.inlineText} style={weightStyle}>{value}</span>
426
+ {:else if variant === 'caption'}
427
+ <span class={context.classes.caption} style={weightStyle}>{value}</span>
428
+ {:else if variant === 'body'}
429
+ <StreamingMarkdown
430
+ content={value}
431
+ size="sm"
432
+ urlPolicy={context.urlPolicy}
433
+ streaming={false}
434
+ style={weightStyle}
435
+ />
436
+ {:else}
437
+ <svelte:element
438
+ this={HEADING_TAG[variant] ?? 'h3'}
439
+ class={[context.classes.heading, HEADING_SIZE[variant]]}
440
+ style={weightStyle}
441
+ >
442
+ {value}
443
+ </svelte:element>
444
+ {/if}
445
+ {:else if component === 'Image'}
446
+ {@const url = text(resolved('url'))}
447
+ {@const description = text(resolved('description'))}
448
+ {@const check = checkImageUrl(url, context.urlPolicy)}
449
+ {#if check.ok}
450
+ <img
451
+ src={check.href}
452
+ alt={description}
453
+ class={[
454
+ context.classes.image,
455
+ FIT[enumOr('fit', 'fill')],
456
+ IMAGE_SIZE[enumOr('variant', 'mediumFeature')]
457
+ ]}
458
+ style={weightStyle}
459
+ />
460
+ {:else}
461
+ <span class={context.classes.blockedChip} style={weightStyle}>
462
+ {context.labels.blockedImage}{description ? `: ${description}` : ''}
463
+ </span>
464
+ {/if}
465
+ {:else if component === 'Icon'}
466
+ {#if iconSvgPath}
467
+ <svg
468
+ viewBox="0 0 24 24"
469
+ width="20"
470
+ height="20"
471
+ class={context.classes.svgIcon}
472
+ style={weightStyle}
473
+ stroke-width="2"
474
+ stroke-linecap="round"
475
+ stroke-linejoin="round"
476
+ role={ariaLabel ? 'img' : undefined}
477
+ aria-label={ariaLabel}
478
+ aria-hidden={ariaLabel ? undefined : 'true'}
479
+ >
480
+ <path d={iconSvgPath} />
481
+ </svg>
482
+ {:else}
483
+ <span
484
+ class={context.classes.icon}
485
+ style={weightStyle}
486
+ role={ariaLabel ? 'img' : undefined}
487
+ aria-label={ariaLabel}
488
+ aria-hidden={ariaLabel ? undefined : 'true'}
489
+ >
490
+ <IconComp size={20} />
491
+ </span>
492
+ {/if}
493
+ {:else if component === 'Button'}
494
+ <Button
495
+ variant={buttonStyle.variant}
496
+ intent={buttonStyle.intent}
497
+ style={weightStyle}
498
+ aria-label={ariaLabel}
499
+ disabled={!buttonUsable}
500
+ title={buttonUsable ? undefined : 'This action is not supported'}
501
+ onclick={buttonUsable ? dispatchButton : undefined}
502
+ >
503
+ {#if node.children[0]}
504
+ {@render renderChild(node.children[0], inlineCtx)}
505
+ {/if}
506
+ </Button>
507
+ {#if !buttonUsable}
508
+ <!-- A disabled button drops out of the tab order, so `title` is invisible to
509
+ screen readers; an adjacent sr-only note is reachable in browse mode. -->
510
+ <span class="sr-only">This action is not supported</span>
511
+ {/if}
512
+ {:else if component === 'TextField'}
513
+ {@const label = text(resolved('label'))}
514
+ {@const variant = enumOr('variant', 'shortText')}
515
+ {#if variant === 'longText'}
516
+ <Textarea
517
+ {label}
518
+ value={textValue}
519
+ oninput={onTextInput}
520
+ style={weightStyle}
521
+ aria-label={ariaLabel}
522
+ />
523
+ {:else}
524
+ <Input
525
+ {label}
526
+ type={INPUT_TYPE[variant] ?? 'text'}
527
+ value={textValue}
528
+ oninput={onTextInput}
529
+ style={weightStyle}
530
+ aria-label={ariaLabel}
531
+ />
532
+ {/if}
533
+ {:else if component === 'CheckBox'}
534
+ <Checkbox
535
+ label={text(resolved('label'))}
536
+ checked={boolValue}
537
+ onCheckedChange={onBoolChange}
538
+ style={weightStyle}
539
+ aria-label={ariaLabel}
540
+ />
541
+ {:else if component === 'ChoicePicker'}
542
+ {#if enumOr('variant', 'mutuallyExclusive') === 'multipleSelection'}
543
+ <div
544
+ class={context.classes.choiceGroup}
545
+ style={weightStyle}
546
+ role="group"
547
+ aria-label={ariaLabel || choiceLabel || undefined}
548
+ >
549
+ {#if choiceLabel}
550
+ <span class={context.classes.choiceLabel}>{choiceLabel}</span>
551
+ {/if}
552
+ {#each choiceOptions as option (option.value)}
553
+ <Checkbox
554
+ label={option.label}
555
+ checked={choiceSelected.includes(option.value)}
556
+ onCheckedChange={(checked) => onOptionToggle(option.value, checked)}
557
+ />
558
+ {/each}
559
+ </div>
560
+ {:else}
561
+ <RadioGroup
562
+ label={ariaLabel || choiceLabel || undefined}
563
+ value={choiceSelected[0]}
564
+ onValueChange={onRadioChange}
565
+ style={weightStyle}
566
+ >
567
+ {#each choiceOptions as option (option.value)}
568
+ <RadioItem value={option.value} label={option.label} />
569
+ {/each}
570
+ </RadioGroup>
571
+ {/if}
572
+ {:else if component === 'Slider'}
573
+ <Slider
574
+ label={text(resolved('label')) || undefined}
575
+ value={sliderValue}
576
+ min={sliderMin}
577
+ max={sliderMax}
578
+ onValueChange={onSliderChange}
579
+ style={weightStyle}
580
+ aria-label={ariaLabel}
581
+ />
582
+ {/if}
@@ -0,0 +1,10 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { A2uiRenderContext, A2uiRenderNode } from './a2ui-render.js';
3
+ type $$ComponentProps = {
4
+ node: A2uiRenderNode;
5
+ context: A2uiRenderContext;
6
+ renderChild: Snippet<[A2uiRenderNode, A2uiRenderContext]>;
7
+ };
8
+ declare const A2UINode: import("svelte").Component<$$ComponentProps, {}, "">;
9
+ type A2UINode = ReturnType<typeof A2UINode>;
10
+ export default A2UINode;