svelte-fluentui 1.0.0-rc07 → 1.0.0-rc15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +544 -0
  2. package/dist/actions/portal.d.ts +16 -0
  3. package/dist/actions/portal.js +24 -0
  4. package/dist/assets/styles/components.scss +13 -0
  5. package/dist/assets/styles/fluent-blazor-compat.scss +13 -9
  6. package/dist/assets/styles/theme.scss +14 -0
  7. package/dist/components/Autocomplete.svelte +945 -909
  8. package/dist/components/Autocomplete.svelte.d.ts +1 -0
  9. package/dist/components/Calendar.svelte +22 -5
  10. package/dist/components/Calendar.svelte.d.ts +6 -0
  11. package/dist/components/Combobox.svelte +300 -259
  12. package/dist/components/Combobox.svelte.d.ts +1 -0
  13. package/dist/components/ContextMenu.svelte +592 -0
  14. package/dist/components/ContextMenu.svelte.d.ts +30 -0
  15. package/dist/components/DatePicker.svelte +93 -30
  16. package/dist/components/DatePicker.svelte.d.ts +15 -1
  17. package/dist/components/Dialog.svelte +213 -37
  18. package/dist/components/Dialog.svelte.d.ts +18 -0
  19. package/dist/components/GridCellEditor.svelte +1005 -1015
  20. package/dist/components/MenuButton.svelte +312 -0
  21. package/dist/components/MenuButton.svelte.d.ts +61 -0
  22. package/dist/components/PositioningRegion.svelte +13 -4
  23. package/dist/components/PositioningRegion.svelte.d.ts +3 -0
  24. package/dist/components/QuickGrid.svelte +552 -70
  25. package/dist/components/QuickGrid.svelte.d.ts +99 -9
  26. package/dist/components/Select.svelte +21 -0
  27. package/dist/components/SiteSettings.svelte +1 -1
  28. package/dist/components/Slider.svelte +101 -7
  29. package/dist/components/Slider.svelte.d.ts +22 -2
  30. package/dist/components/Tab.svelte +68 -80
  31. package/dist/components/Tab.svelte.d.ts +2 -3
  32. package/dist/components/Tabs.svelte +1226 -55
  33. package/dist/components/Tabs.svelte.d.ts +56 -1
  34. package/dist/components/TextField.svelte +17 -2
  35. package/dist/components/TextField.svelte.d.ts +2 -0
  36. package/dist/components/Textarea.svelte +1 -1
  37. package/dist/components/TimePicker.svelte +124 -30
  38. package/dist/components/TimePicker.svelte.d.ts +18 -1
  39. package/dist/components/Tooltip.svelte +435 -31
  40. package/dist/components/Tooltip.svelte.d.ts +8 -4
  41. package/dist/components/layout/Grid.svelte +20 -2
  42. package/dist/components/layout/Grid.svelte.d.ts +2 -0
  43. package/dist/components/layout/TopNav.svelte +3 -3
  44. package/dist/fluent-ui/calendar/fluent-calendar-extended.d.ts +1 -1
  45. package/dist/fluent-ui/calendar/fluent-calendar-extended.js +5 -2
  46. package/dist/global.d.ts +11 -0
  47. package/dist/global.js +13 -0
  48. package/dist/index.d.ts +7 -0
  49. package/dist/index.js +7 -0
  50. package/dist/main.css +29 -12
  51. package/dist/main.css.map +1 -1
  52. package/dist/version.d.ts +1 -0
  53. package/dist/version.js +4 -0
  54. package/dist/vite/vite-plugin-fluentui-icons.js +4 -6
  55. package/package.json +8 -5
