@juspay/svelte-ui-components 2.80.3 → 2.80.4
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/Banner/Banner.svelte +1 -1
- package/dist/BarChart/BarChart.svelte +2 -1
- package/dist/BarChart/properties.d.ts +6 -0
- package/dist/Combobox/Combobox.svelte +275 -27
- package/dist/Combobox/Combobox.svelte.d.ts +1 -1
- package/dist/Combobox/properties.d.ts +36 -0
- package/dist/DualAxisBarChart/DualAxisBarChart.svelte +2 -1
- package/dist/DualAxisBarChart/properties.d.ts +6 -1
- package/dist/FunnelChart/FunnelChart.svelte +3 -1
- package/dist/FunnelChart/properties.d.ts +7 -0
- package/dist/Input/Input.svelte +59 -1
- package/dist/Input/properties.d.ts +18 -0
- package/dist/LineChart/LineChart.svelte +9 -1
- package/dist/LineChart/properties.d.ts +4 -0
- package/dist/Pill/Pill.svelte +1 -1
- package/dist/SankeyChart/SankeyChart.svelte +4 -2
- package/dist/SankeyChart/properties.d.ts +7 -0
- package/dist/Select/Select.svelte +34 -1
- package/dist/Select/properties.d.ts +7 -0
- package/dist/_chart/ChartTooltip.svelte +1 -1
- package/dist/_chart/Legend.svelte +1 -1
- package/dist/_chart/geometry.js +32 -1
- package/dist/_chart/types.d.ts +10 -0
- package/dist/_chart/types.js +11 -1
- package/package.json +1 -1
|
@@ -114,7 +114,7 @@
|
|
|
114
114
|
line-height: var(--banner-line-height, 1.3);
|
|
115
115
|
border-bottom: var(--banner-border-bottom, none);
|
|
116
116
|
border: var(--banner-border);
|
|
117
|
-
border-radius: var(--banner-border-radius,
|
|
117
|
+
border-radius: var(--banner-border-radius, var(--radius, 4px));
|
|
118
118
|
cursor: var(--banner-cursor, pointer);
|
|
119
119
|
position: var(--banner-position, sticky);
|
|
120
120
|
top: var(--banner-top, 0);
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import { formatNumber } from '../_chart/format';
|
|
19
19
|
import { roundedRectPath } from '../_chart/paths';
|
|
20
20
|
import type { LegendItem, BarRect } from '../_chart/types';
|
|
21
|
+
import { DEFAULT_CHART_CORNER_RADIUS } from '../_chart/types';
|
|
21
22
|
import { SvelteMap } from 'svelte/reactivity';
|
|
22
23
|
|
|
23
24
|
// ── Per-instance uid prefix for <defs> ids (A1-3) ─────────────
|
|
@@ -52,7 +53,7 @@
|
|
|
52
53
|
showYAxis = true,
|
|
53
54
|
showLegend = false,
|
|
54
55
|
barPadding = 0.2,
|
|
55
|
-
barRadius =
|
|
56
|
+
barRadius = DEFAULT_CHART_CORNER_RADIUS,
|
|
56
57
|
aspectRatio = 16 / 9,
|
|
57
58
|
xAxisLabel,
|
|
58
59
|
yAxisLabel,
|
|
@@ -76,6 +76,12 @@ export type OptionalBarChartProperties = {
|
|
|
76
76
|
showXAxis?: boolean;
|
|
77
77
|
showYAxis?: boolean;
|
|
78
78
|
barPadding?: number;
|
|
79
|
+
/**
|
|
80
|
+
* Corner radius on bar/column shapes in pixels. Defaults to
|
|
81
|
+
* `DEFAULT_CHART_CORNER_RADIUS` (4), mirroring the design system's base
|
|
82
|
+
* `--radius` token. SVG `rx`/`ry` cannot read CSS `var()`, so pass this
|
|
83
|
+
* prop explicitly to track a changed `--radius` at runtime.
|
|
84
|
+
*/
|
|
79
85
|
barRadius?: number;
|
|
80
86
|
aspectRatio?: number;
|
|
81
87
|
xAxisLabel?: string;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onMount, tick } from 'svelte';
|
|
3
3
|
import Input from '../Input/Input.svelte';
|
|
4
|
+
import Pill from '../Pill/Pill.svelte';
|
|
4
5
|
import type { ComboboxItem, ComboboxProperties } from './properties';
|
|
5
6
|
|
|
6
7
|
function defaultFilter(item: ComboboxItem, query: string): boolean {
|
|
@@ -29,13 +30,27 @@
|
|
|
29
30
|
inputSuffix,
|
|
30
31
|
dropdownHeader,
|
|
31
32
|
dropdownFooter,
|
|
33
|
+
// multi-select + create/action
|
|
34
|
+
multiple = false,
|
|
35
|
+
selected = $bindable([]),
|
|
36
|
+
maxSelected,
|
|
37
|
+
maxSelectedText,
|
|
38
|
+
pillSnippet,
|
|
39
|
+
allowCreate = false,
|
|
40
|
+
createLabel = (query: string) => `Create "${query}"`,
|
|
41
|
+
action,
|
|
42
|
+
actionIcon,
|
|
32
43
|
onselect,
|
|
33
44
|
oninput,
|
|
34
45
|
onopen,
|
|
35
46
|
onclose,
|
|
36
47
|
onkeydown,
|
|
37
48
|
onfocus,
|
|
38
|
-
onblur
|
|
49
|
+
onblur,
|
|
50
|
+
onchange,
|
|
51
|
+
onadd,
|
|
52
|
+
onremove,
|
|
53
|
+
oncreate
|
|
39
54
|
}: ComboboxProperties = $props();
|
|
40
55
|
|
|
41
56
|
let containerEl: HTMLDivElement | null = $state(null);
|
|
@@ -45,20 +60,63 @@
|
|
|
45
60
|
return inputRef?.getInputRef() ?? null;
|
|
46
61
|
}
|
|
47
62
|
|
|
63
|
+
function focusInput(): void {
|
|
64
|
+
inputRef?.getInputRef()?.focus();
|
|
65
|
+
}
|
|
66
|
+
|
|
48
67
|
const listboxId = `combobox-listbox-${Math.random().toString(36).slice(2, 9)}`;
|
|
49
68
|
|
|
69
|
+
let selectedSet = $derived(new Set(selected));
|
|
70
|
+
let trimmedQuery = $derived(inputValue.trim());
|
|
71
|
+
|
|
50
72
|
let filteredItems: ComboboxItem[] = $derived(
|
|
51
|
-
|
|
73
|
+
items.filter((item) => {
|
|
74
|
+
if (multiple && selectedSet.has(item.id)) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
return inputValue.length > 0 ? filterFn(item, inputValue) : true;
|
|
78
|
+
})
|
|
52
79
|
);
|
|
53
80
|
|
|
54
81
|
let selectableItems: ComboboxItem[] = $derived(
|
|
55
82
|
filteredItems.filter((item) => item.disabled !== true)
|
|
56
83
|
);
|
|
57
84
|
|
|
85
|
+
let exactMatch: ComboboxItem | null = $derived(
|
|
86
|
+
items.find((item) => item.label.toLowerCase() === trimmedQuery.toLowerCase()) ?? null
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
let atLimit = $derived(
|
|
90
|
+
multiple && typeof maxSelected === 'number' && selected.length >= maxSelected
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
let limitText = $derived(
|
|
94
|
+
maxSelectedText ??
|
|
95
|
+
(typeof maxSelected === 'number'
|
|
96
|
+
? `You can select up to ${maxSelected}.`
|
|
97
|
+
: 'Selection limit reached.')
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
let showCreate = $derived(
|
|
101
|
+
allowCreate &&
|
|
102
|
+
!atLimit &&
|
|
103
|
+
trimmedQuery !== '' &&
|
|
104
|
+
exactMatch === null &&
|
|
105
|
+
!(multiple && selectedSet.has(trimmedQuery))
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
// Navigable rows: selectable options (hidden at the limit) → create → action.
|
|
109
|
+
let navItemCount = $derived(atLimit ? 0 : selectableItems.length);
|
|
110
|
+
let createNavIndex = $derived(showCreate ? navItemCount : -1);
|
|
111
|
+
let actionNavIndex = $derived(action ? navItemCount + (showCreate ? 1 : 0) : -1);
|
|
112
|
+
let navCount = $derived(navItemCount + (showCreate ? 1 : 0) + (action ? 1 : 0));
|
|
113
|
+
|
|
58
114
|
let highlightedOptionId: string | null = $derived(
|
|
59
115
|
highlightedIndex >= 0 ? `${listboxId}-option-${highlightedIndex}` : null
|
|
60
116
|
);
|
|
61
117
|
|
|
118
|
+
const labelOf = (id: string): string => items.find((item) => item.id === id)?.label ?? id;
|
|
119
|
+
|
|
62
120
|
function openDropdown() {
|
|
63
121
|
if (disabled || open) {
|
|
64
122
|
return;
|
|
@@ -77,37 +135,75 @@
|
|
|
77
135
|
onclose?.();
|
|
78
136
|
}
|
|
79
137
|
|
|
138
|
+
function emitChange(): void {
|
|
139
|
+
onchange?.([...selected]);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function addValue(id: string): void {
|
|
143
|
+
if (atLimit || selectedSet.has(id)) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
selected = [...selected, id];
|
|
147
|
+
inputValue = '';
|
|
148
|
+
highlightedIndex = -1;
|
|
149
|
+
onadd?.(id);
|
|
150
|
+
emitChange();
|
|
151
|
+
focusInput();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function removeValue(id: string): void {
|
|
155
|
+
if (!selectedSet.has(id)) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
selected = selected.filter((current) => current !== id);
|
|
159
|
+
onremove?.(id);
|
|
160
|
+
emitChange();
|
|
161
|
+
}
|
|
162
|
+
|
|
80
163
|
function selectItem(item: ComboboxItem) {
|
|
81
164
|
if (item.disabled === true) {
|
|
82
165
|
return;
|
|
83
166
|
}
|
|
167
|
+
onselect?.(item);
|
|
168
|
+
if (multiple) {
|
|
169
|
+
addValue(item.id);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
84
172
|
value = item.id;
|
|
85
173
|
inputValue = item.label;
|
|
86
|
-
onselect?.(item);
|
|
87
174
|
closeDropdown();
|
|
88
175
|
}
|
|
89
176
|
|
|
90
|
-
function
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
177
|
+
function create(): void {
|
|
178
|
+
const created = trimmedQuery;
|
|
179
|
+
if (created === '') {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
oncreate?.(created);
|
|
183
|
+
if (multiple) {
|
|
184
|
+
addValue(created);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
value = created;
|
|
188
|
+
inputValue = created;
|
|
189
|
+
closeDropdown();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function runAction(): void {
|
|
193
|
+
action?.onClick();
|
|
194
|
+
if (!action?.keepOpen) {
|
|
195
|
+
closeDropdown();
|
|
99
196
|
}
|
|
100
|
-
return -1;
|
|
101
197
|
}
|
|
102
198
|
|
|
103
199
|
async function moveHighlight(delta: number): Promise<void> {
|
|
104
|
-
if (
|
|
200
|
+
if (navCount === 0) {
|
|
105
201
|
return;
|
|
106
202
|
}
|
|
107
203
|
let next = highlightedIndex + delta;
|
|
108
204
|
if (next < 0) {
|
|
109
|
-
next =
|
|
110
|
-
} else if (next >=
|
|
205
|
+
next = navCount - 1;
|
|
206
|
+
} else if (next >= navCount) {
|
|
111
207
|
next = 0;
|
|
112
208
|
}
|
|
113
209
|
highlightedIndex = next;
|
|
@@ -121,19 +217,38 @@
|
|
|
121
217
|
}
|
|
122
218
|
|
|
123
219
|
function selectHighlighted() {
|
|
124
|
-
if (highlightedIndex < 0 || highlightedIndex >=
|
|
220
|
+
if (highlightedIndex < 0 || highlightedIndex >= navCount) {
|
|
125
221
|
return;
|
|
126
222
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
223
|
+
if (highlightedIndex < navItemCount) {
|
|
224
|
+
const item = selectableItems.at(highlightedIndex);
|
|
225
|
+
if (item) {
|
|
226
|
+
selectItem(item);
|
|
227
|
+
}
|
|
228
|
+
} else if (highlightedIndex === createNavIndex) {
|
|
229
|
+
create();
|
|
230
|
+
} else if (highlightedIndex === actionNavIndex) {
|
|
231
|
+
runAction();
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function getFilteredSelectableIndex(item: ComboboxItem): number {
|
|
236
|
+
let selectableIdx = 0;
|
|
237
|
+
for (let i = 0; i < filteredItems.length; i++) {
|
|
238
|
+
if (filteredItems[i] === item) {
|
|
239
|
+
return filteredItems[i].disabled === true ? -1 : selectableIdx;
|
|
240
|
+
}
|
|
241
|
+
if (filteredItems[i].disabled !== true) {
|
|
242
|
+
selectableIdx++;
|
|
243
|
+
}
|
|
130
244
|
}
|
|
245
|
+
return -1;
|
|
131
246
|
}
|
|
132
247
|
|
|
133
|
-
function handleInput(val: string,
|
|
248
|
+
function handleInput(val: string, event: Event) {
|
|
134
249
|
inputValue = val;
|
|
135
250
|
oninput?.(val);
|
|
136
|
-
inputEventProperties?.onInput?.(val,
|
|
251
|
+
inputEventProperties?.onInput?.(val, event);
|
|
137
252
|
if (!open) {
|
|
138
253
|
openDropdown();
|
|
139
254
|
}
|
|
@@ -167,6 +282,17 @@
|
|
|
167
282
|
if (open && highlightedIndex >= 0) {
|
|
168
283
|
event.preventDefault();
|
|
169
284
|
selectHighlighted();
|
|
285
|
+
} else if (exactMatch && !(multiple && selectedSet.has(exactMatch.id))) {
|
|
286
|
+
event.preventDefault();
|
|
287
|
+
selectItem(exactMatch);
|
|
288
|
+
} else if (showCreate) {
|
|
289
|
+
event.preventDefault();
|
|
290
|
+
create();
|
|
291
|
+
}
|
|
292
|
+
break;
|
|
293
|
+
case 'Backspace':
|
|
294
|
+
if (multiple && inputValue === '' && selected.length > 0) {
|
|
295
|
+
removeValue(selected[selected.length - 1]);
|
|
170
296
|
}
|
|
171
297
|
break;
|
|
172
298
|
case 'Escape':
|
|
@@ -194,6 +320,13 @@
|
|
|
194
320
|
inputEventProperties?.onBlur?.(event);
|
|
195
321
|
}
|
|
196
322
|
|
|
323
|
+
function handleControlClick() {
|
|
324
|
+
if (multiple && !disabled) {
|
|
325
|
+
focusInput();
|
|
326
|
+
openDropdown();
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
197
330
|
function handleClickOutside(event: Event) {
|
|
198
331
|
if (
|
|
199
332
|
event.target instanceof Node &&
|
|
@@ -213,16 +346,26 @@
|
|
|
213
346
|
</script>
|
|
214
347
|
|
|
215
348
|
<div class="combobox {classes ?? ''}" class:disabled bind:this={containerEl} data-pw={testId}>
|
|
216
|
-
|
|
349
|
+
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions -->
|
|
350
|
+
<div class="combobox-input-wrapper" class:multiple onclick={handleControlClick}>
|
|
217
351
|
{#if typeof inputPrefix === 'function'}
|
|
218
352
|
<div class="combobox-input-prefix">{@render inputPrefix()}</div>
|
|
219
353
|
{/if}
|
|
354
|
+
{#if multiple}
|
|
355
|
+
{#each selected as id (id)}
|
|
356
|
+
{#if typeof pillSnippet === 'function'}
|
|
357
|
+
{@render pillSnippet(id, () => !disabled && removeValue(id), disabled)}
|
|
358
|
+
{:else}
|
|
359
|
+
<Pill text={labelOf(id)} dismissible {disabled} ondismiss={() => removeValue(id)} />
|
|
360
|
+
{/if}
|
|
361
|
+
{/each}
|
|
362
|
+
{/if}
|
|
220
363
|
<div class="combobox-input">
|
|
221
364
|
<Input
|
|
222
365
|
{...inputProperties}
|
|
223
366
|
bind:value={inputValue}
|
|
224
367
|
bind:this={inputRef}
|
|
225
|
-
{placeholder}
|
|
368
|
+
placeholder={multiple && selected.length > 0 ? '' : placeholder}
|
|
226
369
|
{name}
|
|
227
370
|
disable={disabled}
|
|
228
371
|
autoComplete="off"
|
|
@@ -249,7 +392,10 @@
|
|
|
249
392
|
{#if typeof dropdownHeader === 'function'}
|
|
250
393
|
<div class="combobox-dropdown-header">{@render dropdownHeader()}</div>
|
|
251
394
|
{/if}
|
|
252
|
-
|
|
395
|
+
|
|
396
|
+
{#if atLimit}
|
|
397
|
+
<div class="combobox-limit" role="alert">{limitText}</div>
|
|
398
|
+
{:else if filteredItems.length === 0 && !showCreate && !action}
|
|
253
399
|
{#if typeof emptySnippet === 'function'}
|
|
254
400
|
{@render emptySnippet()}
|
|
255
401
|
{:else}
|
|
@@ -263,11 +409,11 @@
|
|
|
263
409
|
<div
|
|
264
410
|
class="combobox-option"
|
|
265
411
|
class:highlighted={isHighlighted}
|
|
266
|
-
class:selected={item.id === value}
|
|
412
|
+
class:selected={!multiple && item.id === value}
|
|
267
413
|
class:combobox-option-disabled={item.disabled === true}
|
|
268
414
|
role="option"
|
|
269
415
|
id={`${listboxId}-option-${selectableIndex}`}
|
|
270
|
-
aria-selected={item.id === value}
|
|
416
|
+
aria-selected={!multiple && item.id === value}
|
|
271
417
|
aria-disabled={item.disabled === true ? 'true' : null}
|
|
272
418
|
tabindex="-1"
|
|
273
419
|
onclick={() => selectItem(item)}
|
|
@@ -286,6 +432,56 @@
|
|
|
286
432
|
</div>
|
|
287
433
|
{/each}
|
|
288
434
|
{/if}
|
|
435
|
+
|
|
436
|
+
{#if showCreate}
|
|
437
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
438
|
+
<div
|
|
439
|
+
class="combobox-option combobox-create"
|
|
440
|
+
class:highlighted={highlightedIndex === createNavIndex}
|
|
441
|
+
class:with-divider={navItemCount > 0}
|
|
442
|
+
role="option"
|
|
443
|
+
id={`${listboxId}-option-${createNavIndex}`}
|
|
444
|
+
aria-selected="false"
|
|
445
|
+
tabindex="-1"
|
|
446
|
+
onclick={() => create()}
|
|
447
|
+
onmouseenter={() => (highlightedIndex = createNavIndex)}
|
|
448
|
+
data-pw={typeof testId === 'string' ? `${testId}-create` : null}
|
|
449
|
+
>
|
|
450
|
+
<span class="combobox-create-icon" aria-hidden="true">
|
|
451
|
+
<svg viewBox="0 0 20 20" width="14" height="14" fill="none">
|
|
452
|
+
<path
|
|
453
|
+
d="M10 4v12M4 10h12"
|
|
454
|
+
stroke="currentColor"
|
|
455
|
+
stroke-width="1.7"
|
|
456
|
+
stroke-linecap="round"
|
|
457
|
+
/>
|
|
458
|
+
</svg>
|
|
459
|
+
</span>
|
|
460
|
+
{createLabel(trimmedQuery)}
|
|
461
|
+
</div>
|
|
462
|
+
{/if}
|
|
463
|
+
|
|
464
|
+
{#if action}
|
|
465
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
466
|
+
<div
|
|
467
|
+
class="combobox-option combobox-action"
|
|
468
|
+
class:highlighted={highlightedIndex === actionNavIndex}
|
|
469
|
+
class:with-divider={navItemCount > 0 || showCreate}
|
|
470
|
+
role="option"
|
|
471
|
+
id={`${listboxId}-option-${actionNavIndex}`}
|
|
472
|
+
aria-selected="false"
|
|
473
|
+
tabindex="-1"
|
|
474
|
+
onclick={() => runAction()}
|
|
475
|
+
onmouseenter={() => (highlightedIndex = actionNavIndex)}
|
|
476
|
+
data-pw={typeof testId === 'string' ? `${testId}-action` : null}
|
|
477
|
+
>
|
|
478
|
+
{#if typeof actionIcon === 'function'}
|
|
479
|
+
<span class="combobox-action-icon" aria-hidden="true">{@render actionIcon()}</span>
|
|
480
|
+
{/if}
|
|
481
|
+
{action.label}
|
|
482
|
+
</div>
|
|
483
|
+
{/if}
|
|
484
|
+
|
|
289
485
|
{#if typeof dropdownFooter === 'function'}
|
|
290
486
|
<div class="combobox-dropdown-footer">{@render dropdownFooter()}</div>
|
|
291
487
|
{/if}
|
|
@@ -317,6 +513,14 @@
|
|
|
317
513
|
transition: var(--combobox-input-transition, border-color 0.15s, box-shadow 0.15s);
|
|
318
514
|
}
|
|
319
515
|
|
|
516
|
+
/* Multi-select control: pills wrap above the typeahead input. */
|
|
517
|
+
.combobox-input-wrapper.multiple {
|
|
518
|
+
flex-wrap: wrap;
|
|
519
|
+
gap: var(--combobox-pill-gap, 4px);
|
|
520
|
+
padding: var(--combobox-multiple-padding, 4px 6px);
|
|
521
|
+
cursor: text;
|
|
522
|
+
}
|
|
523
|
+
|
|
320
524
|
.combobox-input-wrapper:hover {
|
|
321
525
|
border-color: var(--combobox-input-hover-border-color, #999999);
|
|
322
526
|
}
|
|
@@ -357,6 +561,12 @@
|
|
|
357
561
|
--input-radius: 0;
|
|
358
562
|
}
|
|
359
563
|
|
|
564
|
+
.combobox-input-wrapper.multiple .combobox-input {
|
|
565
|
+
flex: 1 1 60px;
|
|
566
|
+
min-width: 60px;
|
|
567
|
+
--input-padding: var(--combobox-multiple-input-padding, 2px 4px);
|
|
568
|
+
}
|
|
569
|
+
|
|
360
570
|
.combobox-input::placeholder {
|
|
361
571
|
color: var(--combobox-placeholder-color, #999999);
|
|
362
572
|
}
|
|
@@ -378,6 +588,9 @@
|
|
|
378
588
|
}
|
|
379
589
|
|
|
380
590
|
.combobox-option {
|
|
591
|
+
display: flex;
|
|
592
|
+
align-items: center;
|
|
593
|
+
gap: var(--combobox-option-gap, 8px);
|
|
381
594
|
padding: var(--combobox-option-padding, 8px 12px);
|
|
382
595
|
color: var(--combobox-option-color, #333333);
|
|
383
596
|
font-size: var(--combobox-option-font-size, inherit);
|
|
@@ -414,6 +627,41 @@
|
|
|
414
627
|
pointer-events: none;
|
|
415
628
|
}
|
|
416
629
|
|
|
630
|
+
.combobox-create {
|
|
631
|
+
color: var(--combobox-create-color, #2563eb);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
.combobox-action {
|
|
635
|
+
color: var(--combobox-action-color, #374151);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
.combobox-option.with-divider {
|
|
639
|
+
border-top: var(--combobox-divider, 1px solid #e5e7eb);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
.combobox-create-icon,
|
|
643
|
+
.combobox-action-icon {
|
|
644
|
+
display: inline-flex;
|
|
645
|
+
align-items: center;
|
|
646
|
+
flex-shrink: 0;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
.combobox-action-icon :global(svg) {
|
|
650
|
+
width: 14px;
|
|
651
|
+
height: 14px;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
.combobox-limit {
|
|
655
|
+
display: flex;
|
|
656
|
+
align-items: center;
|
|
657
|
+
gap: 6px;
|
|
658
|
+
padding: var(--combobox-limit-padding, 8px 12px);
|
|
659
|
+
font-size: var(--combobox-limit-font-size, 13px);
|
|
660
|
+
font-weight: 500;
|
|
661
|
+
color: var(--combobox-limit-color, #b45309);
|
|
662
|
+
background: var(--combobox-limit-background, #fffbeb);
|
|
663
|
+
}
|
|
664
|
+
|
|
417
665
|
.combobox-dropdown-header {
|
|
418
666
|
border-bottom: var(--combobox-dropdown-header-border, none);
|
|
419
667
|
padding: var(--combobox-dropdown-header-padding, 0);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ComboboxProperties } from './properties';
|
|
2
2
|
declare const Combobox: import("svelte").Component<ComboboxProperties, {
|
|
3
3
|
getInputRef: () => HTMLInputElement | HTMLTextAreaElement | null;
|
|
4
|
-
}, "value" | "open" | "inputValue" | "highlightedIndex">;
|
|
4
|
+
}, "value" | "open" | "selected" | "inputValue" | "highlightedIndex">;
|
|
5
5
|
type Combobox = ReturnType<typeof Combobox>;
|
|
6
6
|
export default Combobox;
|
|
@@ -5,6 +5,13 @@ export type ComboboxItem = {
|
|
|
5
5
|
label: string;
|
|
6
6
|
disabled?: boolean;
|
|
7
7
|
};
|
|
8
|
+
/** A persistent custom action row rendered at the foot of the dropdown. */
|
|
9
|
+
export type ComboboxAction = {
|
|
10
|
+
label: string;
|
|
11
|
+
onClick: () => void;
|
|
12
|
+
/** Keep the dropdown open after the action runs. Defaults to `false`. */
|
|
13
|
+
keepOpen?: boolean;
|
|
14
|
+
};
|
|
8
15
|
export type ComboboxProperties = MandatoryComboboxProperties & OptionalComboboxProperties & ComboboxEventProperties;
|
|
9
16
|
export type MandatoryComboboxProperties = {
|
|
10
17
|
items: ComboboxItem[];
|
|
@@ -30,6 +37,27 @@ export type OptionalComboboxProperties = {
|
|
|
30
37
|
inputSuffix?: Snippet;
|
|
31
38
|
dropdownHeader?: Snippet;
|
|
32
39
|
dropdownFooter?: Snippet;
|
|
40
|
+
/** Enable multi-select: picked options become removable pills inside the control. */
|
|
41
|
+
multiple?: boolean;
|
|
42
|
+
/** Bindable array of selected ids (multi-select mode). */
|
|
43
|
+
selected?: string[];
|
|
44
|
+
/** Cap the number of selections (multi-select mode). */
|
|
45
|
+
maxSelected?: number;
|
|
46
|
+
/**
|
|
47
|
+
* Message shown in the dropdown once `maxSelected` is reached (option/create rows are hidden).
|
|
48
|
+
* Defaults to "You can select up to {maxSelected}.".
|
|
49
|
+
*/
|
|
50
|
+
maxSelectedText?: string;
|
|
51
|
+
/** Custom pill renderer; receives `(value, remove, disabled)`. */
|
|
52
|
+
pillSnippet?: Snippet<[string, () => void, boolean]>;
|
|
53
|
+
/** Show a "Create …" row when the query has no exact match. Default `false`. */
|
|
54
|
+
allowCreate?: boolean;
|
|
55
|
+
/** Build the create-row label from the current query. */
|
|
56
|
+
createLabel?: (query: string) => string;
|
|
57
|
+
/** A persistent custom action row shown at the foot of the dropdown. */
|
|
58
|
+
action?: ComboboxAction;
|
|
59
|
+
/** Custom leading icon for the persistent action row. */
|
|
60
|
+
actionIcon?: Snippet;
|
|
33
61
|
};
|
|
34
62
|
export type ComboboxEventProperties = {
|
|
35
63
|
onselect?: (item: ComboboxItem) => void;
|
|
@@ -39,4 +67,12 @@ export type ComboboxEventProperties = {
|
|
|
39
67
|
onkeydown?: (event: KeyboardEvent) => void;
|
|
40
68
|
onfocus?: (event: FocusEvent) => void;
|
|
41
69
|
onblur?: (event: FocusEvent) => void;
|
|
70
|
+
/** Multi-select: fires whenever the selection changes (add, remove, or create). */
|
|
71
|
+
onchange?: (selected: string[]) => void;
|
|
72
|
+
/** Multi-select: fires when a value is added. */
|
|
73
|
+
onadd?: (value: string) => void;
|
|
74
|
+
/** Multi-select: fires when a value is removed. */
|
|
75
|
+
onremove?: (value: string) => void;
|
|
76
|
+
/** Fires when the create row is chosen, with the trimmed query. */
|
|
77
|
+
oncreate?: (value: string) => void;
|
|
42
78
|
};
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import { formatNumber } from '../_chart/format';
|
|
15
15
|
import { roundedRectPath, linePath } from '../_chart/paths';
|
|
16
16
|
import type { LegendItem, TooltipData, LinearScale, BandScale, Point } from '../_chart/types';
|
|
17
|
+
import { DEFAULT_CHART_CORNER_RADIUS } from '../_chart/types';
|
|
17
18
|
|
|
18
19
|
// ── Per-instance uid for SVG <defs> ids ────────────────────────
|
|
19
20
|
const uid = Math.random().toString(36).slice(2, 9);
|
|
@@ -27,7 +28,7 @@
|
|
|
27
28
|
rightAxis = {},
|
|
28
29
|
showGridlines = true,
|
|
29
30
|
showLegend = true,
|
|
30
|
-
barRadius =
|
|
31
|
+
barRadius = DEFAULT_CHART_CORNER_RADIUS,
|
|
31
32
|
barPadding = 0.25,
|
|
32
33
|
aspectRatio = 16 / 9,
|
|
33
34
|
minBarHeight = 2,
|
|
@@ -77,7 +77,12 @@ export type OptionalDualAxisBarChartProperties = {
|
|
|
77
77
|
showGridlines?: boolean;
|
|
78
78
|
/** Whether to render the shared legend below the chart. Default `true`. */
|
|
79
79
|
showLegend?: boolean;
|
|
80
|
-
/**
|
|
80
|
+
/**
|
|
81
|
+
* Corner radius on column/bar shapes in pixels. Defaults to
|
|
82
|
+
* `DEFAULT_CHART_CORNER_RADIUS` (4), mirroring the design system's base
|
|
83
|
+
* `--radius` token. SVG `rx`/`ry` cannot read CSS `var()`, so pass this
|
|
84
|
+
* prop explicitly to track a changed `--radius` at runtime.
|
|
85
|
+
*/
|
|
81
86
|
barRadius?: number;
|
|
82
87
|
/** Padding between category bands as a fraction of band width (0–1). Default `0.25`. */
|
|
83
88
|
barPadding?: number;
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import ChartTooltip from '../_chart/ChartTooltip.svelte';
|
|
5
5
|
import { getColor } from '../_chart/colors';
|
|
6
6
|
import { formatNumber, formatPercent } from '../_chart/format';
|
|
7
|
+
import { DEFAULT_CHART_CORNER_RADIUS } from '../_chart/types';
|
|
7
8
|
|
|
8
9
|
// ── Props ──────────────────────────────────────────────────────
|
|
9
10
|
|
|
@@ -16,6 +17,7 @@
|
|
|
16
17
|
showValueLabels = true,
|
|
17
18
|
valueFormat,
|
|
18
19
|
aspectRatio = 16 / 9,
|
|
20
|
+
radius = DEFAULT_CHART_CORNER_RADIUS,
|
|
19
21
|
testId,
|
|
20
22
|
classes,
|
|
21
23
|
empty,
|
|
@@ -249,7 +251,7 @@
|
|
|
249
251
|
width={stageColumnWidth}
|
|
250
252
|
height={bh}
|
|
251
253
|
fill={color}
|
|
252
|
-
rx={
|
|
254
|
+
rx={radius}
|
|
253
255
|
aria-label="{stage.category}: {formatLabel(stage)}"
|
|
254
256
|
onmouseenter={(event) => handleEnter(event, index)}
|
|
255
257
|
onmousemove={trackMouse}
|
|
@@ -21,6 +21,13 @@ export type OptionalFunnelChartProperties = {
|
|
|
21
21
|
* Defaults to a light-teal shared palette neutral.
|
|
22
22
|
*/
|
|
23
23
|
connectorColor?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Corner radius on each stage bar in pixels. Defaults to
|
|
26
|
+
* `DEFAULT_CHART_CORNER_RADIUS` (4), mirroring the design system's base
|
|
27
|
+
* `--radius` token. SVG `rx`/`ry` cannot read CSS `var()`, so pass this
|
|
28
|
+
* prop explicitly to track a changed `--radius` at runtime.
|
|
29
|
+
*/
|
|
30
|
+
radius?: number;
|
|
24
31
|
/**
|
|
25
32
|
* Horizontal width (in SVG user units relative to total inner width) of each
|
|
26
33
|
* trapezoidal slope connector. Larger values produce steeper visual drops between stages.
|
package/dist/Input/Input.svelte
CHANGED
|
@@ -47,7 +47,13 @@
|
|
|
47
47
|
leftIconLabel = 'Leading action',
|
|
48
48
|
rightIconLabel = 'Trailing action',
|
|
49
49
|
mandatory = false,
|
|
50
|
-
forceError = false
|
|
50
|
+
forceError = false,
|
|
51
|
+
rows,
|
|
52
|
+
autoResize = false,
|
|
53
|
+
minRows,
|
|
54
|
+
maxRows,
|
|
55
|
+
resize = 'none',
|
|
56
|
+
showCount = false
|
|
51
57
|
}: InputProperties = $props();
|
|
52
58
|
|
|
53
59
|
export function focus() {
|
|
@@ -104,6 +110,38 @@
|
|
|
104
110
|
const hasLeftIcon = $derived(typeof leftIcon === 'function');
|
|
105
111
|
const hasRightIcon = $derived(typeof rightIcon === 'function');
|
|
106
112
|
|
|
113
|
+
const charCount = $derived(value?.length ?? 0);
|
|
114
|
+
const effectiveResize = $derived(autoResize ? 'none' : resize);
|
|
115
|
+
|
|
116
|
+
// Grow the textarea to fit its content between minRows and maxRows.
|
|
117
|
+
function adjustTextAreaHeight(): void {
|
|
118
|
+
const el = inputElement;
|
|
119
|
+
if (!el || !useTextArea || !autoResize) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
el.style.height = 'auto';
|
|
123
|
+
const styles = window.getComputedStyle(el);
|
|
124
|
+
const lineHeight = parseFloat(styles.lineHeight) || 20;
|
|
125
|
+
const verticalPadding = parseFloat(styles.paddingTop) + parseFloat(styles.paddingBottom);
|
|
126
|
+
const border = parseFloat(styles.borderTopWidth) + parseFloat(styles.borderBottomWidth);
|
|
127
|
+
const lower = minRows ?? rows ?? 2;
|
|
128
|
+
const minHeight = lower * lineHeight + verticalPadding + border;
|
|
129
|
+
const maxHeight =
|
|
130
|
+
maxRows != null ? maxRows * lineHeight + verticalPadding + border : Number.POSITIVE_INFINITY;
|
|
131
|
+
const nextHeight = Math.min(Math.max(el.scrollHeight, minHeight), maxHeight);
|
|
132
|
+
el.style.height = `${nextHeight}px`;
|
|
133
|
+
el.style.overflowY = el.scrollHeight > maxHeight ? 'auto' : 'hidden';
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
137
|
+
$effect(() => {
|
|
138
|
+
// Re-run on every value change (and on mount) while auto-resize is enabled.
|
|
139
|
+
void value;
|
|
140
|
+
if (useTextArea && autoResize) {
|
|
141
|
+
adjustTextAreaHeight();
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
107
145
|
function handleOnInput(event: Event) {
|
|
108
146
|
if (inputElement === null) {
|
|
109
147
|
return;
|
|
@@ -232,8 +270,11 @@
|
|
|
232
270
|
onpaste={handleOnPaste}
|
|
233
271
|
onclick={onClick}
|
|
234
272
|
onkeydown={onKeyDown}
|
|
273
|
+
data-pw={testId}
|
|
235
274
|
class:action-input={actionInput}
|
|
236
275
|
style="--focus-border: {addFocusColor ? 1 : 0}px;"
|
|
276
|
+
style:resize={effectiveResize}
|
|
277
|
+
rows={rows ?? null}
|
|
237
278
|
disabled={disable}
|
|
238
279
|
bind:this={inputElement}
|
|
239
280
|
maxlength={dataType === 'tel' ? null : maxLength}
|
|
@@ -321,6 +362,11 @@
|
|
|
321
362
|
{infoMessage}
|
|
322
363
|
</div>
|
|
323
364
|
{/if}
|
|
365
|
+
{#if useTextArea && showCount && !actionInput}
|
|
366
|
+
<div class="input-char-count" class:at-limit={charCount >= maxLength}>
|
|
367
|
+
{charCount}/{maxLength}
|
|
368
|
+
</div>
|
|
369
|
+
{/if}
|
|
324
370
|
</div>
|
|
325
371
|
|
|
326
372
|
<style>
|
|
@@ -466,6 +512,18 @@
|
|
|
466
512
|
padding: var(--input-info-msg-padding);
|
|
467
513
|
}
|
|
468
514
|
|
|
515
|
+
.input-char-count {
|
|
516
|
+
align-self: flex-end;
|
|
517
|
+
font-size: var(--input-char-count-size, 12px);
|
|
518
|
+
color: var(--input-char-count-color, #98a2b3);
|
|
519
|
+
margin: var(--input-char-count-margin, 4px 0 0);
|
|
520
|
+
font-variant-numeric: tabular-nums;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
.input-char-count.at-limit {
|
|
524
|
+
color: var(--input-char-count-limit-color, var(--input-error-msg-text-color, #fa1405));
|
|
525
|
+
}
|
|
526
|
+
|
|
469
527
|
::placeholder {
|
|
470
528
|
color: var(--input-placeholder-color);
|
|
471
529
|
}
|
|
@@ -22,6 +22,24 @@ export type OptionalInputProperties = {
|
|
|
22
22
|
max?: number;
|
|
23
23
|
actionInput?: boolean;
|
|
24
24
|
useTextArea?: boolean;
|
|
25
|
+
/** Initial visible rows for the textarea (only applies when `useTextArea`). */
|
|
26
|
+
rows?: number;
|
|
27
|
+
/**
|
|
28
|
+
* Grow/shrink the textarea to fit its content between `minRows` and `maxRows`
|
|
29
|
+
* (only when `useTextArea`). Disables manual resizing while active.
|
|
30
|
+
*/
|
|
31
|
+
autoResize?: boolean;
|
|
32
|
+
/** Lower bound (in rows) when `autoResize` is on. Defaults to `rows`. */
|
|
33
|
+
minRows?: number;
|
|
34
|
+
/** Upper bound (in rows) when `autoResize` is on; beyond this the textarea scrolls. */
|
|
35
|
+
maxRows?: number;
|
|
36
|
+
/**
|
|
37
|
+
* Manual resize-handle behaviour for the textarea. Defaults to `'none'` (unchanged from
|
|
38
|
+
* before); forced to `'none'` when `autoResize` is on.
|
|
39
|
+
*/
|
|
40
|
+
resize?: 'none' | 'vertical' | 'horizontal' | 'both';
|
|
41
|
+
/** Show a live `current / maxLength` character counter beneath the field. */
|
|
42
|
+
showCount?: boolean;
|
|
25
43
|
autoComplete?: HTMLInputAttributes['autocomplete'];
|
|
26
44
|
name?: string;
|
|
27
45
|
textTransformers?: TextTransformer[];
|
|
@@ -44,6 +44,8 @@
|
|
|
44
44
|
xTickFormat,
|
|
45
45
|
yTickFormat,
|
|
46
46
|
aspectRatio = 16 / 9,
|
|
47
|
+
minHeight = 0,
|
|
48
|
+
maxHeight = Infinity,
|
|
47
49
|
tooltipSnippet,
|
|
48
50
|
empty,
|
|
49
51
|
highlightedIndex = null,
|
|
@@ -352,7 +354,13 @@
|
|
|
352
354
|
<Legend items={legendItems} position="top" />
|
|
353
355
|
{/if}
|
|
354
356
|
|
|
355
|
-
<ChartContainer
|
|
357
|
+
<ChartContainer
|
|
358
|
+
bind:width={chartWidth}
|
|
359
|
+
bind:height={chartHeight}
|
|
360
|
+
{aspectRatio}
|
|
361
|
+
{minHeight}
|
|
362
|
+
{maxHeight}
|
|
363
|
+
>
|
|
356
364
|
{#if gradientFill || showArea}
|
|
357
365
|
<defs>
|
|
358
366
|
{#each lines as line, si (si)}
|
|
@@ -95,6 +95,10 @@ export type OptionalLineChartProperties = {
|
|
|
95
95
|
yTickFormat?: (value: number | string) => string;
|
|
96
96
|
/** Width-to-height ratio for the chart. */
|
|
97
97
|
aspectRatio?: number;
|
|
98
|
+
/** Minimum chart height in pixels, regardless of computed aspect-ratio height. */
|
|
99
|
+
minHeight?: number;
|
|
100
|
+
/** Maximum chart height in pixels, regardless of computed aspect-ratio height. */
|
|
101
|
+
maxHeight?: number;
|
|
98
102
|
/** Custom tooltip. Receives `{x, points: [{name, y, color, label?}]}`. */
|
|
99
103
|
tooltipSnippet?: Snippet<[LineChartTooltipContext]>;
|
|
100
104
|
/** Content rendered when all series are empty. */
|
package/dist/Pill/Pill.svelte
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { computeSankeyLayout } from '../_chart/geometry';
|
|
6
6
|
import { getColor } from '../_chart/colors';
|
|
7
7
|
import { formatNumber } from '../_chart/format';
|
|
8
|
+
import { DEFAULT_CHART_CORNER_RADIUS } from '../_chart/types';
|
|
8
9
|
import { SvelteMap, SvelteSet } from 'svelte/reactivity';
|
|
9
10
|
|
|
10
11
|
// ── Props ──────────────────────────────────────────────────────
|
|
@@ -18,6 +19,7 @@
|
|
|
18
19
|
showValues = false,
|
|
19
20
|
showLabels = true,
|
|
20
21
|
aspectRatio = 16 / 9,
|
|
22
|
+
radius = DEFAULT_CHART_CORNER_RADIUS,
|
|
21
23
|
maxHeight = Infinity,
|
|
22
24
|
valueFormat,
|
|
23
25
|
tooltipSnippet,
|
|
@@ -343,8 +345,8 @@
|
|
|
343
345
|
y={node.y}
|
|
344
346
|
width={node.width}
|
|
345
347
|
height={node.height}
|
|
346
|
-
rx={
|
|
347
|
-
ry={
|
|
348
|
+
rx={radius}
|
|
349
|
+
ry={radius}
|
|
348
350
|
fill={color}
|
|
349
351
|
onmouseenter={(e) => handleNodeEnter(e, node.id)}
|
|
350
352
|
onmousemove={trackMouse}
|
|
@@ -41,6 +41,13 @@ export type OptionalSankeyChartProperties = {
|
|
|
41
41
|
nodeWidth?: number;
|
|
42
42
|
nodePadding?: number;
|
|
43
43
|
iterations?: number;
|
|
44
|
+
/**
|
|
45
|
+
* Corner radius on each node rect in pixels. Defaults to
|
|
46
|
+
* `DEFAULT_CHART_CORNER_RADIUS` (4), mirroring the design system's base
|
|
47
|
+
* `--radius` token. SVG `rx`/`ry` cannot read CSS `var()`, so pass this
|
|
48
|
+
* prop explicitly to track a changed `--radius` at runtime.
|
|
49
|
+
*/
|
|
50
|
+
radius?: number;
|
|
44
51
|
showValues?: boolean;
|
|
45
52
|
showLabels?: boolean;
|
|
46
53
|
aspectRatio?: number;
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
optionIndicator,
|
|
18
18
|
showSelectAll = false,
|
|
19
19
|
selectAllLabel = 'Select all',
|
|
20
|
+
showSelectedTick = false,
|
|
20
21
|
triggerSummary,
|
|
21
22
|
testId,
|
|
22
23
|
itemTestId,
|
|
@@ -443,6 +444,7 @@
|
|
|
443
444
|
<div
|
|
444
445
|
class="select-option"
|
|
445
446
|
class:multi={multiple}
|
|
447
|
+
class:tickable={showSelectedTick && !multiple}
|
|
446
448
|
class:selected={value.includes(row.item.id)}
|
|
447
449
|
class:highlighted={index === highlightedIndex}
|
|
448
450
|
role="option"
|
|
@@ -478,7 +480,11 @@
|
|
|
478
480
|
</span>
|
|
479
481
|
{/if}
|
|
480
482
|
{/if}
|
|
481
|
-
{row.item.label}
|
|
483
|
+
<span class="select-option-label">{row.item.label}</span>
|
|
484
|
+
{#if showSelectedTick && !multiple && value.includes(row.item.id)}
|
|
485
|
+
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
486
|
+
<span class="select-option-tick" aria-hidden="true">{@html checkmarkSvg}</span>
|
|
487
|
+
{/if}
|
|
482
488
|
</div>
|
|
483
489
|
{/if}
|
|
484
490
|
{/each}
|
|
@@ -740,4 +746,31 @@
|
|
|
740
746
|
background: var(--select-ghost-trigger-open-background, rgba(0, 0, 0, 0.06));
|
|
741
747
|
box-shadow: none;
|
|
742
748
|
}
|
|
749
|
+
|
|
750
|
+
/* Single-select right-edge tick (showSelectedTick) */
|
|
751
|
+
.select-option.tickable {
|
|
752
|
+
display: flex;
|
|
753
|
+
align-items: center;
|
|
754
|
+
gap: 8px;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
.select-option.tickable .select-option-label {
|
|
758
|
+
flex: 1 1 auto;
|
|
759
|
+
min-width: 0;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
.select-option-tick {
|
|
763
|
+
display: inline-flex;
|
|
764
|
+
align-items: center;
|
|
765
|
+
justify-content: center;
|
|
766
|
+
flex-shrink: 0;
|
|
767
|
+
width: var(--select-option-tick-size, 16px);
|
|
768
|
+
height: var(--select-option-tick-size, 16px);
|
|
769
|
+
color: var(--select-option-tick-color, #2563eb);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
.select-option-tick :global(svg) {
|
|
773
|
+
width: 100%;
|
|
774
|
+
height: 100%;
|
|
775
|
+
}
|
|
743
776
|
</style>
|
|
@@ -40,6 +40,13 @@ export type OptionalSelectProperties = {
|
|
|
40
40
|
showSelectAll?: boolean;
|
|
41
41
|
/** Label for the `showSelectAll` row. Defaults to `'Select all'`. */
|
|
42
42
|
selectAllLabel?: string;
|
|
43
|
+
/**
|
|
44
|
+
* Single-select only: when `true`, the currently selected option shows a
|
|
45
|
+
* checkmark at its right edge. No effect in `multiple` mode (which already
|
|
46
|
+
* renders a checkbox indicator). Themeable via `--select-option-tick-size`
|
|
47
|
+
* and `--select-option-tick-color`. Defaults to `false`.
|
|
48
|
+
*/
|
|
49
|
+
showSelectedTick?: boolean;
|
|
43
50
|
testId?: string;
|
|
44
51
|
/** Fallback per-option test id prefix. Each option emits `data-pw="{itemTestId}-{id}"` when its own `item.testId` is not set. */
|
|
45
52
|
itemTestId?: string;
|
package/dist/_chart/geometry.js
CHANGED
|
@@ -128,7 +128,7 @@ export function computeSankeyLayout(nodes, links, width, height, nodeWidth = 16,
|
|
|
128
128
|
nodeY.set(id, Math.max(0, weightedY - (nodeH.get(id) ?? 0) / 2));
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
|
-
// Resolve overlaps
|
|
131
|
+
// Resolve overlaps: push down from the top.
|
|
132
132
|
ids.sort((a, b) => (nodeY.get(a) ?? 0) - (nodeY.get(b) ?? 0));
|
|
133
133
|
let y = 0;
|
|
134
134
|
for (const id of ids) {
|
|
@@ -138,6 +138,37 @@ export function computeSankeyLayout(nodes, links, width, height, nodeWidth = 16,
|
|
|
138
138
|
}
|
|
139
139
|
y = (nodeY.get(id) ?? 0) + (nodeH.get(id) ?? 0) + nodePadding;
|
|
140
140
|
}
|
|
141
|
+
// Resolve overlaps: pull back up from the bottom. The push-down pass
|
|
142
|
+
// above only ever grows a column's block downward, so a fan-out whose
|
|
143
|
+
// members share a weighted target centre drifts past the column's
|
|
144
|
+
// height budget column-to-column instead of staying level. Sweep from
|
|
145
|
+
// the last node up, clamping each node's bottom edge to the running
|
|
146
|
+
// boundary, mirroring d3-sankey's bidirectional resolveCollisions.
|
|
147
|
+
let bottomBoundary = height;
|
|
148
|
+
for (let index = ids.length - 1; index >= 0; index--) {
|
|
149
|
+
const id = ids[index];
|
|
150
|
+
const nodeBottom = (nodeY.get(id) ?? 0) + (nodeH.get(id) ?? 0);
|
|
151
|
+
if (nodeBottom > bottomBoundary) {
|
|
152
|
+
nodeY.set(id, bottomBoundary - (nodeH.get(id) ?? 0));
|
|
153
|
+
}
|
|
154
|
+
bottomBoundary = (nodeY.get(id) ?? 0) - nodePadding;
|
|
155
|
+
}
|
|
156
|
+
// Re-centre the column's node group within [0, height]: once overlaps
|
|
157
|
+
// are resolved, anchor the block at the midpoint of its remaining
|
|
158
|
+
// slack rather than leaving it wherever the top-down/bottom-up sweeps
|
|
159
|
+
// happened to land it, so a cluster sharing a weighted centre reads as
|
|
160
|
+
// centred on that target instead of stacked toward one edge.
|
|
161
|
+
const firstId = ids[0];
|
|
162
|
+
const lastId = ids[ids.length - 1];
|
|
163
|
+
const groupTop = nodeY.get(firstId) ?? 0;
|
|
164
|
+
const groupBottom = (nodeY.get(lastId) ?? 0) + (nodeH.get(lastId) ?? 0);
|
|
165
|
+
const idealGroupTop = Math.max(0, (height - (groupBottom - groupTop)) / 2);
|
|
166
|
+
const recentreShift = idealGroupTop - groupTop;
|
|
167
|
+
if (recentreShift !== 0) {
|
|
168
|
+
for (const id of ids) {
|
|
169
|
+
nodeY.set(id, (nodeY.get(id) ?? 0) + recentreShift);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
141
172
|
}
|
|
142
173
|
}
|
|
143
174
|
// Build computed nodes
|
package/dist/_chart/types.d.ts
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
2
|
import type { BarChartDataPoint } from '../BarChart/properties';
|
|
3
|
+
/**
|
|
4
|
+
* Corner radius (px) shared by every chart shape (bar/column rects, funnel
|
|
5
|
+
* stage bars, sankey nodes). Mirrors Lighthouse's `--radius` design token
|
|
6
|
+
* (0.25rem = 4px at the 16px root). SVG `rx`/`ry` attributes and the
|
|
7
|
+
* `roundedRectPath()` curve builder in `_chart/paths.ts` consume plain JS
|
|
8
|
+
* numbers, so this constant is the chart-layer equivalent of `var(--radius)`
|
|
9
|
+
* for surfaces CSS cannot reach. Consumers who need runtime sync to a
|
|
10
|
+
* *changed* `--radius` should pass the corresponding radius prop explicitly.
|
|
11
|
+
*/
|
|
12
|
+
export declare const DEFAULT_CHART_CORNER_RADIUS = 4;
|
|
3
13
|
export type Margin = {
|
|
4
14
|
top: number;
|
|
5
15
|
right: number;
|
package/dist/_chart/types.js
CHANGED
|
@@ -1 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
// ── Shared constants ──────────────────────────────────────────
|
|
2
|
+
/**
|
|
3
|
+
* Corner radius (px) shared by every chart shape (bar/column rects, funnel
|
|
4
|
+
* stage bars, sankey nodes). Mirrors Lighthouse's `--radius` design token
|
|
5
|
+
* (0.25rem = 4px at the 16px root). SVG `rx`/`ry` attributes and the
|
|
6
|
+
* `roundedRectPath()` curve builder in `_chart/paths.ts` consume plain JS
|
|
7
|
+
* numbers, so this constant is the chart-layer equivalent of `var(--radius)`
|
|
8
|
+
* for surfaces CSS cannot reach. Consumers who need runtime sync to a
|
|
9
|
+
* *changed* `--radius` should pass the corresponding radius prop explicitly.
|
|
10
|
+
*/
|
|
11
|
+
export const DEFAULT_CHART_CORNER_RADIUS = 4;
|