svelte-fluentui 1.0.0-rc15 → 1.0.0-rc16
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/README.md +22 -9
- package/dist/assets/styles/_variables.scss +14 -0
- package/dist/assets/styles/theme.scss +14 -0
- package/dist/components/AccordionItem.svelte +1 -1
- package/dist/components/Anchor.svelte +2 -1
- package/dist/components/Autocomplete.svelte +138 -48
- package/dist/components/Autocomplete.svelte.d.ts +2 -0
- package/dist/components/Badge.svelte +2 -1
- package/dist/components/Button.svelte +10 -2
- package/dist/components/Button.svelte.d.ts +3 -1
- package/dist/components/Calendar.svelte +39 -36
- package/dist/components/Calendar.svelte.d.ts +1 -0
- package/dist/components/Checkbox.svelte +3 -1
- package/dist/components/Dialog.svelte +4 -3
- package/dist/components/GridCellEditor.svelte +20 -12
- package/dist/components/InputFile.svelte +4 -3
- package/dist/components/Listbox.svelte +2 -0
- package/dist/components/NumberField.svelte +1 -0
- package/dist/components/Option.svelte +2 -1
- package/dist/components/Paginator.svelte +9 -1
- package/dist/components/QuickGrid.svelte +2949 -2901
- package/dist/components/QuickGrid.svelte.d.ts +37 -6
- package/dist/components/SiteSettings.svelte +8 -11
- package/dist/components/Switch.svelte +1 -0
- package/dist/components/Tabs.svelte +1 -1
- package/dist/components/TextField.svelte +6 -2
- package/dist/components/TextField.svelte.d.ts +1 -0
- package/dist/components/Textarea.svelte +1 -0
- package/dist/components/TimePicker.svelte +5 -3
- package/dist/components/Toast.svelte +7 -0
- package/dist/components/ToastContainer.svelte +2 -1
- package/dist/components/Tooltip.svelte +1 -0
- package/dist/components/layout/GridItem.svelte +1 -1
- package/dist/components/layout/MultiSplitter.svelte +1 -1
- package/dist/components/layout/MultiSplitterPane.svelte +8 -5
- package/dist/components/layout/Stack.svelte +3 -3
- package/dist/components/layout/TopNav.svelte +3 -3
- package/dist/components/layout/TopNav.svelte.d.ts +2 -3
- package/dist/components/nav/AppBar.svelte +1 -1
- package/dist/components/nav/NavGroup.svelte +1 -1
- package/dist/components/nav/NavItem.svelte +3 -2
- package/dist/components/nav/NavLink.svelte +18 -4
- package/dist/components/nav/NavLink.svelte.d.ts +3 -0
- package/dist/fluent-ui/calendar/range-of-dates.js +1 -1
- package/dist/main.css +10 -0
- package/dist/main.css.map +1 -1
- package/dist/stores/settings.js +72 -1
- package/dist/stores/theme.js +3 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,15 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
A comprehensive Svelte wrapper library for Microsoft FluentUI web components (v2.6.x), providing a seamless way to use FluentUI components in Svelte applications.
|
|
4
4
|
|
|
5
|
-
## What's New in v1.0.0-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
-
|
|
9
|
-
- **`
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
- **
|
|
5
|
+
## What's New in v1.0.0-rc16
|
|
6
|
+
|
|
7
|
+
### QuickGrid
|
|
8
|
+
- **Per-row-type editability** — `column.isEditable` (renamed from `column.editable`) now accepts a `(row) => boolean` callback so heterogeneous trees can express "only some row types are editable" without the consumer hand-rolling click handlers. Two shapes: per-column (`isEditable: (row) => row.kind === "employee"` — different columns can be editable for different row types) and grid-level (`isRowEditable: (row) => boolean` — single predicate gating the whole row). Three-stage gate: master `editable` → `isRowEditable` → `column.isEditable`. Navigate-mode Tab traversal is row-aware so the cursor skips read-only cells correctly. New demo at `/components/quickgrid-tree` ("Editable per row type — teams vs employees")
|
|
9
|
+
- **`column.nowrap` prop** — body cells never wrap. Combine with `maxWidth` for ellipsis truncation, with `autoWidth` to size the column to `max(header, longest cell)`, or use alone for short identifiers like SKU/code columns. Demo on `/components/quickgrid` shows all three modes
|
|
10
|
+
- **BREAKING — `column.editable` renamed to `column.isEditable`** — boolean form is unchanged; the function form is additive. Migration: rename `editable: true` → `isEditable: true` on every column literal. Grid-level `editable` prop is unchanged
|
|
11
|
+
- **Component-prefixed CSS variables** — `--fluent-quickgrid-header-bg`, `-header-hover-bg`, `-header-sorted-bg`, `-stripe-bg`, `-row-hover-bg` decouple QuickGrid surfaces from the generic `--neutral-layer-*` palette. Themes that overrode `--neutral-layer-2` for navbar branding (e.g. DHL yellow) used to drag QuickGrid header + striped rows along at full saturation; consumers can now tint the grid independently
|
|
12
|
+
- **Sort indicator tracks accent** — the `▲` / `▼` triangle on the actively sorted column uses `var(--accent-fill-rest)` so theme switches propagate to it
|
|
13
|
+
- **Bug fix: body cells inherit width constraints** — `width` / `minWidth` / `maxWidth` / `columnMinWidth` were applied only to `<th>`, so a wide body cell could drag the column past the header's `max-width`. Now mirrored to `<td>` via `getColumnBodyStyle()`. Fixes the case where ellipsis wouldn't activate on `nowrap` columns
|
|
14
|
+
|
|
15
|
+
### Autocomplete
|
|
16
|
+
- **`startIcon` and `endIcon` snippets** — decorative slots inside the input. `startIcon` renders before, `endIcon` renders after but yields to the auto-rendered clear button / loading spinner so a search icon swaps cleanly to a clear button on selection. New demo on `/components/autocomplete`
|
|
17
|
+
- **Chip-size CSS variables** — eight `--fluent-autocomplete-chip-*` tokens (font-size, line-height, padding, gap, max-width, etc.) drive both inline and external chip geometry, defaults in rem. Override globally via `:root { --fluent-autocomplete-chip-font-size: 0.75rem; }`
|
|
18
|
+
- **Inline chip size unified with external** — was 12px font inline / 14px font external; picking a value visually shrunk the chip. Now both use the same `--fluent-autocomplete-chip-font-size` token (default `0.875rem`)
|
|
19
|
+
- **Focus border follows FluentUI 2** — was painting all four sides accent + outer glow; now only the bottom edge turns accent (the standard FluentUI textfield convention). Matches the Blazor reference and avoids layout shift on focus
|
|
20
|
+
- **Bug fix: single-select replaces instead of appends** — `selectOption` was always appending, which silently wedged single-select mode after the second click (input went blank with two values held in state)
|
|
21
|
+
- **Bug fix: dropdown closes on outside scroll** — `PositioningRegion` doesn't re-anchor on scroll, so scrolling the page used to leave the popover floating detached from its anchor. Now a capture-phase scroll listener closes it when scrolling outside the input or popover surface
|
|
22
|
+
|
|
23
|
+
### Tabs / Site settings
|
|
24
|
+
- **`Tabs` active tab text tracks accent** — `.fluent-tab.active` color changed to `var(--accent-fill-rest)` so the active label matches its bottom indicator instead of staying neutral gray
|
|
25
|
+
- **Site settings store applies to the document** — pushes every change to `document.documentElement` (`data-theme`, `dir`, `--accent-fill-*`, `--foreground-on-accent-*`, `--neutral-layer-1`, mirrored `--fluent-*` tokens). Theme mode `'system'` resolves via `matchMedia` and re-applies on OS-level theme changes. Foreground-on-accent is computed via W3C luminance so dark accents get white labels and light accents (PowerBI yellow) get black labels
|
|
26
|
+
- **`theme` store localStorage key namespaced** — `"theme"` → `"fluent-theme"` to prevent collisions with host applications using the same generic key (e.g. PureAdmin saves its theme name to `localStorage.theme`)
|
|
14
27
|
|
|
15
28
|
## Features
|
|
16
29
|
|
|
@@ -50,6 +50,13 @@ $footer-bg-light: $neutral-white !default;
|
|
|
50
50
|
$footer-text-light: $neutral-gray-110 !default;
|
|
51
51
|
$footer-border-light: $neutral-gray-40 !default;
|
|
52
52
|
|
|
53
|
+
// QuickGrid
|
|
54
|
+
$quickgrid-header-bg-light: $neutral-gray-20 !default;
|
|
55
|
+
$quickgrid-header-hover-bg-light: $neutral-gray-30 !default;
|
|
56
|
+
$quickgrid-header-sorted-bg-light: $neutral-gray-30 !default;
|
|
57
|
+
$quickgrid-stripe-bg-light: $neutral-gray-10 !default;
|
|
58
|
+
$quickgrid-row-hover-bg-light: $neutral-gray-30 !default;
|
|
59
|
+
|
|
53
60
|
// ===== DARK THEME DEFAULTS =====
|
|
54
61
|
|
|
55
62
|
// Background colors
|
|
@@ -94,6 +101,13 @@ $footer-bg-dark: $neutral-gray-150 !default;
|
|
|
94
101
|
$footer-text-dark: $neutral-gray-20 !default;
|
|
95
102
|
$footer-border-dark: $neutral-gray-100 !default;
|
|
96
103
|
|
|
104
|
+
// QuickGrid
|
|
105
|
+
$quickgrid-header-bg-dark: $neutral-gray-120 !default;
|
|
106
|
+
$quickgrid-header-hover-bg-dark: $neutral-gray-110 !default;
|
|
107
|
+
$quickgrid-header-sorted-bg-dark: $neutral-gray-110 !default;
|
|
108
|
+
$quickgrid-stripe-bg-dark: $neutral-gray-130 !default;
|
|
109
|
+
$quickgrid-row-hover-bg-dark: $neutral-gray-110 !default;
|
|
110
|
+
|
|
97
111
|
// ===== COMPONENT-SPECIFIC VARIABLES =====
|
|
98
112
|
|
|
99
113
|
// Spacing
|
|
@@ -52,6 +52,13 @@
|
|
|
52
52
|
--fluent-footer-text: #{$footer-text-light};
|
|
53
53
|
--fluent-footer-border: #{$footer-border-light};
|
|
54
54
|
|
|
55
|
+
// QuickGrid
|
|
56
|
+
--fluent-quickgrid-header-bg: #{$quickgrid-header-bg-light};
|
|
57
|
+
--fluent-quickgrid-header-hover-bg: #{$quickgrid-header-hover-bg-light};
|
|
58
|
+
--fluent-quickgrid-header-sorted-bg: #{$quickgrid-header-sorted-bg-light};
|
|
59
|
+
--fluent-quickgrid-stripe-bg: #{$quickgrid-stripe-bg-light};
|
|
60
|
+
--fluent-quickgrid-row-hover-bg: #{$quickgrid-row-hover-bg-light};
|
|
61
|
+
|
|
55
62
|
// Component spacing
|
|
56
63
|
--fluent-component-padding: #{$component-padding};
|
|
57
64
|
--fluent-component-margin: #{$component-margin};
|
|
@@ -132,6 +139,13 @@
|
|
|
132
139
|
--fluent-footer-bg: #{$footer-bg-dark};
|
|
133
140
|
--fluent-footer-text: #{$footer-text-dark};
|
|
134
141
|
--fluent-footer-border: #{$footer-border-dark};
|
|
142
|
+
|
|
143
|
+
// QuickGrid
|
|
144
|
+
--fluent-quickgrid-header-bg: #{$quickgrid-header-bg-dark};
|
|
145
|
+
--fluent-quickgrid-header-hover-bg: #{$quickgrid-header-hover-bg-dark};
|
|
146
|
+
--fluent-quickgrid-header-sorted-bg: #{$quickgrid-header-sorted-bg-dark};
|
|
147
|
+
--fluent-quickgrid-stripe-bg: #{$quickgrid-stripe-bg-dark};
|
|
148
|
+
--fluent-quickgrid-row-hover-bg: #{$quickgrid-row-hover-bg-dark};
|
|
135
149
|
}
|
|
136
150
|
|
|
137
151
|
[data-theme="dhl"] {
|
|
@@ -64,7 +64,8 @@
|
|
|
64
64
|
}
|
|
65
65
|
</script>
|
|
66
66
|
|
|
67
|
-
<!-- svelte-ignore a11y_no_static_element_interactions
|
|
67
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
68
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
68
69
|
<fluent-anchor
|
|
69
70
|
bind:this={element}
|
|
70
71
|
{id}
|
|
@@ -49,6 +49,12 @@
|
|
|
49
49
|
headerContent?: Snippet
|
|
50
50
|
footerContent?: Snippet
|
|
51
51
|
optionTemplate?: Snippet<[OptionItem<T>]>
|
|
52
|
+
// Decorative slots inside the input. Render arbitrary content (typically icons or
|
|
53
|
+
// small buttons) at the start (left) or end (right) of the input. The auto-rendered
|
|
54
|
+
// clear button and loading spinner take precedence in the end slot — `endIcon`
|
|
55
|
+
// renders only when neither is active, so a search icon doesn't fight a clear button.
|
|
56
|
+
startIcon?: Snippet
|
|
57
|
+
endIcon?: Snippet
|
|
52
58
|
class?: string
|
|
53
59
|
style?: string
|
|
54
60
|
initialSearchQuery?: string
|
|
@@ -89,6 +95,8 @@
|
|
|
89
95
|
headerContent = undefined,
|
|
90
96
|
footerContent = undefined,
|
|
91
97
|
optionTemplate = undefined,
|
|
98
|
+
startIcon = undefined,
|
|
99
|
+
endIcon = undefined,
|
|
92
100
|
class: className = "",
|
|
93
101
|
style = "",
|
|
94
102
|
initialSearchQuery = "",
|
|
@@ -204,16 +212,20 @@
|
|
|
204
212
|
function selectOption(option: OptionItem) {
|
|
205
213
|
if (disabled || readonly || option.disabled) return
|
|
206
214
|
|
|
207
|
-
if (maxSelectedOptions && selectedOptions.length >= maxSelectedOptions) {
|
|
215
|
+
if (effectiveMultiple && maxSelectedOptions && selectedOptions.length >= maxSelectedOptions) {
|
|
208
216
|
return
|
|
209
217
|
}
|
|
210
218
|
|
|
211
|
-
|
|
219
|
+
// Single-select replaces the previous value; multi-select appends.
|
|
220
|
+
selectedOptions = effectiveMultiple
|
|
221
|
+
? [...selectedOptions, option.value]
|
|
222
|
+
: [option.value]
|
|
212
223
|
searchText = ""
|
|
213
224
|
filteredOptions = []
|
|
214
225
|
highlightedIndex = -1
|
|
215
226
|
|
|
216
|
-
|
|
227
|
+
// Always close after a single-select pick; multi-select honors keepOpen / max-cap.
|
|
228
|
+
if (!effectiveMultiple || !keepOpen || (maxSelectedOptions && selectedOptions.length >= maxSelectedOptions)) {
|
|
217
229
|
closeDropdown()
|
|
218
230
|
}
|
|
219
231
|
|
|
@@ -344,12 +356,31 @@
|
|
|
344
356
|
}
|
|
345
357
|
}
|
|
346
358
|
|
|
347
|
-
//
|
|
359
|
+
// Close dropdown on outside scroll. The options list itself is scrollable
|
|
360
|
+
// (max-height + overflow-y: auto) and is portaled out via PositioningRegion,
|
|
361
|
+
// so scrolling inside the listbox must not trigger this. Scrolling anywhere
|
|
362
|
+
// else on the page would otherwise leave the dropdown floating at its old
|
|
363
|
+
// anchor position, since PositioningRegion does not re-anchor on scroll.
|
|
364
|
+
function handleOutsideScroll(event: Event) {
|
|
365
|
+
const target = event.target as Node | null
|
|
366
|
+
if (!target) return
|
|
367
|
+
// Allow scroll inside our own anchor container (input + inline chips, etc.)
|
|
368
|
+
if (containerElement && containerElement.contains(target)) return
|
|
369
|
+
// Allow scroll inside the popover surface (options-list lives in a portal).
|
|
370
|
+
if (target instanceof Element && target.closest('.options-list, .positioning-region')) return
|
|
371
|
+
closeDropdown()
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Add click-outside and outside-scroll listeners
|
|
348
375
|
$effect(() => {
|
|
349
376
|
if (isOpen) {
|
|
350
377
|
document.addEventListener("click", handleClickOutside)
|
|
378
|
+
// `capture: true` because most page scroll containers don't bubble
|
|
379
|
+
// scroll events to document; capture phase reliably catches them.
|
|
380
|
+
window.addEventListener("scroll", handleOutsideScroll, true)
|
|
351
381
|
return () => {
|
|
352
382
|
document.removeEventListener("click", handleClickOutside)
|
|
383
|
+
window.removeEventListener("scroll", handleOutsideScroll, true)
|
|
353
384
|
}
|
|
354
385
|
}
|
|
355
386
|
})
|
|
@@ -446,7 +477,10 @@
|
|
|
446
477
|
{/if}
|
|
447
478
|
|
|
448
479
|
<!-- Input container -->
|
|
449
|
-
<!-- svelte-ignore a11y_click_events_have_key_events
|
|
480
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
481
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
482
|
+
<!-- svelte-ignore a11y_role_has_required_aria_props -->
|
|
483
|
+
<!-- svelte-ignore a11y_interactive_supports_focus -->
|
|
450
484
|
<div
|
|
451
485
|
class="autocomplete-input-container"
|
|
452
486
|
class:inline-mode={tagsPosition === 'inline'}
|
|
@@ -460,6 +494,13 @@
|
|
|
460
494
|
aria-expanded={isOpen}
|
|
461
495
|
aria-haspopup="listbox"
|
|
462
496
|
>
|
|
497
|
+
<!-- Start icon slot (e.g. search magnifying glass) -->
|
|
498
|
+
{#if startIcon}
|
|
499
|
+
<div class="input-start">
|
|
500
|
+
{@render startIcon()}
|
|
501
|
+
</div>
|
|
502
|
+
{/if}
|
|
503
|
+
|
|
463
504
|
<!-- Tags INLINE (if tagsPosition === 'inline') -->
|
|
464
505
|
{#if tagsPosition === 'inline' && showTags}
|
|
465
506
|
{#each selectedOptions as value}
|
|
@@ -483,6 +524,7 @@
|
|
|
483
524
|
{/if}
|
|
484
525
|
|
|
485
526
|
<!-- Native input element -->
|
|
527
|
+
<!-- svelte-ignore a11y_autofocus -->
|
|
486
528
|
<input
|
|
487
529
|
type="text"
|
|
488
530
|
class="autocomplete-native-input"
|
|
@@ -493,36 +535,42 @@
|
|
|
493
535
|
readonly={readonly}
|
|
494
536
|
{required}
|
|
495
537
|
autofocus={autofocus}
|
|
496
|
-
autocomplete={autocomplete || 'off'}
|
|
538
|
+
autocomplete={(autocomplete || 'off') as AutoFill}
|
|
497
539
|
oninput={handleInput}
|
|
498
540
|
onkeydown={handleKeyDown}
|
|
499
541
|
onfocus={handleInputFocus}
|
|
500
542
|
onblur={handleInputBlur}
|
|
501
543
|
/>
|
|
502
544
|
|
|
503
|
-
<!-- End slot: clear button
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
545
|
+
<!-- End slot: clear button OR loading indicator OR custom endIcon (in that priority).
|
|
546
|
+
Only renders when something will show inside, so the `:has(.input-end)` padding
|
|
547
|
+
rule on the input doesn't reserve space for an empty slot. -->
|
|
548
|
+
{#if (hasSingleSelection && !disabled && !readonly) || showLoading || endIcon}
|
|
549
|
+
<div class="input-end">
|
|
550
|
+
{#if hasSingleSelection && !disabled && !readonly}
|
|
551
|
+
<button
|
|
552
|
+
type="button"
|
|
553
|
+
class="clear-button"
|
|
554
|
+
onclick={clearSingleSelection}
|
|
555
|
+
aria-label="Clear selection"
|
|
556
|
+
title="Clear selection"
|
|
557
|
+
>
|
|
558
|
+
<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor">
|
|
559
|
+
<path d="M2.09 2.22a.75.75 0 0 1 1.06-.13L6 4.94l2.85-2.85a.75.75 0 1 1 1.06 1.06L7.06 6l2.85 2.85a.75.75 0 1 1-1.06 1.06L6 7.06l-2.85 2.85a.75.75 0 0 1-1.06-1.06L4.94 6 2.09 3.15a.75.75 0 0 1-.13-1.06z"/>
|
|
560
|
+
</svg>
|
|
561
|
+
</button>
|
|
562
|
+
{:else if showLoading}
|
|
563
|
+
<div class="loading-indicator">
|
|
564
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" class="spinner">
|
|
565
|
+
<circle cx="8" cy="8" r="7" stroke="currentColor" stroke-width="2" fill="none" opacity="0.25"/>
|
|
566
|
+
<path d="M8 1a7 7 0 0 1 7 7" stroke="currentColor" stroke-width="2" fill="none"/>
|
|
567
|
+
</svg>
|
|
568
|
+
</div>
|
|
569
|
+
{:else if endIcon}
|
|
570
|
+
{@render endIcon()}
|
|
571
|
+
{/if}
|
|
572
|
+
</div>
|
|
573
|
+
{/if}
|
|
526
574
|
</div>
|
|
527
575
|
|
|
528
576
|
<!-- Tags BELOW (if tagsPosition === 'below') -->
|
|
@@ -648,8 +696,12 @@
|
|
|
648
696
|
}
|
|
649
697
|
|
|
650
698
|
.autocomplete-input-container.inline-mode.focused:not(.disabled) {
|
|
651
|
-
|
|
652
|
-
|
|
699
|
+
/* FluentUI 2 textfield focus: thin gray border on top/sides stays put,
|
|
700
|
+
bottom edge becomes accent + visually 2px thick. We use an inset box-shadow
|
|
701
|
+
to add the 1px instead of bumping border-bottom-width — that would shift
|
|
702
|
+
the input's content by 1px on focus. */
|
|
703
|
+
border-bottom-color: var(--accent-fill-rest, #0078d4);
|
|
704
|
+
box-shadow: inset 0 -1px 0 0 var(--accent-fill-rest, #0078d4);
|
|
653
705
|
}
|
|
654
706
|
|
|
655
707
|
.autocomplete-input-container.inline-mode.disabled {
|
|
@@ -679,18 +731,26 @@
|
|
|
679
731
|
box-shadow: 0 1px 0 0 var(--accent-fill-rest, #0078d4);
|
|
680
732
|
}
|
|
681
733
|
|
|
682
|
-
/* ===== Inline Chips =====
|
|
734
|
+
/* ===== Inline Chips =====
|
|
735
|
+
Inline chips share the same height + typography as external chips so picking a value
|
|
736
|
+
doesn't visually shrink it on its way into the input. The only intentional difference
|
|
737
|
+
is the border: inline chips sit inside the input's own border so adding another would
|
|
738
|
+
double up; external chips are standalone elements and need their own.
|
|
739
|
+
|
|
740
|
+
Sizing tokens (`--fluent-autocomplete-chip-*`) drive both inline and external chip
|
|
741
|
+
geometry, so theme overrides apply everywhere chips render. Defaults are in rem so
|
|
742
|
+
chips scale with the user's root font-size. */
|
|
683
743
|
.inline-chip {
|
|
684
744
|
display: inline-flex;
|
|
685
745
|
align-items: center;
|
|
686
|
-
gap:
|
|
687
|
-
padding:
|
|
746
|
+
gap: var(--fluent-autocomplete-chip-gap, 0.25rem);
|
|
747
|
+
padding: var(--fluent-autocomplete-chip-padding-y, 0.125rem) var(--fluent-autocomplete-chip-padding-x, 0.5rem);
|
|
688
748
|
background: var(--neutral-fill-secondary-rest, #f0f0f0);
|
|
689
749
|
border-radius: calc(var(--control-corner-radius, 4) * 1px);
|
|
690
|
-
font-size:
|
|
691
|
-
line-height: 1.4;
|
|
750
|
+
font-size: var(--fluent-autocomplete-chip-font-size, 0.875rem);
|
|
751
|
+
line-height: var(--fluent-autocomplete-chip-line-height, 1.4);
|
|
692
752
|
white-space: nowrap;
|
|
693
|
-
max-width:
|
|
753
|
+
max-width: var(--fluent-autocomplete-chip-max-width, 9.375rem);
|
|
694
754
|
color: var(--neutral-foreground-rest, #242424);
|
|
695
755
|
}
|
|
696
756
|
|
|
@@ -705,10 +765,10 @@
|
|
|
705
765
|
justify-content: center;
|
|
706
766
|
background: transparent;
|
|
707
767
|
border: none;
|
|
708
|
-
padding:
|
|
768
|
+
padding: var(--fluent-autocomplete-chip-remove-padding, 0.125rem);
|
|
709
769
|
cursor: pointer;
|
|
710
770
|
color: var(--neutral-foreground-hint, #717171);
|
|
711
|
-
border-radius:
|
|
771
|
+
border-radius: 0.125rem;
|
|
712
772
|
flex-shrink: 0;
|
|
713
773
|
transition: color 0.1s ease, background 0.1s ease;
|
|
714
774
|
}
|
|
@@ -765,8 +825,9 @@
|
|
|
765
825
|
}
|
|
766
826
|
|
|
767
827
|
.autocomplete-input-container:not(.inline-mode) .autocomplete-native-input:focus {
|
|
768
|
-
|
|
769
|
-
|
|
828
|
+
/* See comment on `.inline-mode.focused` — same FluentUI bottom-emphasis pattern. */
|
|
829
|
+
border-bottom-color: var(--accent-fill-rest, #0078d4);
|
|
830
|
+
box-shadow: inset 0 -1px 0 0 var(--accent-fill-rest, #0078d4);
|
|
770
831
|
}
|
|
771
832
|
|
|
772
833
|
.autocomplete-input-container:not(.inline-mode).filled .autocomplete-native-input {
|
|
@@ -776,12 +837,22 @@
|
|
|
776
837
|
border-radius: calc(var(--control-corner-radius, 4) * 1px) calc(var(--control-corner-radius, 4) * 1px) 0 0;
|
|
777
838
|
}
|
|
778
839
|
|
|
840
|
+
/* ===== Input Start Slot ===== */
|
|
841
|
+
.input-start {
|
|
842
|
+
flex-shrink: 0;
|
|
843
|
+
display: flex;
|
|
844
|
+
align-items: center;
|
|
845
|
+
padding-right: 4px;
|
|
846
|
+
color: var(--neutral-foreground-hint, #707070);
|
|
847
|
+
}
|
|
848
|
+
|
|
779
849
|
/* ===== Input End Slot ===== */
|
|
780
850
|
.input-end {
|
|
781
851
|
flex-shrink: 0;
|
|
782
852
|
display: flex;
|
|
783
853
|
align-items: center;
|
|
784
854
|
padding-left: 4px;
|
|
855
|
+
color: var(--neutral-foreground-hint, #707070);
|
|
785
856
|
}
|
|
786
857
|
|
|
787
858
|
/* Non-inline mode: position end slot inside input */
|
|
@@ -792,6 +863,22 @@
|
|
|
792
863
|
transform: translateY(-50%);
|
|
793
864
|
}
|
|
794
865
|
|
|
866
|
+
.autocomplete-input-container:not(.inline-mode) .input-start {
|
|
867
|
+
position: absolute;
|
|
868
|
+
left: 8px;
|
|
869
|
+
top: 50%;
|
|
870
|
+
transform: translateY(-50%);
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
/* Non-inline mode: pad the input so its text doesn't sit underneath the icons */
|
|
874
|
+
.autocomplete-input-container:not(.inline-mode):has(.input-start) .autocomplete-native-input {
|
|
875
|
+
padding-left: 32px;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
.autocomplete-input-container:not(.inline-mode):has(.input-end) .autocomplete-native-input {
|
|
879
|
+
padding-right: 32px;
|
|
880
|
+
}
|
|
881
|
+
|
|
795
882
|
/* ===== Selected Options Container (above/below modes) ===== */
|
|
796
883
|
.selected-options {
|
|
797
884
|
display: flex;
|
|
@@ -802,17 +889,20 @@
|
|
|
802
889
|
overflow-y: auto;
|
|
803
890
|
}
|
|
804
891
|
|
|
805
|
-
/* External chips (above/below modes) - FluentUI Blazor style
|
|
892
|
+
/* External chips (above/below modes) - FluentUI Blazor style.
|
|
893
|
+
Same `--fluent-autocomplete-chip-*` tokens as inline chips. Adds a 1px border
|
|
894
|
+
since these aren't sitting inside the input's own border. */
|
|
806
895
|
.external-chip {
|
|
807
896
|
display: inline-flex;
|
|
808
897
|
align-items: center;
|
|
809
|
-
gap:
|
|
810
|
-
padding:
|
|
898
|
+
gap: var(--fluent-autocomplete-chip-gap, 0.25rem);
|
|
899
|
+
padding: var(--fluent-autocomplete-chip-padding-y, 0.125rem) var(--fluent-autocomplete-chip-padding-x, 0.5rem);
|
|
811
900
|
background: var(--neutral-fill-secondary-rest, #f5f5f5);
|
|
812
901
|
color: var(--neutral-foreground-rest, #242424);
|
|
813
902
|
border: 1px solid var(--neutral-stroke-rest, #d1d1d1);
|
|
814
903
|
border-radius: calc(var(--control-corner-radius, 4) * 1px);
|
|
815
|
-
font-size:
|
|
904
|
+
font-size: var(--fluent-autocomplete-chip-font-size, 0.875rem);
|
|
905
|
+
line-height: var(--fluent-autocomplete-chip-line-height, 1.4);
|
|
816
906
|
font-weight: 400;
|
|
817
907
|
white-space: nowrap;
|
|
818
908
|
}
|
|
@@ -820,7 +910,7 @@
|
|
|
820
910
|
.external-chip .chip-text {
|
|
821
911
|
overflow: hidden;
|
|
822
912
|
text-overflow: ellipsis;
|
|
823
|
-
max-width:
|
|
913
|
+
max-width: var(--fluent-autocomplete-chip-text-max-width, 12.5rem);
|
|
824
914
|
}
|
|
825
915
|
|
|
826
916
|
.external-chip .chip-remove {
|
|
@@ -829,10 +919,10 @@
|
|
|
829
919
|
justify-content: center;
|
|
830
920
|
background: transparent;
|
|
831
921
|
border: none;
|
|
832
|
-
padding:
|
|
922
|
+
padding: var(--fluent-autocomplete-chip-remove-padding, 0.125rem);
|
|
833
923
|
cursor: pointer;
|
|
834
924
|
color: var(--error-foreground-rest, #c42b1c);
|
|
835
|
-
border-radius:
|
|
925
|
+
border-radius: 0.125rem;
|
|
836
926
|
}
|
|
837
927
|
|
|
838
928
|
.external-chip .chip-remove:hover {
|
|
@@ -62,7 +62,8 @@
|
|
|
62
62
|
})
|
|
63
63
|
</script>
|
|
64
64
|
|
|
65
|
-
<!-- svelte-ignore a11y_no_static_element_interactions
|
|
65
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
66
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
66
67
|
<span
|
|
67
68
|
class={classes}
|
|
68
69
|
style={computedStyle || null}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
type Props = {
|
|
8
8
|
appearance?: string
|
|
9
9
|
autofocus?: boolean
|
|
10
|
-
disabled?:
|
|
10
|
+
disabled?: boolean
|
|
11
11
|
form?: string
|
|
12
12
|
formaction?: string
|
|
13
13
|
formenctype?: string
|
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
end?: SlotType
|
|
22
22
|
children?: SlotType
|
|
23
23
|
style?: string
|
|
24
|
+
class?: string
|
|
25
|
+
"aria-label"?: string
|
|
24
26
|
onclick?: (ev: MouseEvent) => void
|
|
25
27
|
}
|
|
26
28
|
|
|
@@ -41,11 +43,15 @@
|
|
|
41
43
|
end = undefined,
|
|
42
44
|
children = undefined,
|
|
43
45
|
style= "",
|
|
46
|
+
class: className = undefined,
|
|
47
|
+
"aria-label": ariaLabel = undefined,
|
|
44
48
|
onclick = undefined
|
|
45
49
|
}: Props = $props()
|
|
46
50
|
</script>
|
|
47
51
|
|
|
48
|
-
<!-- svelte-ignore a11y_no_static_element_interactions
|
|
52
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
53
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
54
|
+
<!-- svelte-ignore a11y_autofocus -->
|
|
49
55
|
<fluent-button
|
|
50
56
|
{type}
|
|
51
57
|
{name}
|
|
@@ -60,6 +66,8 @@
|
|
|
60
66
|
{formtarget}
|
|
61
67
|
{disabled}
|
|
62
68
|
{style}
|
|
69
|
+
class={className}
|
|
70
|
+
aria-label={ariaLabel}
|
|
63
71
|
{onclick}
|
|
64
72
|
>
|
|
65
73
|
{#if start}
|
|
@@ -2,7 +2,7 @@ import type { SlotType } from "../types/index.js";
|
|
|
2
2
|
type Props = {
|
|
3
3
|
appearance?: string;
|
|
4
4
|
autofocus?: boolean;
|
|
5
|
-
disabled?:
|
|
5
|
+
disabled?: boolean;
|
|
6
6
|
form?: string;
|
|
7
7
|
formaction?: string;
|
|
8
8
|
formenctype?: string;
|
|
@@ -16,6 +16,8 @@ type Props = {
|
|
|
16
16
|
end?: SlotType;
|
|
17
17
|
children?: SlotType;
|
|
18
18
|
style?: string;
|
|
19
|
+
class?: string;
|
|
20
|
+
"aria-label"?: string;
|
|
19
21
|
onclick?: (ev: MouseEvent) => void;
|
|
20
22
|
};
|
|
21
23
|
declare const Button: import("svelte").Component<Props, {}, "">;
|