package/README.md ADDED
@@ -0,0 +1,544 @@
1
+ # Svelte FluentUI
2
+
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
+
5
+ ## What's New in v1.0.0-rc15
6
+
7
+ - **`QuickGrid` tree mode** — pass `treePathMember="path"` and mark one column with `isTree: true` to render hierarchical data with indentation + expand/collapse. Supports PostgreSQL ltree (`"1.2.3"`), POSIX (`"/1/2/3"`), and Windows (`"C:\foo\bar"`) path styles with auto-detection. Optional `treeLevelMember` / `treeParentMember` skip path parsing when those values are pre-computed in the database. `treeDataSorted` skips internal sort. `expandedPaths` is `$bindable`. `defaultExpandDepth` is interpreted relative to the dataset's shallowest level so subtree views always show their roots. Filter is ancestor-aware. Optional `treeDoubleClickBehavior="toggle"` lets users smash the row instead of aiming at the chevron
8
+ - **`QuickGrid` per-column `filter` callback** — `(filterValue: string, row: T) => boolean | null` replaces the built-in substring match for one column. Use for numeric ranges, date ranges, regex, multi-field search. Returning `null` signals "input is syntactically incomplete" — the grid then ignores the filter (all rows pass) AND adds a `.invalid` class to the input so the user gets a red border instead of an empty grid
9
+ - **`QuickGrid` grid-level `onfilterchange`** — `(filters: Record<string, string>) => void` for server-side filtering. When set, internal filtering is fully bypassed; the grid still renders the inputs, fires this on every keystroke, and trusts the caller to update `items`
10
+ - **`QuickGrid` `idMember` prop** — stable row identity. Drafts and invalid-cell markers now key by this value (or `treePathMember` in tree mode, or displayed-row index as a last-resort fallback that triggers a one-shot console warning) so they survive pagination, filter, sort, and tree expand/collapse re-orderings
11
+ - **`QuickGrid` `columnMinWidth` prop** — default `min-width` for any column without its own. Stops content-sized columns from collapsing too far when paired with `fillerColumn`
12
+ - **`QuickGrid` `fillerColumn` actually absorbs leftover space now** — was `width: auto` (a no-op for absorption), now `width: 100%`. Affects every demo using `fillerColumn`, not just tree
13
+ - **BREAKING — `QuickGrid` `invalidCells` shape: `rowIndex` → `rowKey`** — bindable `invalidCells: CellValidationState[]` now carries `rowKey: string` instead of `rowIndex: number`, matching the new stable-id keying. Migration: callers binding `invalidCells` need to read `c.rowKey` instead of `c.rowIndex`. The `onvalidationerror` callback's `detail.rowIndex` is unchanged
14
+
15
+ ## Features
16
+
17
+ - 🎨 **Complete FluentUI Component Set** - Wraps all major FluentUI web components
18
+ - 🔧 **TypeScript Support** - Full type definitions for all components
19
+ - 📱 **Responsive Design** - Built-in responsive layout components (incl. responsive `Tabs` with scroll/wrap/menu modes)
20
+ - 🎯 **Svelte 5 Compatible** - Works with the latest Svelte features
21
+ - 🎨 **SCSS & Tailwind CSS** - Flexible styling options
22
+ - 📦 **Tree-shakeable** - Import only what you need
23
+ - ✨ **FluentUI Blazor Inspired** - Advanced components like Calendar, DatePicker, TimePicker, InputFile, Autocomplete
24
+ - 💾 **Navigation Persistence** - Sidebar menu state persists across page reloads with localStorage
25
+ - 🎯 **Active Route Highlighting** - Current page automatically highlighted in navigation
26
+ - 🔔 **Toast Notifications** - Programmatic toast service with multiple positions and auto-dismiss
27
+ - 🗂 **Centralized z-index scale + portaled overlays** - Dialogs, popovers and tooltips render through `document.body` so they escape ancestor stacking contexts; every overlay uses a shared `--fluent-z-*` token scale with baked-in fallbacks
28
+ - 🪟 **`portal` action** - Utility action exported for consumer-built overlays that need to escape parent stacking contexts
29
+ - 🔌 **Global runtime API** - `window.components["svelte-fluentui"].version()` exposed at runtime for introspection (mirrors the pattern used by sister packages)
30
+
31
+ ## Highlights
32
+
33
+ - **Unified form label styling** - TextField, Select, Autocomplete, Radio, RadioGroup and Textarea all share a single canonical `.fluent-label` class so labels look identical across every field
34
+ - **Autocomplete height parity** - The custom Autocomplete now matches FluentUI text field / select height (32px) using the same `--base-height-multiplier × --design-unit` tokens
35
+ - **Ctrl+Space in Autocomplete** - Press `Ctrl+Space` (or `⌘+Space` on Mac) to force-open the dropdown with all available options
36
+ - **Dialog** - Full Fluent-style layout: `title` / `header` slot, right-aligned footer, `primaryAction` / `secondaryAction` shorthand, or custom `footer` snippet. Portaled to `<body>` so it escapes ancestor stacking contexts
37
+ - **DatePicker & TimePicker** - Calendar/clock popups anchored correctly to their inputs, natural-width popups (no more stretch-to-input-width), outside-click + `Escape` dismissal, `minDate`/`maxDate` / `minTime`/`maxTime`, `firstDayOfWeek`, `disabledDateFunc`, `autoClose`, bindable `open`
38
+ - **Calendar** - `firstDayOfWeek` override, `selectableDates` allow-list, `onPickerMonthChange` callback
39
+ - **Tabs** - Pure-Svelte implementation (no more shadow-DOM wrestling) with animated full-tab-width active indicator. `responsive="scroll" | "wrap" | "menu"` — `scroll` auto-shows `‹ ›` arrow buttons on overflow; `menu` collapses overflow into a `⋯` dropdown that swaps the picked tab into the strip on select, with ellipsis truncation on the borderline tab. Full keyboard nav, vertical orientation with `stripWidth` sidebar sizing + auto-ellipsis labels, swipe-to-navigate on the panels (opt out via `swipe={false}`), per-tab `title` tooltips, and `justify` for equal-width stretching
40
+ - **MenuButton** - Button that opens a dropdown menu on click instead of a one-off `onclick`. Takes `items: MenuButtonItem[]` (label, icon, disabled, visible, danger, dividerBefore, onclick), forwards button props, exposes bindable `open`. Positioned with Floating UI — auto-flips when near viewport edges, shifts to stay in bounds, and caps height with internal scroll when tight. `position="bottom" | "top"` controls the preferred side
41
+ - **ContextMenu** - Standalone right-click menu that opens at the cursor. Wrap any element with `<ContextMenu items={…}>…</ContextMenu>` and right-click inside opens a Floating-UI-positioned menu. Supports **inline expandable sections** (`children` + `expandable: true`, with a rotating chevron) for sidebar-style nav menus *and* **side-opening submenus** (nested `children`) with hover-intent delays, auto-flip away from the viewport edge, and arbitrary nesting. `offsetMenuX` / `offsetMenuY` push the menu away from the cursor so the click position doesn't land on the first item. Closes on scroll, click-outside, or Escape (Escape pops submenus one level at a time)
42
+ - **Slider** - Proper wrapper with `bind:value`, `min`/`max`/`step`, `orientation`, `onchange`/`oninput`, labels
43
+ - **Navigation Persistence** - Sidebar menu state automatically saved to localStorage and restored on page reload
44
+ - **Active Route Highlighting** - Current page is automatically highlighted in the navigation menu
45
+ - **Toast Service** - Programmatic notifications with `toast.success()`, `toast.error()`, etc. - 6 positions, progress bars, auto-dismiss
46
+ - **InputFile** - Drag-and-drop file upload with validation and progress tracking
47
+ - **Autocomplete** - Multiple selection with tag/chip display, async search with AbortSignal, initial options, Ctrl+Space to show all
48
+ - **QuickGrid** - Advanced data grid with sorting, filtering, pagination, editable rows, row toolbar, context menu, tree mode with ltree-style paths, per-column custom filter predicates, and stable row identity via `idMember`
49
+ - **Three-State Checkbox** - Checkbox with indeterminate state support
50
+ - **Responsive Layout** - Complete layout system with Grid, Stack, and responsive components
51
+
52
+ ## Installation
53
+
54
+ ```bash
55
+ npm install svelte-fluentui
56
+ ```
57
+
58
+ ## Quick Start
59
+
60
+ ```svelte
61
+ <script>
62
+ import { Button, TextField, Card } from 'svelte-fluentui'
63
+ </script>
64
+
65
+ <Card>
66
+ <TextField placeholder="Enter your name" />
67
+ <Button appearance="accent">Submit</Button>
68
+ </Card>
69
+ ```
70
+
71
+ ## Available Components
72
+
73
+ ### Form Controls
74
+ - `Button` - Various button styles and appearances
75
+ - `TextField` - Text input with validation
76
+ - `NumberField` - Numeric input control
77
+ - `Textarea` - Multi-line text input
78
+ - `Checkbox` - Checkbox input with three-state support
79
+ - `Radio` / `RadioGroup` - Radio button controls
80
+ - `Switch` - Toggle switch
81
+ - `Select` - Dropdown selection
82
+ - `Combobox` - Searchable dropdown
83
+ - `Autocomplete` - Multiple selection with tags/chips (inspired by FluentUI Blazor)
84
+ - `Slider` - Range slider control
85
+ - `Search` - Search input field
86
+ - `DatePicker` - Date selection with calendar popup (inspired by FluentUI Blazor)
87
+ - `TimePicker` - Time selection with hour/minute/second picker (inspired by FluentUI Blazor)
88
+ - `InputFile` - File upload with drag-drop and progress tracking (inspired by FluentUI Blazor)
89
+
90
+ ### Data Display
91
+ - `DataGrid` / `DataGridRow` / `DataGridCell` - Data table components
92
+ - `QuickGrid` - Advanced data grid with sorting, filtering, pagination, inline editing, tree mode, and custom filter predicates
93
+ - `Card` - Content container
94
+ - `Badge` - Status indicators
95
+ - `ProgressBar` - Progress indication
96
+ - `Tooltip` - Contextual information
97
+ - `Calendar` - Date picker and calendar
98
+ - `Paginator` - Pagination control
99
+
100
+ ### Navigation
101
+ - `Tabs` / `Tab` / `TabPanel` - Tab navigation
102
+ - `Breadcrumb` / `BreadcrumbItem` - Breadcrumb navigation
103
+ - `Menu` / `MenuItem` - Context menus
104
+ - `AppBar` / `AppBarItem` - Application bar
105
+ - `NavMenu` / `NavItem` / `NavLink` - Navigation components
106
+ - `Anchor` - Link component
107
+
108
+ ### Layout
109
+ - `Stack` - Flexible layout container
110
+ - `Layout` - Page layout wrapper
111
+ - `Header` / `Footer` / `BodyContent` - Layout sections
112
+ - `Spacer` - Spacing utility
113
+ - `Divider` - Visual separator
114
+
115
+ ### Feedback
116
+ - `Dialog` - Modal dialogs
117
+ - `Toast` - Declarative notification messages
118
+ - `ToastContainer` + `toast` - Programmatic toast service (success, error, warning, info)
119
+ - `Accordion` / `AccordionItem` - Collapsible content
120
+
121
+ ### Utilities
122
+ - `Listbox` / `Option` - List selection
123
+ - `Tree` / `TreeItem` - Hierarchical data
124
+ - `Toolbar` - Action toolbars
125
+
126
+ ## Usage Examples
127
+
128
+ ### Form with Validation
129
+ ```svelte
130
+ <script>
131
+ import { TextField, Button, Stack } from 'svelte-fluentui'
132
+
133
+ let email = ''
134
+ let password = ''
135
+ </script>
136
+
137
+ <Stack orientation="vertical" gap="16">
138
+ <TextField
139
+ bind:value={email}
140
+ type="email"
141
+ placeholder="Enter email"
142
+ required
143
+ />
144
+ <TextField
145
+ bind:value={password}
146
+ type="password"
147
+ placeholder="Enter password"
148
+ required
149
+ />
150
+ <Button appearance="accent">Sign In</Button>
151
+ </Stack>
152
+ ```
153
+
154
+ ### Data Grid
155
+ ```svelte
156
+ <script>
157
+ import { DataGrid, DataGridRow, DataGridCell } from 'svelte-fluentui'
158
+
159
+ const users = [
160
+ { name: 'Alice', email: 'alice@example.com', role: 'Admin' },
161
+ { name: 'Bob', email: 'bob@example.com', role: 'User' }
162
+ ]
163
+ </script>
164
+
165
+ <DataGrid>
166
+ {#each users as user}
167
+ <DataGridRow>
168
+ <DataGridCell>{user.name}</DataGridCell>
169
+ <DataGridCell>{user.email}</DataGridCell>
170
+ <DataGridCell>{user.role}</DataGridCell>
171
+ </DataGridRow>
172
+ {/each}
173
+ </DataGrid>
174
+ ```
175
+
176
+ ### QuickGrid with Sorting & Filtering
177
+ ```svelte
178
+ <script lang="ts">
179
+ import { QuickGrid } from 'svelte-fluentui'
180
+
181
+ type User = {
182
+ id: number
183
+ name: string
184
+ email: string
185
+ role: string
186
+ }
187
+
188
+ const users: User[] = [
189
+ { id: 1, name: 'Alice', email: 'alice@example.com', role: 'Admin' },
190
+ { id: 2, name: 'Bob', email: 'bob@example.com', role: 'User' },
191
+ { id: 3, name: 'Carol', email: 'carol@example.com', role: 'User' }
192
+ ]
193
+
194
+ const columns = [
195
+ { field: 'id', title: 'ID', width: '80px', sortable: true },
196
+ { field: 'name', title: 'Name', sortable: true, filterable: true },
197
+ { field: 'email', title: 'Email', filterable: true },
198
+ { field: 'role', title: 'Role', sortable: true }
199
+ ]
200
+ </script>
201
+
202
+ <QuickGrid
203
+ items={users}
204
+ {columns}
205
+ sortable
206
+ filterable
207
+ pageable
208
+ pageSize={10}
209
+ />
210
+ ```
211
+
212
+ ### Navigation Layout with Persistence
213
+ ```svelte
214
+ <script>
215
+ import { Layout, Header, NavMenu, NavGroup, NavLinkItem, BodyContent } from 'svelte-fluentui'
216
+ import { page } from '$app/stores'
217
+
218
+ // Check if a link is active based on current route
219
+ function isActive(href: string): boolean {
220
+ if (!href) return false
221
+ if (href === "/" && $page.url.pathname === "/") return true
222
+ if (href !== "/" && $page.url.pathname.startsWith(href)) return true
223
+ return false
224
+ }
225
+ </script>
226
+
227
+ <Layout>
228
+ <Header slot="header">
229
+ <h1>My App</h1>
230
+ </Header>
231
+
232
+ <NavMenu slot="navigation">
233
+ <NavGroup title="Main Menu">
234
+ {#snippet linkText()}
235
+ Main Menu
236
+ {/snippet}
237
+
238
+ <NavLinkItem href="/" class={isActive("/") ? "active" : ""}>
239
+ Home
240
+ </NavLinkItem>
241
+ <NavLinkItem href="/about" class={isActive("/about") ? "active" : ""}>
242
+ About
243
+ </NavLinkItem>
244
+ <NavLinkItem href="/contact" class={isActive("/contact") ? "active" : ""}>
245
+ Contact
246
+ </NavLinkItem>
247
+ </NavGroup>
248
+ </NavMenu>
249
+
250
+ <BodyContent>
251
+ <!-- Main content here -->
252
+ </BodyContent>
253
+ </Layout>
254
+ ```
255
+
256
+ ### Date and Time Pickers
257
+ ```svelte
258
+ <script>
259
+ import { DatePicker, TimePicker } from 'svelte-fluentui'
260
+
261
+ let selectedDate = $state<Date | null>(new Date())
262
+ let selectedTime = $state<string | null>("14:30")
263
+
264
+ // Deny weekends
265
+ const disabledDateFunc = (d: Date) => d.getDay() === 0 || d.getDay() === 6
266
+ </script>
267
+
268
+ <DatePicker
269
+ bind:value={selectedDate}
270
+ label="Select date"
271
+ placeholder="Choose a date"
272
+ firstDayOfWeek={1}
273
+ {disabledDateFunc}
274
+ autoClose={true}
275
+ />
276
+
277
+ <TimePicker
278
+ bind:value={selectedTime}
279
+ label="Select time"
280
+ useAmPm={false}
281
+ showSeconds={false}
282
+ minTime="08:00"
283
+ maxTime="18:00"
284
+ autoClose={true}
285
+ />
286
+ ```
287
+
288
+ ### Dialog with Actions
289
+ ```svelte
290
+ <script>
291
+ import { Dialog, Button } from 'svelte-fluentui'
292
+
293
+ let open = $state(false)
294
+ let name = $state("")
295
+ </script>
296
+
297
+ <Button onclick={() => open = true}>Open Dialog</Button>
298
+
299
+ <Dialog
300
+ bind:visible={open}
301
+ title="Rename item"
302
+ modal
303
+ primaryAction={{
304
+ label: "Save",
305
+ onClick: async () => {
306
+ if (!name.trim()) return false // keep open
307
+ await saveName(name)
308
+ // auto-closes when onClick resolves to anything but `false`
309
+ }
310
+ }}
311
+ secondaryAction={{
312
+ label: "Cancel"
313
+ }}
314
+ >
315
+ <label>
316
+ Name
317
+ <input bind:value={name} />
318
+ </label>
319
+ </Dialog>
320
+ ```
321
+
322
+ ### Responsive Tabs
323
+ ```svelte
324
+ <script>
325
+ import { Tabs, Tab } from 'svelte-fluentui'
326
+ </script>
327
+
328
+ <!-- Default: tabs scroll horizontally when too wide -->
329
+ <Tabs activeId="overview">
330
+ {#snippet childContent()}
331
+ <Tab id="overview" label="Overview" />
332
+ <Tab id="members" label="Members" />
333
+ <Tab id="activity" label="Activity" />
334
+ <Tab id="integrations" label="Integrations" />
335
+ <Tab id="settings" label="Settings" />
336
+ {/snippet}
337
+ </Tabs>
338
+
339
+ <!-- Wrap onto multiple rows instead of scrolling -->
340
+ <Tabs responsive="wrap" activeId="a">...</Tabs>
341
+
342
+ <!-- Justify: tabs divide the row equally -->
343
+ <Tabs justify activeId="a">...</Tabs>
344
+ ```
345
+
346
+ ### Autocomplete with Multiple Selection
347
+ ```svelte
348
+ <script lang="ts">
349
+ import { Autocomplete } from 'svelte-fluentui'
350
+
351
+ const options = [
352
+ { value: "1", text: "Option 1" },
353
+ { value: "2", text: "Option 2" },
354
+ { value: "3", text: "Option 3" }
355
+ ]
356
+
357
+ let selected = $state<string[]>([])
358
+ </script>
359
+
360
+ <Autocomplete
361
+ bind:selectedOptions={selected}
362
+ options={options}
363
+ label="Select multiple"
364
+ placeholder="Type to search..."
365
+ maxSelectedOptions={5}
366
+ />
367
+ ```
368
+
369
+ ### File Upload with Progress
370
+ ```svelte
371
+ <script lang="ts">
372
+ import { InputFile } from 'svelte-fluentui'
373
+ import type { FileUploadHandler } from 'svelte-fluentui'
374
+
375
+ const uploadFile: FileUploadHandler = async (file, onProgress) => {
376
+ const formData = new FormData()
377
+ formData.append('file', file)
378
+
379
+ // Your upload logic here
380
+ // Call onProgress(percent) to update progress bar
381
+
382
+ const response = await fetch('/api/upload', {
383
+ method: 'POST',
384
+ body: formData
385
+ })
386
+
387
+ if (!response.ok) throw new Error('Upload failed')
388
+ }
389
+ </script>
390
+
391
+ <InputFile
392
+ multiple={true}
393
+ accept="image/*"
394
+ maxFileSize={5 * 1024 * 1024}
395
+ uploadFileCallback={uploadFile}
396
+ onFileUploaded={(file) => console.log('Uploaded:', file.name)}
397
+ />
398
+ ```
399
+
400
+ ### Toast Notifications
401
+ ```svelte
402
+ <script>
403
+ import { ToastContainer, toast } from 'svelte-fluentui'
404
+
405
+ function showSuccess() {
406
+ toast.success('Operation completed successfully!')
407
+ }
408
+
409
+ function showError() {
410
+ const id = toast.error('Something went wrong', {
411
+ persistent: true,
412
+ position: 'top-center'
413
+ })
414
+ // Later: toast.dismiss(id)
415
+ }
416
+
417
+ function showWithProgress() {
418
+ toast.info('Processing your request...', {
419
+ showProgress: true,
420
+ duration: 8000
421
+ })
422
+ }
423
+ </script>
424
+
425
+ <!-- Add once in your layout -->
426
+ <ToastContainer />
427
+
428
+ <button onclick={showSuccess}>Show Success</button>
429
+ <button onclick={showError}>Show Error</button>
430
+ <button onclick={showWithProgress}>Show With Progress</button>
431
+ ```
432
+
433
+ ### `portal` Action (for custom overlays)
434
+ ```svelte
435
+ <script>
436
+ import { portal } from 'svelte-fluentui'
437
+
438
+ let open = $state(false)
439
+ </script>
440
+
441
+ <button onclick={() => open = true}>Show overlay</button>
442
+
443
+ {#if open}
444
+ <!-- Moved to <body> for the lifetime of this block so ancestor
445
+ stacking contexts / transforms don't affect its z-index. -->
446
+ <div use:portal class="my-overlay" onclick={() => open = false}>
447
+ My custom overlay
448
+ </div>
449
+ {/if}
450
+ ```
451
+
452
+ ### Global Runtime API
453
+ ```js
454
+ // In any browser console, after the page has loaded svelte-fluentui:
455
+ window.components["svelte-fluentui"].version()
456
+ // => "1.0.0-rc08"
457
+ ```
458
+
459
+ Also available as a direct import:
460
+ ```js
461
+ import { VERSION } from 'svelte-fluentui'
462
+ console.log(VERSION)
463
+ ```
464
+
465
+ ## Z-Index Scale
466
+
467
+ All overlays use a shared token scale so the layering stays consistent across the library. Variables are exposed at `:root` when you import the SCSS bundle; every `z-index: var(--fluent-z-*)` declaration in components also includes the numeric fallback so things still stack correctly even without our SCSS loaded.
468
+
469
+ | Token | Value | Used for |
470
+ |---|---|---|
471
+ | `--fluent-z-dropdown` | 1000 | Simple dropdowns, grid overlays |
472
+ | `--fluent-z-sticky` | 1020 | Sticky app bars / headers |
473
+ | `--fluent-z-fixed` | 1030 | Fixed sidebars / mobile nav |
474
+ | `--fluent-z-modal-backdrop` | 1040 | Dialog overlay |
475
+ | `--fluent-z-modal` | 1050 | Dialog itself |
476
+ | `--fluent-z-popover` | 1060 | Calendar / time / autocomplete popups (above modal) |
477
+ | `--fluent-z-tooltip` | 1070 | Tooltip |
478
+ | `--fluent-z-toast` | 1080 | Toast |
479
+
480
+ ## Development
481
+
482
+ Clone the repository and install dependencies:
483
+
484
+ ```bash
485
+ git clone https://github.com/KeenMate/svelte-fluentui.git
486
+ cd svelte-fluentui
487
+ npm install
488
+ ```
489
+
490
+ ### Working on the Library
491
+
492
+ The library source code is in `src/lib/`. To package the library:
493
+
494
+ ```bash
495
+ npm run package
496
+ ```
497
+
498
+ ### Working on Documentation
499
+
500
+ The documentation site is in the `docs/` folder as a separate SvelteKit project:
501
+
502
+ ```bash
503
+ cd docs
504
+ npm install
505
+ npm run dev
506
+ ```
507
+
508
+ Or use the Makefile:
509
+
510
+ ```bash
511
+ make dev # Runs docs dev server
512
+ ```
513
+
514
+ Visit `http://localhost:5173` to see the component showcase and examples.
515
+
516
+ ## Building
517
+
518
+ Build the library for publishing:
519
+
520
+ ```bash
521
+ npm run build
522
+ ```
523
+
524
+ Build documentation Docker image:
525
+
526
+ ```bash
527
+ # With local source
528
+ make docker-build-docs
529
+
530
+ # With specific npm version
531
+ make docker-build-docs VERSION=1.0.0-rc03
532
+ ```
533
+
534
+ ## Contributing
535
+
536
+ Contributions are welcome! Please feel free to submit a Pull Request.
537
+
538
+ ## License
539
+
540
+ MIT © [KeenMate](https://github.com/KeenMate)
541
+
542
+ ## Credits
543
+
544
+ Built on top of [Microsoft FluentUI Web Components](https://github.com/microsoft/fluentui) v2.6.x.
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Move an element out of its current DOM position into another container
3
+ * (defaults to `document.body`) for the lifetime of the action. Useful for
4
+ * overlays — modals, popovers, tooltips — that must escape any ancestor
5
+ * `transform` / `filter` / `position: fixed` stacking context to honor the
6
+ * library's z-index scale.
7
+ *
8
+ * Usage:
9
+ * <div use:portal>...</div> // appends to <body>
10
+ * <div use:portal={'#layer-root'}> // appends to a specific selector / element
11
+ */
12
+ export type PortalTarget = HTMLElement | string;
13
+ export declare function portal(node: HTMLElement, target?: PortalTarget): {
14
+ update(newTarget: PortalTarget): void;
15
+ destroy(): void;
16
+ };
@@ -0,0 +1,24 @@
1
+ export function portal(node, target = document.body) {
2
+ function resolve(t) {
3
+ if (typeof t === "string")
4
+ return document.querySelector(t);
5
+ return t ?? null;
6
+ }
7
+ function mount(t) {
8
+ const host = resolve(t);
9
+ if (!host) {
10
+ console.warn("portal: target not found", t);
11
+ return;
12
+ }
13
+ host.appendChild(node);
14
+ }
15
+ mount(target);
16
+ return {
17
+ update(newTarget) {
18
+ mount(newTarget);
19
+ },
20
+ destroy() {
21
+ node.parentNode?.removeChild(node);
22
+ }
23
+ };
24
+ }
@@ -31,3 +31,16 @@
31
31
  gap: 1rem;
32
32
  align-items: center;
33
33
  }
34
+
35
+ // Canonical label style used by form components (TextField, Select,
36
+ // Autocomplete, Radio, RadioGroup) so labels stay visually consistent
37
+ // regardless of whether the input is a fluent web component or a custom one.
38
+ .fluent-label {
39
+ display: block;
40
+ font-size: 0.875rem;
41
+ font-weight: 600;
42
+ line-height: 1.25;
43
+ color: var(--neutral-foreground-rest);
44
+ margin-bottom: 0.25rem;
45
+ }
46
+
@@ -265,15 +265,6 @@ fluent-dialog {
265
265
  z-index: 1050;
266
266
  }
267
267
 
268
- // Modal backdrop - blocks interaction with content behind the dialog
269
- fluent-dialog[modal]:not([hidden])::before {
270
- content: "";
271
- position: fixed;
272
- inset: 0;
273
- background-color: rgba(0, 0, 0, 0.4);
274
- z-index: -1;
275
- }
276
-
277
268
  fluent-dialog::part(control) {
278
269
  --dialog-padding: calc(var(--design-unit) * 6 * 1px);
279
270
  position: relative;
@@ -690,3 +681,16 @@ div[dragged-over] {
690
681
  outline-width: var(--focus-stroke-width);
691
682
  }
692
683
  }
684
+
685
+ // =============================================================================
686
+ // Tab z-index override
687
+ // Blazor ships fluent-tab[aria-selected="true"] { z-index: 1 } via fluent-components.scss.
688
+ // That rule, combined with `position: relative` from elsewhere, creates a stacking
689
+ // context that can fight with overlay components when the tabs sit in an elevated
690
+ // ancestor (sticky header, transformed container, etc.). Reset it — selected state
691
+ // is already conveyed via font-weight and color tokens.
692
+ // =============================================================================
693
+ fluent-tab[aria-selected="true"] {
694
+ z-index: auto;
695
+ }
696
+
@@ -3,6 +3,7 @@
3
3
  * Converts SCSS variables to CSS custom properties for runtime theme switching
4
4
  */
5
5
 
6
+ @use 'tokens' as *;
6
7
  @use 'variables' as *;
7
8
 
8
9
  // Generate CSS custom properties from SCSS variables
@@ -72,6 +73,19 @@
72
73
 
73
74
  // Transitions
74
75
  --fluent-transition: #{$component-transition};
76
+
77
+ // Z-index scale (theme-independent — kept here so authors can use var() in any component).
78
+ // Order matters: dropdown < sticky < fixed < modal-backdrop < modal < popover < tooltip < toast.
79
+ // "popover" is intentionally above modal so dropdowns/calendars opened from inside a modal
80
+ // render on top of the modal.
81
+ --fluent-z-dropdown: #{$z-index-dropdown};
82
+ --fluent-z-sticky: #{$z-index-sticky};
83
+ --fluent-z-fixed: #{$z-index-fixed};
84
+ --fluent-z-modal-backdrop: #{$z-index-modal-backdrop};
85
+ --fluent-z-modal: #{$z-index-modal};
86
+ --fluent-z-popover: #{$z-index-popover};
87
+ --fluent-z-tooltip: #{$z-index-tooltip};
88
+ --fluent-z-toast: #{$z-index-toast};
75
89
  }
76
90
 
77
91
  [data-theme="dark"] {