@selvajs/ui 6.6.0-beta.7 → 6.6.0-beta.9
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/dist/components/compute/AppLayout.svelte +37 -7
- package/dist/components/compute/AppLayout.svelte.d.ts +5 -0
- package/dist/components/compute/ParameterPresetManager.svelte +8 -20
- package/dist/components/compute/SolveMessages.svelte +26 -3
- package/dist/components/compute/SolveMessages.svelte.d.ts +7 -0
- package/dist/components/layout/PageFooter.svelte +3 -1
- package/dist/components/layout/SideNav.svelte +55 -14
- package/dist/components/preview/ChartOutput.svelte +18 -13
- package/dist/components/preview/Group.svelte +6 -1
- package/dist/components/preview/ImageOutput.svelte +3 -1
- package/dist/components/preview/InputControl.svelte +108 -44
- package/dist/components/preview/InputControl.svelte.d.ts +5 -1
- package/dist/components/preview/TabContent.svelte +61 -29
- package/dist/components/preview/TabContent.svelte.d.ts +5 -1
- package/dist/components/preview/TabLayout.svelte +25 -3
- package/dist/components/preview/TabLayout.svelte.d.ts +5 -0
- package/dist/components/preview/inputs/ChecklistInput.svelte +7 -2
- package/dist/components/preview/inputs/ChecklistInput.svelte.d.ts +2 -1
- package/dist/components/preview/inputs/DropdownInput.svelte +16 -4
- package/dist/components/preview/inputs/DropdownInput.svelte.d.ts +2 -1
- package/dist/components/preview/inputs/FileInput.svelte +56 -65
- package/dist/components/preview/inputs/GeometryInput.svelte +128 -0
- package/dist/components/preview/inputs/GeometryInput.svelte.d.ts +21 -0
- package/dist/components/preview/inputs/OptionGlyph.svelte +29 -0
- package/dist/components/preview/inputs/OptionGlyph.svelte.d.ts +8 -0
- package/dist/components/preview/inputs/SegmentedInput.svelte +44 -0
- package/dist/components/preview/inputs/SegmentedInput.svelte.d.ts +12 -0
- package/dist/components/preview/inputs/TileInput.svelte +80 -0
- package/dist/components/preview/inputs/TileInput.svelte.d.ts +12 -0
- package/dist/components/preview/inputs/index.d.ts +3 -0
- package/dist/components/preview/inputs/index.js +3 -0
- package/dist/components/primitives/SolvingIndicator.svelte +28 -3
- package/dist/components/primitives/SolvingIndicator.svelte.d.ts +7 -0
- package/dist/components/primitives/button/button.svelte +3 -0
- package/dist/components/primitives/button/button.svelte.d.ts +3 -0
- package/dist/components/primitives/toggle/toggle.svelte +3 -1
- package/dist/components/primitives/toggle/toggle.svelte.d.ts +3 -0
- package/dist/components/viewer/Viewer.svelte +11 -11
- package/dist/compute/solving.svelte.d.ts +7 -1
- package/dist/compute/solving.svelte.js +12 -1
- package/dist/contexts/hostContext.svelte.d.ts +39 -0
- package/dist/contexts/hostContext.svelte.js +9 -0
- package/dist/i18n/messages.d.ts +33 -0
- package/dist/i18n/messages.js +58 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/schema/host-support.d.ts +29 -0
- package/dist/schema/host-support.js +97 -0
- package/dist/schema/option-display.d.ts +12 -0
- package/dist/schema/option-display.js +29 -0
- package/dist/schema/param-exporter.d.ts +4 -1
- package/dist/schema/param-exporter.js +10 -6
- package/dist/styles/base.css +6 -3
- package/dist/utils/file-download.js +10 -22
- package/dist/utils/host-files.d.ts +12 -0
- package/dist/utils/host-files.js +46 -0
- package/dist/utils/webp-to-png.d.ts +10 -0
- package/dist/utils/webp-to-png.js +41 -0
- package/package.json +9 -9
- package/src/lib/components/compute/AppLayout.svelte +37 -7
- package/src/lib/components/compute/ParameterPresetManager.svelte +8 -20
- package/src/lib/components/compute/SolveMessages.svelte +26 -3
- package/src/lib/components/layout/PageFooter.svelte +3 -1
- package/src/lib/components/layout/SideNav.svelte +55 -14
- package/src/lib/components/preview/ChartOutput.svelte +18 -13
- package/src/lib/components/preview/Group.svelte +6 -1
- package/src/lib/components/preview/ImageOutput.svelte +3 -1
- package/src/lib/components/preview/InputControl.svelte +108 -44
- package/src/lib/components/preview/TabContent.svelte +61 -29
- package/src/lib/components/preview/TabLayout.svelte +25 -3
- package/src/lib/components/preview/inputs/ChecklistInput.svelte +7 -2
- package/src/lib/components/preview/inputs/DropdownInput.svelte +16 -4
- package/src/lib/components/preview/inputs/FileInput.svelte +56 -65
- package/src/lib/components/preview/inputs/GeometryInput.svelte +128 -0
- package/src/lib/components/preview/inputs/OptionGlyph.svelte +29 -0
- package/src/lib/components/preview/inputs/SegmentedInput.svelte +44 -0
- package/src/lib/components/preview/inputs/TileInput.svelte +80 -0
- package/src/lib/components/preview/inputs/index.ts +3 -0
- package/src/lib/components/primitives/SolvingIndicator.svelte +28 -3
- package/src/lib/components/primitives/button/button.svelte +3 -0
- package/src/lib/components/primitives/toggle/toggle.svelte +3 -1
- package/src/lib/components/viewer/Viewer.svelte +11 -11
- package/src/lib/compute/solving.svelte.ts +16 -1
- package/src/lib/contexts/hostContext.svelte.ts +51 -0
- package/src/lib/i18n/messages.ts +96 -0
- package/src/lib/index.ts +20 -0
- package/src/lib/schema/host-support.ts +129 -0
- package/src/lib/schema/option-display.ts +44 -0
- package/src/lib/schema/param-exporter.ts +10 -7
- package/src/lib/styles/base.css +6 -3
- package/src/lib/utils/file-download.ts +11 -21
- package/src/lib/utils/host-files.ts +61 -0
- package/src/lib/utils/webp-to-png.ts +48 -0
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
FileInputWidgetConfig,
|
|
5
5
|
DynamicValueListWidgetConfig,
|
|
6
6
|
DropdownWidgetConfig,
|
|
7
|
+
UISchema,
|
|
7
8
|
SupportedTypes
|
|
8
9
|
} from '@selvajs/schemas';
|
|
9
10
|
import {
|
|
@@ -16,6 +17,8 @@
|
|
|
16
17
|
isColorWidget
|
|
17
18
|
} from '@selvajs/schemas';
|
|
18
19
|
import * as Field from '../primitives/field';
|
|
20
|
+
import { Button } from '../primitives/button';
|
|
21
|
+
import { toast } from '../primitives/sonner';
|
|
19
22
|
import HelpButton from './HelpButton.svelte';
|
|
20
23
|
import {
|
|
21
24
|
CheckboxInput,
|
|
@@ -23,11 +26,21 @@
|
|
|
23
26
|
ColorInput,
|
|
24
27
|
DropdownInput,
|
|
25
28
|
FileInput,
|
|
29
|
+
GeometryInput,
|
|
26
30
|
NumberInput,
|
|
27
|
-
|
|
31
|
+
SegmentedInput,
|
|
32
|
+
TextInput,
|
|
33
|
+
TileInput
|
|
28
34
|
} from './inputs';
|
|
29
35
|
import { getClientSlot } from '../../contexts/clientSlotContext.svelte';
|
|
36
|
+
import {
|
|
37
|
+
getHostContext,
|
|
38
|
+
type GeometryPickTarget,
|
|
39
|
+
type PickKind
|
|
40
|
+
} from '../../contexts/hostContext.svelte';
|
|
30
41
|
import { getLocaleContext } from '../../i18n/localeContext.svelte';
|
|
42
|
+
import { isGeometryParamType, isTriggerItem } from '../../schema/host-support';
|
|
43
|
+
import { resolveOptionDisplay } from '../../schema/option-display';
|
|
31
44
|
|
|
32
45
|
interface Props {
|
|
33
46
|
item: InputLayoutItem;
|
|
@@ -42,6 +55,10 @@
|
|
|
42
55
|
disabled?: boolean;
|
|
43
56
|
/** Runtime-computed options for a dynamic value list input (name -> value). */
|
|
44
57
|
dynamicOptions?: Record<string, string>;
|
|
58
|
+
/** The input's paramType: a geometry input gets its control from this, not from the widget type. */
|
|
59
|
+
paramType?: string;
|
|
60
|
+
/** The schema's icons and thumbnails, which value-list options point into. */
|
|
61
|
+
assets?: UISchema['assets'];
|
|
45
62
|
}
|
|
46
63
|
|
|
47
64
|
let {
|
|
@@ -50,10 +67,25 @@
|
|
|
50
67
|
displayName,
|
|
51
68
|
onChange,
|
|
52
69
|
disabled = false,
|
|
53
|
-
dynamicOptions
|
|
70
|
+
dynamicOptions,
|
|
71
|
+
paramType,
|
|
72
|
+
assets
|
|
54
73
|
}: Props = $props();
|
|
55
74
|
|
|
56
75
|
const locale = getLocaleContext();
|
|
76
|
+
const hostActions = getHostContext();
|
|
77
|
+
const { pickGeometry, clearGeometry, onGeometrySummary } = hostActions;
|
|
78
|
+
const isGeometry = $derived(isGeometryParamType(paramType));
|
|
79
|
+
const isTrigger = $derived(isTriggerItem(item));
|
|
80
|
+
const pickTarget = $derived(
|
|
81
|
+
isGeometry ? hostActions.describeGeometry?.(item.paramId) : undefined
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// Get Point/Line/Plane take their own kind; Get Geometry takes what its filter allows.
|
|
85
|
+
function pickKinds(target: GeometryPickTarget | undefined): readonly PickKind[] | undefined {
|
|
86
|
+
if (paramType === 'point' || paramType === 'line' || paramType === 'plane') return [paramType];
|
|
87
|
+
return target?.kinds;
|
|
88
|
+
}
|
|
57
89
|
|
|
58
90
|
const inputId = $derived(`input-${item.paramId}`);
|
|
59
91
|
const label = $derived(displayName || item.displayName || item.paramId);
|
|
@@ -102,7 +134,8 @@
|
|
|
102
134
|
// As a dropdown config so DropdownInput/ChecklistInput can consume it unchanged.
|
|
103
135
|
const dynamicListAsDropdownConfig = $derived<DropdownWidgetConfig>({
|
|
104
136
|
options: dynamicListOptions,
|
|
105
|
-
displayAs: dynamicListConfig?.displayAs ?? 'dropdown'
|
|
137
|
+
displayAs: dynamicListConfig?.displayAs ?? 'dropdown',
|
|
138
|
+
optionMedia: dynamicListConfig?.optionMedia
|
|
106
139
|
});
|
|
107
140
|
const hideDynamicListWhenEmpty = $derived(
|
|
108
141
|
isDynamicValueListWidget(item) &&
|
|
@@ -173,6 +206,48 @@
|
|
|
173
206
|
}
|
|
174
207
|
</script>
|
|
175
208
|
|
|
209
|
+
{#snippet valueList(config: DropdownWidgetConfig)}
|
|
210
|
+
{@const display = resolveOptionDisplay(
|
|
211
|
+
config.displayAs,
|
|
212
|
+
Object.keys(config.options ?? {}).length
|
|
213
|
+
)}
|
|
214
|
+
{@const single = typeof value === 'string' ? value : ''}
|
|
215
|
+
{#if display === 'checklist'}
|
|
216
|
+
<ChecklistInput
|
|
217
|
+
{inputId}
|
|
218
|
+
value={Array.isArray(value)
|
|
219
|
+
? (value as string[])
|
|
220
|
+
: typeof value === 'string' && value
|
|
221
|
+
? [value]
|
|
222
|
+
: []}
|
|
223
|
+
{config}
|
|
224
|
+
{assets}
|
|
225
|
+
onChange={commit}
|
|
226
|
+
{disabled}
|
|
227
|
+
/>
|
|
228
|
+
{:else if display === 'segmented'}
|
|
229
|
+
<SegmentedInput
|
|
230
|
+
labelId="{inputId}-label"
|
|
231
|
+
value={single}
|
|
232
|
+
{config}
|
|
233
|
+
{assets}
|
|
234
|
+
onChange={commit}
|
|
235
|
+
{disabled}
|
|
236
|
+
/>
|
|
237
|
+
{:else if display === 'tiles'}
|
|
238
|
+
<TileInput
|
|
239
|
+
labelId="{inputId}-label"
|
|
240
|
+
value={single}
|
|
241
|
+
{config}
|
|
242
|
+
{assets}
|
|
243
|
+
onChange={commit}
|
|
244
|
+
{disabled}
|
|
245
|
+
/>
|
|
246
|
+
{:else}
|
|
247
|
+
<DropdownInput value={single} {config} {assets} onChange={commit} {disabled} />
|
|
248
|
+
{/if}
|
|
249
|
+
{/snippet}
|
|
250
|
+
|
|
176
251
|
{#snippet labelText(forId?: string, tone: 'quiet' | 'strong' = 'quiet')}
|
|
177
252
|
<Field.Label
|
|
178
253
|
for={forId}
|
|
@@ -218,6 +293,34 @@
|
|
|
218
293
|
})}
|
|
219
294
|
</div>
|
|
220
295
|
{/if}
|
|
296
|
+
{:else if isGeometry}
|
|
297
|
+
<div role="group" class="group/field gap-1.5 min-w-0 flex flex-col">
|
|
298
|
+
{@render fieldHeader(inputId)}
|
|
299
|
+
<GeometryInput
|
|
300
|
+
{inputId}
|
|
301
|
+
kinds={pickKinds(pickTarget)}
|
|
302
|
+
atMost={pickTarget?.atMost}
|
|
303
|
+
summary={typeof value === 'string' ? value : undefined}
|
|
304
|
+
onPick={pickGeometry && (() => pickGeometry(item.paramId))}
|
|
305
|
+
onClear={clearGeometry && (() => clearGeometry(item.paramId))}
|
|
306
|
+
watchSummary={onGeometrySummary && ((listener) => onGeometrySummary(item.paramId, listener))}
|
|
307
|
+
onError={(message) => toast.error(message)}
|
|
308
|
+
{disabled}
|
|
309
|
+
/>
|
|
310
|
+
</div>
|
|
311
|
+
{:else if isTrigger}
|
|
312
|
+
<div role="group" class="group/field gap-1.5 min-w-0 flex items-center" data-control-line>
|
|
313
|
+
<Button
|
|
314
|
+
id={inputId}
|
|
315
|
+
variant="outline"
|
|
316
|
+
class="min-w-0 flex-1"
|
|
317
|
+
onclick={() => hostActions.trigger?.(item.paramId)}
|
|
318
|
+
disabled={disabled || !hostActions.trigger || hostActions.isSolving?.()}
|
|
319
|
+
>
|
|
320
|
+
<span class="truncate">{label}</span>
|
|
321
|
+
</Button>
|
|
322
|
+
{@render helpButton()}
|
|
323
|
+
</div>
|
|
221
324
|
{:else if !hideDynamicListWhenEmpty}
|
|
222
325
|
<div role="group" class="group/field gap-1.5 min-w-0 flex flex-col">
|
|
223
326
|
{#if !isCheckboxWidget(item)}
|
|
@@ -254,49 +357,10 @@
|
|
|
254
357
|
{disabled}
|
|
255
358
|
/>
|
|
256
359
|
{:else if isDropdownWidget(item)}
|
|
257
|
-
{@
|
|
258
|
-
{#if config.displayAs === 'checklist'}
|
|
259
|
-
<ChecklistInput
|
|
260
|
-
{inputId}
|
|
261
|
-
value={Array.isArray(value)
|
|
262
|
-
? (value as string[])
|
|
263
|
-
: typeof value === 'string' && value
|
|
264
|
-
? [value]
|
|
265
|
-
: []}
|
|
266
|
-
{config}
|
|
267
|
-
onChange={commit}
|
|
268
|
-
{disabled}
|
|
269
|
-
/>
|
|
270
|
-
{:else}
|
|
271
|
-
<DropdownInput
|
|
272
|
-
value={typeof value === 'string' ? value : ''}
|
|
273
|
-
{config}
|
|
274
|
-
onChange={commit}
|
|
275
|
-
{disabled}
|
|
276
|
-
/>
|
|
277
|
-
{/if}
|
|
360
|
+
{@render valueList(item.config)}
|
|
278
361
|
{:else if isDynamicValueListWidget(item)}
|
|
279
362
|
{#if dynamicListHasOptions}
|
|
280
|
-
{
|
|
281
|
-
<ChecklistInput
|
|
282
|
-
{inputId}
|
|
283
|
-
value={Array.isArray(value)
|
|
284
|
-
? (value as string[])
|
|
285
|
-
: typeof value === 'string' && value
|
|
286
|
-
? [value]
|
|
287
|
-
: []}
|
|
288
|
-
config={dynamicListAsDropdownConfig}
|
|
289
|
-
onChange={commit}
|
|
290
|
-
{disabled}
|
|
291
|
-
/>
|
|
292
|
-
{:else}
|
|
293
|
-
<DropdownInput
|
|
294
|
-
value={typeof value === 'string' ? value : ''}
|
|
295
|
-
config={dynamicListAsDropdownConfig}
|
|
296
|
-
onChange={commit}
|
|
297
|
-
{disabled}
|
|
298
|
-
/>
|
|
299
|
-
{/if}
|
|
363
|
+
{@render valueList(dynamicListAsDropdownConfig)}
|
|
300
364
|
{:else}
|
|
301
365
|
<p class="text-sm text-muted-foreground">{locale.messages.noOptionsYet}</p>
|
|
302
366
|
{/if}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { InputLayoutItem, SupportedTypes } from '@selvajs/schemas';
|
|
1
|
+
import type { InputLayoutItem, UISchema, SupportedTypes } from '@selvajs/schemas';
|
|
2
2
|
interface Props {
|
|
3
3
|
item: InputLayoutItem;
|
|
4
4
|
value?: SupportedTypes;
|
|
@@ -12,6 +12,10 @@ interface Props {
|
|
|
12
12
|
disabled?: boolean;
|
|
13
13
|
/** Runtime-computed options for a dynamic value list input (name -> value). */
|
|
14
14
|
dynamicOptions?: Record<string, string>;
|
|
15
|
+
/** The input's paramType: a geometry input gets its control from this, not from the widget type. */
|
|
16
|
+
paramType?: string;
|
|
17
|
+
/** The schema's icons and thumbnails, which value-list options point into. */
|
|
18
|
+
assets?: UISchema['assets'];
|
|
15
19
|
}
|
|
16
20
|
declare const InputControl: import("svelte").Component<Props, {}, "value">;
|
|
17
21
|
type InputControl = ReturnType<typeof InputControl>;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
TabConfig,
|
|
4
4
|
SchemaInput,
|
|
5
5
|
DiscoveredOutput,
|
|
6
|
+
UISchema,
|
|
6
7
|
SupportedTypes,
|
|
7
8
|
InputLayoutItem,
|
|
8
9
|
OutputLayoutItem
|
|
@@ -14,6 +15,8 @@
|
|
|
14
15
|
import InputControl from './InputControl.svelte';
|
|
15
16
|
import OutputDisplay from './OutputDisplay.svelte';
|
|
16
17
|
import { evaluateGroupVisibility } from '../../schema/visibility-rules';
|
|
18
|
+
import { isSupportedOn, layoutItemKind } from '../../schema/host-support';
|
|
19
|
+
import { getHostContext } from '../../contexts/hostContext.svelte';
|
|
17
20
|
import { getLocaleContext } from '../../i18n/localeContext.svelte';
|
|
18
21
|
|
|
19
22
|
const locale = getLocaleContext();
|
|
@@ -29,6 +32,10 @@
|
|
|
29
32
|
outputs: DiscoveredOutput[];
|
|
30
33
|
/** Computed value list options keyed by the target dynamic-value-list input id. */
|
|
31
34
|
dynamicOptions?: Record<string, Record<string, string>>;
|
|
35
|
+
/** The schema's icons and thumbnails, which value-list options point into. */
|
|
36
|
+
assets?: UISchema['assets'];
|
|
37
|
+
/** No tinted backdrop and no scroll area of its own: the container scrolls. */
|
|
38
|
+
bare?: boolean;
|
|
32
39
|
}
|
|
33
40
|
|
|
34
41
|
let {
|
|
@@ -39,11 +46,24 @@
|
|
|
39
46
|
onValueChange,
|
|
40
47
|
inputs,
|
|
41
48
|
outputs,
|
|
42
|
-
dynamicOptions = {}
|
|
49
|
+
dynamicOptions = {},
|
|
50
|
+
assets,
|
|
51
|
+
bare = false
|
|
43
52
|
}: Props = $props();
|
|
44
53
|
|
|
54
|
+
const { host } = getHostContext();
|
|
55
|
+
|
|
56
|
+
// An element this host can't render is left out, not shown broken: its input still solves with
|
|
57
|
+
// the definition's own default. A group left with nothing to show goes too.
|
|
45
58
|
const visibleGroups = $derived(
|
|
46
|
-
tab.groups
|
|
59
|
+
tab.groups
|
|
60
|
+
.filter((group) => evaluateGroupVisibility(group, values))
|
|
61
|
+
.flatMap((group) => {
|
|
62
|
+
const items = group.items.filter((item) =>
|
|
63
|
+
isSupportedOn(host, layoutItemKind(item, inputs))
|
|
64
|
+
);
|
|
65
|
+
return items.length > 0 || group.items.length === 0 ? [{ ...group, items }] : [];
|
|
66
|
+
})
|
|
47
67
|
);
|
|
48
68
|
|
|
49
69
|
function getInputById(paramId: string): SchemaInput | undefined {
|
|
@@ -68,6 +88,8 @@
|
|
|
68
88
|
onChange={onValueChange}
|
|
69
89
|
disabled={visibility.disabled}
|
|
70
90
|
dynamicOptions={dynamicOptions[input.id]}
|
|
91
|
+
paramType={input.paramType}
|
|
92
|
+
{assets}
|
|
71
93
|
/>
|
|
72
94
|
{/if}
|
|
73
95
|
{/snippet}
|
|
@@ -83,34 +105,44 @@
|
|
|
83
105
|
{/if}
|
|
84
106
|
{/snippet}
|
|
85
107
|
|
|
86
|
-
|
|
87
|
-
<div class=
|
|
88
|
-
|
|
89
|
-
<div class="px-3">
|
|
90
|
-
<!-- A heading per panel lets a screen reader jump between them; the tab, when shown,
|
|
108
|
+
{#snippet body()}
|
|
109
|
+
<div class={bare ? 'px-1' : 'px-3'}>
|
|
110
|
+
<!-- A heading per panel lets a screen reader jump between them; the tab, when shown,
|
|
91
111
|
already names it on screen. -->
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
</div>
|
|
112
|
-
{/if}
|
|
112
|
+
<h2 class="sr-only">{tab.label}</h2>
|
|
113
|
+
{#if visibleGroups.length === 0}
|
|
114
|
+
<StateDisplay type="empty" size="medium" message={t.tabNoGroups} />
|
|
115
|
+
{:else}
|
|
116
|
+
<div class="gap-3 py-3 flex flex-col">
|
|
117
|
+
{#each visibleGroups as group (group.id)}
|
|
118
|
+
<Group
|
|
119
|
+
label={group.label}
|
|
120
|
+
description={group.description}
|
|
121
|
+
items={group.items}
|
|
122
|
+
columns={group.columns ?? 1}
|
|
123
|
+
collapsed={collapsedGroups[group.id] ?? false}
|
|
124
|
+
{values}
|
|
125
|
+
onToggle={() => onToggleGroup(group.id)}
|
|
126
|
+
inputSnippet={renderInput}
|
|
127
|
+
outputSnippet={renderOutput}
|
|
128
|
+
flat={visibleGroups.length === 1}
|
|
129
|
+
/>
|
|
130
|
+
{/each}
|
|
113
131
|
</div>
|
|
114
|
-
|
|
132
|
+
{/if}
|
|
115
133
|
</div>
|
|
134
|
+
{/snippet}
|
|
135
|
+
|
|
136
|
+
<Tabs.Content value={tab.id} class="min-h-0">
|
|
137
|
+
{#if bare}
|
|
138
|
+
{@render body()}
|
|
139
|
+
{:else}
|
|
140
|
+
<div class="h-full bg-muted dark:bg-background">
|
|
141
|
+
<!-- `auto`, not the default `hover`: a hover-only bar comes and goes with the pointer,
|
|
142
|
+
and a host that freezes mid-solve drops the pointer state with it. -->
|
|
143
|
+
<ScrollArea class="h-full" orientation="vertical" type="auto">
|
|
144
|
+
{@render body()}
|
|
145
|
+
</ScrollArea>
|
|
146
|
+
</div>
|
|
147
|
+
{/if}
|
|
116
148
|
</Tabs.Content>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { TabConfig, SchemaInput, DiscoveredOutput, SupportedTypes } from '@selvajs/schemas';
|
|
1
|
+
import type { TabConfig, SchemaInput, DiscoveredOutput, UISchema, SupportedTypes } from '@selvajs/schemas';
|
|
2
2
|
interface Props {
|
|
3
3
|
tab: TabConfig;
|
|
4
4
|
values: Record<string, unknown>;
|
|
@@ -9,6 +9,10 @@ interface Props {
|
|
|
9
9
|
outputs: DiscoveredOutput[];
|
|
10
10
|
/** Computed value list options keyed by the target dynamic-value-list input id. */
|
|
11
11
|
dynamicOptions?: Record<string, Record<string, string>>;
|
|
12
|
+
/** The schema's icons and thumbnails, which value-list options point into. */
|
|
13
|
+
assets?: UISchema['assets'];
|
|
14
|
+
/** No tinted backdrop and no scroll area of its own: the container scrolls. */
|
|
15
|
+
bare?: boolean;
|
|
12
16
|
}
|
|
13
17
|
declare const TabContent: import("svelte").Component<Props, {}, "">;
|
|
14
18
|
type TabContent = ReturnType<typeof TabContent>;
|
|
@@ -15,9 +15,21 @@
|
|
|
15
15
|
panelFilter?: 'left' | 'right';
|
|
16
16
|
/** Externally request a specific tab to be active (e.g. from collapsed strip click) */
|
|
17
17
|
requestedTabId?: string | null;
|
|
18
|
+
/**
|
|
19
|
+
* Drops the card frame and the tab's own scroll area, for a container that already frames
|
|
20
|
+
* and scrolls it (the phone drawer). Nested, the inner scroll gets only a sliver of height.
|
|
21
|
+
*/
|
|
22
|
+
bare?: boolean;
|
|
18
23
|
}
|
|
19
24
|
|
|
20
|
-
let {
|
|
25
|
+
let {
|
|
26
|
+
schema,
|
|
27
|
+
values,
|
|
28
|
+
onValueChange,
|
|
29
|
+
panelFilter,
|
|
30
|
+
requestedTabId = null,
|
|
31
|
+
bare = false
|
|
32
|
+
}: Props = $props();
|
|
21
33
|
|
|
22
34
|
let activeTabId = $state('');
|
|
23
35
|
let collapsedGroups = $state<Record<string, boolean>>({});
|
|
@@ -66,7 +78,7 @@
|
|
|
66
78
|
}
|
|
67
79
|
</script>
|
|
68
80
|
|
|
69
|
-
|
|
81
|
+
{#snippet tabs()}
|
|
70
82
|
<Tabs.Root value={activeTabId} variant="line" class="min-h-0 flex-1">
|
|
71
83
|
{#if showTabBar}
|
|
72
84
|
<TabBar tabs={visibleTabs} {activeTabId} onTabChange={handleTabChange} />
|
|
@@ -82,7 +94,17 @@
|
|
|
82
94
|
inputs={schema.inputs}
|
|
83
95
|
outputs={schema.outputs}
|
|
84
96
|
{dynamicOptions}
|
|
97
|
+
assets={schema.assets}
|
|
98
|
+
{bare}
|
|
85
99
|
/>
|
|
86
100
|
{/each}
|
|
87
101
|
</Tabs.Root>
|
|
88
|
-
|
|
102
|
+
{/snippet}
|
|
103
|
+
|
|
104
|
+
{#if bare}
|
|
105
|
+
{@render tabs()}
|
|
106
|
+
{:else}
|
|
107
|
+
<Card.Root variant="flush" class="min-h-0 mx-1 flex-1">
|
|
108
|
+
{@render tabs()}
|
|
109
|
+
</Card.Root>
|
|
110
|
+
{/if}
|
|
@@ -7,6 +7,11 @@ interface Props {
|
|
|
7
7
|
panelFilter?: 'left' | 'right';
|
|
8
8
|
/** Externally request a specific tab to be active (e.g. from collapsed strip click) */
|
|
9
9
|
requestedTabId?: string | null;
|
|
10
|
+
/**
|
|
11
|
+
* Drops the card frame and the tab's own scroll area, for a container that already frames
|
|
12
|
+
* and scrolls it (the phone drawer). Nested, the inner scroll gets only a sliver of height.
|
|
13
|
+
*/
|
|
14
|
+
bare?: boolean;
|
|
10
15
|
}
|
|
11
16
|
declare const TabLayout: import("svelte").Component<Props, {}, "">;
|
|
12
17
|
type TabLayout = ReturnType<typeof TabLayout>;
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type { DropdownWidgetConfig, SupportedTypes } from '@selvajs/schemas';
|
|
2
|
+
import type { DropdownWidgetConfig, SupportedTypes, UISchema } from '@selvajs/schemas';
|
|
3
3
|
import { Checkbox } from '../../primitives/checkbox';
|
|
4
|
+
import { optionGlyph } from '../../../schema/option-display';
|
|
5
|
+
import OptionGlyph from './OptionGlyph.svelte';
|
|
4
6
|
import { SvelteSet } from 'svelte/reactivity';
|
|
5
7
|
|
|
6
8
|
interface Props {
|
|
7
9
|
inputId: string;
|
|
8
10
|
value?: string[];
|
|
9
11
|
config?: DropdownWidgetConfig;
|
|
12
|
+
assets?: UISchema['assets'];
|
|
10
13
|
onChange: (value: SupportedTypes) => void;
|
|
11
14
|
disabled?: boolean;
|
|
12
15
|
}
|
|
13
16
|
|
|
14
|
-
let { inputId, value, config, onChange, disabled = false }: Props = $props();
|
|
17
|
+
let { inputId, value, config, assets, onChange, disabled = false }: Props = $props();
|
|
15
18
|
|
|
16
19
|
const options = $derived(config?.options ?? {});
|
|
17
20
|
const selected = $derived(new Set(value ?? []));
|
|
@@ -32,6 +35,7 @@
|
|
|
32
35
|
{@const optionValue = expr ?? name}
|
|
33
36
|
{@const optionId = `${inputId}-${optionValue}`}
|
|
34
37
|
{@const isSelected = selected.has(optionValue)}
|
|
38
|
+
{@const glyph = optionGlyph(config?.optionMedia?.[name], assets)}
|
|
35
39
|
<label
|
|
36
40
|
for={optionId}
|
|
37
41
|
class="gap-3 px-3 py-2 text-sm flex items-center transition-colors select-none hover:bg-accent/50 {isSelected
|
|
@@ -44,6 +48,7 @@
|
|
|
44
48
|
onCheckedChange={(state) => toggle(optionValue, state === true)}
|
|
45
49
|
{disabled}
|
|
46
50
|
/>
|
|
51
|
+
{#if glyph}<OptionGlyph {glyph} class="text-muted-foreground" />{/if}
|
|
47
52
|
<span class="flex-1" class:font-medium={isSelected}>{name}</span>
|
|
48
53
|
</label>
|
|
49
54
|
{/each}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type { DropdownWidgetConfig, SupportedTypes } from '@selvajs/schemas';
|
|
1
|
+
import type { DropdownWidgetConfig, SupportedTypes, UISchema } from '@selvajs/schemas';
|
|
2
2
|
interface Props {
|
|
3
3
|
inputId: string;
|
|
4
4
|
value?: string[];
|
|
5
5
|
config?: DropdownWidgetConfig;
|
|
6
|
+
assets?: UISchema['assets'];
|
|
6
7
|
onChange: (value: SupportedTypes) => void;
|
|
7
8
|
disabled?: boolean;
|
|
8
9
|
}
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type { DropdownWidgetConfig, SupportedTypes } from '@selvajs/schemas';
|
|
2
|
+
import type { DropdownWidgetConfig, SupportedTypes, UISchema } from '@selvajs/schemas';
|
|
3
3
|
import * as Select from '../../primitives/select';
|
|
4
|
+
import { optionGlyph } from '../../../schema/option-display';
|
|
5
|
+
import OptionGlyph from './OptionGlyph.svelte';
|
|
4
6
|
import { getLocaleContext } from '../../../i18n/localeContext.svelte';
|
|
5
7
|
|
|
6
8
|
interface Props {
|
|
7
9
|
value?: string;
|
|
8
10
|
config?: DropdownWidgetConfig;
|
|
11
|
+
assets?: UISchema['assets'];
|
|
9
12
|
onChange: (value: SupportedTypes) => void;
|
|
10
13
|
disabled?: boolean;
|
|
11
14
|
}
|
|
12
15
|
|
|
13
|
-
let { value, config, onChange, disabled = false }: Props = $props();
|
|
16
|
+
let { value, config, assets, onChange, disabled = false }: Props = $props();
|
|
14
17
|
|
|
15
18
|
const locale = getLocaleContext();
|
|
16
19
|
|
|
@@ -19,6 +22,8 @@
|
|
|
19
22
|
const currentLabel = $derived(
|
|
20
23
|
Object.entries(options).find(([_, expr]) => expr === currentValue)?.[0] ?? currentValue
|
|
21
24
|
);
|
|
25
|
+
const glyphOf = (name: string) => optionGlyph(config?.optionMedia?.[name], assets);
|
|
26
|
+
const currentGlyph = $derived(glyphOf(currentLabel));
|
|
22
27
|
</script>
|
|
23
28
|
|
|
24
29
|
<Select.Root
|
|
@@ -33,11 +38,18 @@
|
|
|
33
38
|
{disabled}
|
|
34
39
|
>
|
|
35
40
|
<Select.Trigger class="w-full" {disabled}>
|
|
36
|
-
|
|
41
|
+
<span class="gap-2 min-w-0 flex items-center">
|
|
42
|
+
{#if currentGlyph}<OptionGlyph glyph={currentGlyph} />{/if}
|
|
43
|
+
<span class="truncate">{currentLabel || locale.messages.selectOption}</span>
|
|
44
|
+
</span>
|
|
37
45
|
</Select.Trigger>
|
|
38
46
|
<Select.Content>
|
|
39
47
|
{#each Object.entries(options) as [name, expr] (name)}
|
|
40
|
-
|
|
48
|
+
{@const glyph = glyphOf(name)}
|
|
49
|
+
<Select.Item value={expr ?? name} label={name}>
|
|
50
|
+
{#if glyph}<OptionGlyph {glyph} />{/if}
|
|
51
|
+
{name}
|
|
52
|
+
</Select.Item>
|
|
41
53
|
{/each}
|
|
42
54
|
</Select.Content>
|
|
43
55
|
</Select.Root>
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type { DropdownWidgetConfig, SupportedTypes } from '@selvajs/schemas';
|
|
1
|
+
import type { DropdownWidgetConfig, SupportedTypes, UISchema } from '@selvajs/schemas';
|
|
2
2
|
interface Props {
|
|
3
3
|
value?: string;
|
|
4
4
|
config?: DropdownWidgetConfig;
|
|
5
|
+
assets?: UISchema['assets'];
|
|
5
6
|
onChange: (value: SupportedTypes) => void;
|
|
6
7
|
disabled?: boolean;
|
|
7
8
|
}
|