svelte-fluentui 1.0.0-rc14 → 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.
package/README.md CHANGED
@@ -2,12 +2,15 @@
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-rc14
5
+ ## What's New in v1.0.0-rc15
6
6
 
7
- - **`Combobox` / `Autocomplete` `minSearchLength` prop** keep the dropdown closed until the user has typed N characters. In `Autocomplete` this also short-circuits before the debounce timer, so `onoptionssearch` is never invoked against expensive backends for stray single-character input. `showInitialOptions` and `Ctrl+Space` still override
8
- - **`Grid` columns mode** — `<Grid columns={N} gap="1rem">` switches from the 12-column flex-spacing system to CSS grid with `repeat(N, minmax(0, 1fr))`, so every `<GridItem>` is locked at 1/N of the container regardless of content (no more one chip-stuffed cell stealing width). Fully backward-compatible: the existing `<Grid spacing={N}>` flow is untouched
9
- - **Docs pages standardized across the board** all 41 component demo pages now share one layout (outer `<Stack>`, h1, description, References card, 3-column API grid for Properties/Callbacks/Slots, then a single Examples card with `<h3>` subsections). Every previously-missing component description was backfilled
10
- - **Changelog viewer no longer auto-registers stray custom elements** the home and `/changelog` renderers now HTML-escape markdown before converting, so code spans like `` `<fluent-dialog>` `` display as literal text instead of silently painting an empty Fluent dialog on the page
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
11
14
 
12
15
  ## Features
13
16
 
@@ -42,7 +45,7 @@ A comprehensive Svelte wrapper library for Microsoft FluentUI web components (v2
42
45
  - **Toast Service** - Programmatic notifications with `toast.success()`, `toast.error()`, etc. - 6 positions, progress bars, auto-dismiss
43
46
  - **InputFile** - Drag-and-drop file upload with validation and progress tracking
44
47
  - **Autocomplete** - Multiple selection with tag/chip display, async search with AbortSignal, initial options, Ctrl+Space to show all
45
- - **QuickGrid** - Advanced data grid with sorting, filtering, pagination, editable rows, row toolbar, context menu
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`
46
49
  - **Three-State Checkbox** - Checkbox with indeterminate state support
47
50
  - **Responsive Layout** - Complete layout system with Grid, Stack, and responsive components
48
51
 
@@ -86,7 +89,7 @@ npm install svelte-fluentui
86
89
 
87
90
  ### Data Display
88
91
  - `DataGrid` / `DataGridRow` / `DataGridCell` - Data table components
89
- - `QuickGrid` - Advanced data grid with sorting, filtering, and pagination
92
+ - `QuickGrid` - Advanced data grid with sorting, filtering, pagination, inline editing, tree mode, and custom filter predicates
90
93
  - `Card` - Content container
91
94
  - `Badge` - Status indicators
92
95
  - `ProgressBar` - Progress indication
@@ -77,7 +77,7 @@
77
77
 
78
78
  // Validation types
79
79
  type CellValidationState = {
80
- rowIndex: number
80
+ rowKey: string
81
81
  field: string
82
82
  error: string
83
83
  }
@@ -124,6 +124,15 @@
124
124
  format?: (value: any, row: T) => string
125
125
  template?: (row: T) => string
126
126
  snippet?: Snippet<[T]>
127
+ // Custom filter predicate. When set, replaces the built-in substring match for this
128
+ // column. Called once per row with the raw filter input string and the row.
129
+ // Returns `true` to keep the row, `false` to exclude it, or `null` when the input
130
+ // is syntactically invalid (e.g. user has only typed `>` so far) — `null` causes
131
+ // the filter to be ignored entirely (all rows pass) AND the filter input gets a
132
+ // `.invalid` class so the UI can surface the bad-syntax state. Assumes the
133
+ // validity is a property of the input string, not the row, so the grid only
134
+ // probes the first row to decide validity.
135
+ filter?: (filterValue: string, row: T) => boolean | null
127
136
  // Editing props
128
137
  editable?: boolean
129
138
  editor?: EditorType
@@ -137,6 +146,8 @@
137
146
  oncelledit?: (context: CustomEditorContext<T>) => void
138
147
  // Show edit button in cell
139
148
  showEditButton?: boolean
149
+ // Tree mode: render this column with indent + expand/collapse chevron
150
+ isTree?: boolean
140
151
  }
141
152
 
142
153
  type RowChangeDetail<T> = {
@@ -228,6 +239,10 @@
228
239
  * horizontal space. Pair with per-column `width` / `minWidth` / `maxWidth` / `autoWidth` when you
229
240
  * want columns to keep predefined widths instead of stretching to justify across the table. */
230
241
  fillerColumn?: boolean
242
+ /** Default `min-width` applied to every column header that does not specify its own `minWidth`.
243
+ * Useful with `fillerColumn` to stop content-sized columns from collapsing to a one-word column.
244
+ * Accepts any CSS length (e.g. `"100px"`, `"8rem"`). */
245
+ columnMinWidth?: string
231
246
  class?: string
232
247
  style?: string
233
248
  cellTemplate?: SlotType
@@ -249,6 +264,25 @@
249
264
  // Context menu
250
265
  contextMenu?: ContextMenuItem<T>[]
251
266
  oncontextmenuopen?: (context: ContextMenuContext<T>) => void
267
+ // Stable row identity — keys internal state (drafts, edits) so they survive
268
+ // re-orderings (pagination, filter, tree expand/collapse). When omitted, the grid
269
+ // falls back to treePathMember in tree mode, then to the displayed-row index, and
270
+ // console.warns once if neither stable key is available while editing is enabled.
271
+ idMember?: keyof T
272
+ // Tree mode (ltree-style path hierarchy)
273
+ treePathMember?: keyof T // Required to enable tree mode — field holding the path string ("1.2.3", "/1/2/3", "C:\\foo\\bar")
274
+ treeLevelMember?: keyof T // Optional — pre-computed depth (0-based). Falls back to deriving from path.
275
+ treeParentMember?: keyof T // Optional — pre-computed parent path. Falls back to deriving from path.
276
+ treeSeparator?: string // Path separator. Auto-detected from first row if omitted.
277
+ treeDataSorted?: boolean // True when caller already sorted items so parents precede children. Default false → grid sorts internally.
278
+ expandedPaths?: Set<string> // Bindable set of expanded path strings. Default: managed internally.
279
+ defaultExpandDepth?: number // Initial expansion depth when expandedPaths not bound. Omit to expand all.
280
+ treeDoubleClickBehavior?: "none" | "toggle" // "toggle": double-clicking the tree column toggles expand/collapse on rows with children. Default "none".
281
+ // Filter mode. When set, internal client-side filtering is bypassed and the caller
282
+ // owns the filtered dataset (typical for server-side search). Filter inputs still
283
+ // render and fire this callback with a copy of the current `{field: value}` map on
284
+ // every keystroke — debounce + post to your backend, then update `items`.
285
+ onfilterchange?: (filters: Record<string, string>) => void
252
286
  // Callbacks
253
287
  onrowchange?: (detail: RowChangeDetail<T>) => void
254
288
  onroweditstart?: (detail: { row: T, rowIndex: number, field: string }) => void
@@ -268,6 +302,7 @@
268
302
  striped = true,
269
303
  hoverable = true,
270
304
  fillerColumn = false,
305
+ columnMinWidth = undefined,
271
306
  class: className = "",
272
307
  style = "",
273
308
  cellTemplate = undefined,
@@ -288,6 +323,19 @@
288
323
  // Context menu
289
324
  contextMenu = undefined,
290
325
  oncontextmenuopen = undefined,
326
+ // Stable row identity
327
+ idMember = undefined,
328
+ // Tree mode
329
+ treePathMember = undefined,
330
+ treeLevelMember = undefined,
331
+ treeParentMember = undefined,
332
+ treeSeparator = undefined,
333
+ treeDataSorted = false,
334
+ expandedPaths = $bindable(undefined),
335
+ defaultExpandDepth = undefined,
336
+ treeDoubleClickBehavior = "none",
337
+ // Filter delegation
338
+ onfilterchange = undefined,
291
339
  // Callbacks
292
340
  onrowchange = undefined,
293
341
  onroweditstart = undefined,
@@ -314,7 +362,11 @@
314
362
  } else if (column.width) {
315
363
  parts.push(`width: ${column.width}`)
316
364
  }
365
+ // Per-column minWidth wins; otherwise fall back to the grid-level default. Skip both
366
+ // when the column opted into autoWidth — that mode is supposed to shrink to content
367
+ // and a min-width would break the trick.
317
368
  if (column.minWidth) parts.push(`min-width: ${column.minWidth}`)
369
+ else if (columnMinWidth && !column.autoWidth) parts.push(`min-width: ${columnMinWidth}`)
318
370
  if (column.maxWidth) parts.push(`max-width: ${column.maxWidth}`)
319
371
  if (includeAlign) parts.push(`text-align: ${column.align || "left"}`)
320
372
  return parts.join("; ")
@@ -335,8 +387,42 @@
335
387
  let currentCellError = $state<string | null>(null) // Error for currently editing cell
336
388
  let isValidating = $state(false)
337
389
 
338
- // Draft rows - clones of rows being edited (preserves dirty values including invalid ones)
339
- let draftRows = $state<Map<number, T>>(new Map())
390
+ // Draft rows - clones of rows being edited (preserves dirty values including invalid ones).
391
+ // Keyed by stable row id (idMember → treePathMember → displayed index fallback) so drafts
392
+ // survive pagination / filter / tree expand-collapse re-orderings.
393
+ let draftRows = $state<Map<string, T>>(new Map())
394
+
395
+ // Returns a stable key for a row. Coalesces idMember → treePathMember → displayed index.
396
+ // The index fallback is lossy (drifts on re-order) and triggers a one-shot console warning
397
+ // via the effect below when editing is enabled without a real stable id.
398
+ function getRowId(item: T, displayedIndex: number): string {
399
+ if (idMember !== undefined) {
400
+ const v = item[idMember]
401
+ if (v !== undefined && v !== null) return String(v)
402
+ }
403
+ if (treePathMember !== undefined) {
404
+ const v = item[treePathMember]
405
+ if (v !== undefined && v !== null) return String(v)
406
+ }
407
+ return String(displayedIndex)
408
+ }
409
+
410
+ let idWarningEmitted = $state(false)
411
+ $effect(() => {
412
+ if (idWarningEmitted) return
413
+ if (!editable) return
414
+ if (idMember !== undefined || treePathMember !== undefined) return
415
+ // eslint-disable-next-line no-console
416
+ console.warn(
417
+ "[QuickGrid] No `idMember` was provided while `editable` is enabled. Row state " +
418
+ "(drafts, in-progress edits) will be keyed by displayed-row index, which means " +
419
+ "pagination, filtering, sorting, or tree expand/collapse can shift the displayed " +
420
+ "order and cause an in-flight edit to land on the wrong row. Pass `idMember=\"id\"` " +
421
+ "(or any field that uniquely identifies a row) to fix this. In tree mode, " +
422
+ "`treePathMember` is also accepted as a fallback identity."
423
+ )
424
+ idWarningEmitted = true
425
+ })
340
426
 
341
427
  // Navigation mode state (for "navigate" editTrigger)
342
428
  let focusedCell = $state<{ rowIndex: number; colIndex: number } | null>(null)
@@ -384,21 +470,218 @@
384
470
  })
385
471
  })
386
472
 
387
- // Computed: filtered items
473
+ // ============ Tree mode helpers ============
474
+ let isTreeMode = $derived(treePathMember !== undefined)
475
+ let internalExpandedPaths = $state(new Set<string>())
476
+ let treeInitialized = $state(false)
477
+
478
+ let resolvedTreeSeparator = $derived.by(() => {
479
+ if (treeSeparator) return treeSeparator
480
+ if (!isTreeMode) return "/"
481
+ // Scan items until we find a path that contains a recognizable separator —
482
+ // inspecting only items[0] would miss the case where the root is "1" and the
483
+ // first path that actually contains a separator is "1.1" (the second row).
484
+ for (const item of items) {
485
+ const p = String(item[treePathMember as keyof T] ?? "")
486
+ if (p.includes("/")) return "/"
487
+ if (p.includes("\\")) return "\\"
488
+ if (p.includes(".")) return "."
489
+ }
490
+ return "/"
491
+ })
492
+
493
+ function getRowPath(item: T): string {
494
+ if (!treePathMember) return ""
495
+ const raw = String(item[treePathMember] ?? "")
496
+ // Strip trailing separator so "C:\foo\" and "C:\foo" behave the same
497
+ const sep = resolvedTreeSeparator
498
+ return raw.endsWith(sep) ? raw.slice(0, -sep.length) : raw
499
+ }
500
+
501
+ function getRowLevel(item: T): number {
502
+ if (treeLevelMember) {
503
+ const v = item[treeLevelMember]
504
+ const n = Number(v)
505
+ return Number.isFinite(n) ? n : 0
506
+ }
507
+ const path = getRowPath(item)
508
+ if (!path) return 0
509
+ const sep = resolvedTreeSeparator
510
+ return path.split(sep).filter(Boolean).length - 1
511
+ }
512
+
513
+ function getRowParentPath(item: T): string {
514
+ if (treeParentMember) {
515
+ const raw = String(item[treeParentMember] ?? "")
516
+ const sep = resolvedTreeSeparator
517
+ return raw.endsWith(sep) ? raw.slice(0, -sep.length) : raw
518
+ }
519
+ const path = getRowPath(item)
520
+ if (!path) return ""
521
+ const sep = resolvedTreeSeparator
522
+ const idx = path.lastIndexOf(sep)
523
+ return idx >= 0 ? path.slice(0, idx) : ""
524
+ }
525
+
526
+ // Path-aware comparison: numeric segments compare numerically (so "1.2" < "1.10")
527
+ function compareTreePaths(a: string, b: string): number {
528
+ const sep = resolvedTreeSeparator
529
+ const aSegs = a.split(sep).filter(Boolean)
530
+ const bSegs = b.split(sep).filter(Boolean)
531
+ const min = Math.min(aSegs.length, bSegs.length)
532
+ for (let i = 0; i < min; i++) {
533
+ const aSeg = aSegs[i]
534
+ const bSeg = bSegs[i]
535
+ const aNum = Number(aSeg)
536
+ const bNum = Number(bSeg)
537
+ if (!isNaN(aNum) && !isNaN(bNum) && aSeg !== "" && bSeg !== "") {
538
+ if (aNum !== bNum) return aNum - bNum
539
+ } else if (aSeg !== bSeg) {
540
+ return aSeg.localeCompare(bSeg)
541
+ }
542
+ }
543
+ return aSegs.length - bSegs.length
544
+ }
545
+
546
+ // Set of every path that exists as an actual row (used to distinguish "real" ancestors
547
+ // from virtual roots — e.g. "C:" when only "C:\\Windows" and below are in the dataset).
548
+ let treePathSet = $derived.by(() => {
549
+ const result = new Set<string>()
550
+ if (!isTreeMode) return result
551
+ for (const item of items) result.add(getRowPath(item))
552
+ return result
553
+ })
554
+
555
+ // Set of paths that have at least one child in the dataset
556
+ let treeParentPathSet = $derived.by(() => {
557
+ const result = new Set<string>()
558
+ if (!isTreeMode) return result
559
+ for (const item of items) {
560
+ const parent = getRowParentPath(item)
561
+ if (parent) result.add(parent)
562
+ }
563
+ return result
564
+ })
565
+
566
+ function rowHasChildren(item: T): boolean {
567
+ return treeParentPathSet.has(getRowPath(item))
568
+ }
569
+
570
+ let resolvedExpandedPaths = $derived(expandedPaths ?? internalExpandedPaths)
571
+
572
+ function isPathExpanded(path: string): boolean {
573
+ return resolvedExpandedPaths.has(path)
574
+ }
575
+
576
+ function toggleExpand(path: string) {
577
+ if (expandedPaths !== undefined) {
578
+ const next = new Set(expandedPaths)
579
+ if (next.has(path)) next.delete(path)
580
+ else next.add(path)
581
+ expandedPaths = next
582
+ } else {
583
+ const next = new Set(internalExpandedPaths)
584
+ if (next.has(path)) next.delete(path)
585
+ else next.add(path)
586
+ internalExpandedPaths = next
587
+ }
588
+ }
589
+
590
+ // Initialize internal expansion when items first arrive (only if no external binding).
591
+ // `defaultExpandDepth` is interpreted relative to the shallowest level present in the
592
+ // dataset, not absolute level 0 — so a partial tree where the shallowest row is level 3
593
+ // still gets its own "roots" expanded.
594
+ $effect(() => {
595
+ if (!isTreeMode) return
596
+ if (expandedPaths !== undefined) return
597
+ if (treeInitialized) return
598
+ if (items.length === 0) return
599
+ let minLevel = Infinity
600
+ for (const item of items) minLevel = Math.min(minLevel, getRowLevel(item))
601
+ if (!Number.isFinite(minLevel)) minLevel = 0
602
+ const next = new Set<string>()
603
+ for (const item of items) {
604
+ const level = getRowLevel(item) - minLevel
605
+ if (defaultExpandDepth === undefined || level < defaultExpandDepth) {
606
+ next.add(getRowPath(item))
607
+ }
608
+ }
609
+ internalExpandedPaths = next
610
+ treeInitialized = true
611
+ })
612
+
613
+ // ============ Display chain ============
614
+
615
+ // Tree-ordered: caller-sorted, or sorted internally by path
616
+ let treeOrderedItems = $derived.by(() => {
617
+ if (!isTreeMode) return items
618
+ if (treeDataSorted) return items
619
+ return [...items].sort((a, b) => compareTreePaths(getRowPath(a), getRowPath(b)))
620
+ })
621
+
622
+ // Per-field validity for custom filters — `false` when the column's filter predicate
623
+ // returned null on the first row (probe), meaning the input is syntactically invalid
624
+ // (e.g. `>` typed so far without a number). Surfaced to the template so the input gets
625
+ // a `.invalid` class.
626
+ let filterValidity = $derived.by(() => {
627
+ const result: Record<string, boolean> = {}
628
+ if (items.length === 0) return result
629
+ for (const [field, filterValue] of Object.entries(filters)) {
630
+ if (!filterValue) continue
631
+ const col = columns.find((c) => String(c.field) === field)
632
+ if (!col?.filter) continue
633
+ const probe = col.filter(filterValue, items[0])
634
+ if (probe === null) result[field] = false
635
+ }
636
+ return result
637
+ })
638
+
639
+ // Computed: filtered items (tree-aware: includes ancestors of matches).
640
+ // When `onfilterchange` is provided the caller owns filtering — we render the inputs
641
+ // and fire the callback, but skip internal filtering entirely (server-side mode).
388
642
  let filteredItems = $derived.by(() => {
389
- if (!filterable || Object.keys(filters).length === 0) return items
643
+ if (onfilterchange) return treeOrderedItems
644
+ if (!filterable || Object.keys(filters).length === 0) return treeOrderedItems
390
645
 
391
- return items.filter((item) => {
392
- return Object.entries(filters).every(([field, filterValue]) => {
646
+ const matchesFilter = (item: T) =>
647
+ Object.entries(filters).every(([field, filterValue]) => {
393
648
  if (!filterValue) return true
649
+ // Per-column custom predicate wins over the built-in substring match.
650
+ const col = columns.find((c) => String(c.field) === field)
651
+ if (col?.filter) {
652
+ const result = col.filter(filterValue, item)
653
+ // null = invalid input → don't filter, treat row as a match.
654
+ return result === null ? true : result
655
+ }
394
656
  const cellValue = String(item[field as keyof T] ?? "").toLowerCase()
395
657
  return cellValue.includes(filterValue.toLowerCase())
396
658
  })
397
- })
659
+
660
+ if (!isTreeMode) return treeOrderedItems.filter(matchesFilter)
661
+
662
+ // Tree mode: include matched rows + all their ancestors so the hierarchy reads correctly
663
+ const matchedPaths = new Set<string>()
664
+ for (const item of treeOrderedItems) {
665
+ if (matchesFilter(item)) matchedPaths.add(getRowPath(item))
666
+ }
667
+ const includePaths = new Set(matchedPaths)
668
+ const sep = resolvedTreeSeparator
669
+ for (const path of matchedPaths) {
670
+ let parent = path
671
+ const idx0 = parent.lastIndexOf(sep)
672
+ parent = idx0 >= 0 ? parent.slice(0, idx0) : ""
673
+ while (parent) {
674
+ includePaths.add(parent)
675
+ const idx = parent.lastIndexOf(sep)
676
+ parent = idx >= 0 ? parent.slice(0, idx) : ""
677
+ }
678
+ }
679
+ return treeOrderedItems.filter((i) => includePaths.has(getRowPath(i)))
398
680
  })
399
681
 
400
- // Computed: sorted items
682
+ // Computed: sorted items. Tree mode preserves hierarchy and ignores column sort.
401
683
  let sortedItems = $derived.by(() => {
684
+ if (isTreeMode) return filteredItems
402
685
  if (!sortColumn) return filteredItems
403
686
 
404
687
  return [...filteredItems].sort((a, b) => {
@@ -420,17 +703,38 @@
420
703
  })
421
704
  })
422
705
 
706
+ // Computed: visible items — hide descendants of collapsed nodes (tree only).
707
+ // When a filter is active, ancestors are already auto-included above and we render the
708
+ // full filtered set so users can see matches inside otherwise-collapsed branches.
709
+ // Ancestors that aren't actually present as rows in the dataset are treated as virtual
710
+ // roots and never gate visibility (supports partial-tree / subtree views).
711
+ let visibleItems = $derived.by(() => {
712
+ if (!isTreeMode) return sortedItems
713
+ const filterActive = filterable && Object.values(filters).some((v) => !!v)
714
+ if (filterActive) return sortedItems
715
+ const sep = resolvedTreeSeparator
716
+ return sortedItems.filter((item) => {
717
+ let parent = getRowParentPath(item)
718
+ while (parent) {
719
+ if (treePathSet.has(parent) && !resolvedExpandedPaths.has(parent)) return false
720
+ const idx = parent.lastIndexOf(sep)
721
+ parent = idx >= 0 ? parent.slice(0, idx) : ""
722
+ }
723
+ return true
724
+ })
725
+ })
726
+
423
727
  // Computed: paginated items
424
728
  let paginatedItems = $derived.by(() => {
425
- if (!pageable) return sortedItems
729
+ if (!pageable) return visibleItems
426
730
 
427
731
  const start = (currentPage - 1) * pageSize
428
732
  const end = start + pageSize
429
- return sortedItems.slice(start, end)
733
+ return visibleItems.slice(start, end)
430
734
  })
431
735
 
432
736
  // Computed: total pages
433
- let totalPages = $derived(Math.ceil(sortedItems.length / pageSize))
737
+ let totalPages = $derived(Math.ceil(visibleItems.length / pageSize))
434
738
 
435
739
  // Display items (final result)
436
740
  let displayItems = $derived(paginatedItems)
@@ -450,6 +754,8 @@
450
754
  function handleFilter(field: string, value: string) {
451
755
  filters[field] = value
452
756
  currentPage = 1 // Reset to first page when filtering
757
+ // Pass a shallow copy so callers can't mutate our internal state.
758
+ onfilterchange?.({ ...filters })
453
759
  }
454
760
 
455
761
  function goToPage(page: number) {
@@ -460,7 +766,7 @@
460
766
 
461
767
  // Get raw value for a cell (checks draft row first, then original)
462
768
  function getCellRawValue(item: T, rowIndex: number, field: string): unknown {
463
- const draftRow = draftRows.get(rowIndex)
769
+ const draftRow = draftRows.get(getRowId(item, rowIndex))
464
770
  if (draftRow) {
465
771
  return draftRow[field as keyof T]
466
772
  }
@@ -559,8 +865,9 @@
559
865
  currentCellError = null
560
866
 
561
867
  // Clone row if not already cloned (preserves dirty values across edits)
562
- if (!draftRows.has(rowIndex)) {
563
- draftRows.set(rowIndex, { ...item })
868
+ const rowKey = getRowId(item, rowIndex)
869
+ if (!draftRows.has(rowKey)) {
870
+ draftRows.set(rowKey, { ...item })
564
871
  }
565
872
 
566
873
  // Handle custom editor
@@ -608,67 +915,51 @@
608
915
  }
609
916
  }
610
917
 
611
- // Helper functions for managing invalid cells
612
- function addInvalidCell(rowIndex: number, field: string, error: string) {
613
- const existingIndex = invalidCells.findIndex(c => c.rowIndex === rowIndex && c.field === field)
918
+ // Helper functions for managing invalid cells. Keyed by stable row id so markers survive
919
+ // pagination / filter / tree expand-collapse re-orderings.
920
+ function addInvalidCell(rowKey: string, field: string, error: string) {
921
+ const existingIndex = invalidCells.findIndex(c => c.rowKey === rowKey && c.field === field)
614
922
  if (existingIndex >= 0) {
615
- invalidCells[existingIndex] = { rowIndex, field, error }
923
+ invalidCells[existingIndex] = { rowKey, field, error }
616
924
  } else {
617
- invalidCells = [...invalidCells, { rowIndex, field, error }]
925
+ invalidCells = [...invalidCells, { rowKey, field, error }]
618
926
  }
619
927
  }
620
928
 
621
- function removeInvalidCell(rowIndex: number, field: string) {
622
- invalidCells = invalidCells.filter(c => !(c.rowIndex === rowIndex && c.field === field))
929
+ function removeInvalidCell(rowKey: string, field: string) {
930
+ invalidCells = invalidCells.filter(c => !(c.rowKey === rowKey && c.field === field))
623
931
  }
624
932
 
625
- function getCellValidationError(rowIndex: number, field: string): string | null {
626
- const cell = invalidCells.find(c => c.rowIndex === rowIndex && c.field === field)
933
+ function getCellValidationError(rowKey: string, field: string): string | null {
934
+ const cell = invalidCells.find(c => c.rowKey === rowKey && c.field === field)
627
935
  return cell?.error || null
628
936
  }
629
937
 
630
- function isCellInvalid(rowIndex: number, field: string): boolean {
631
- return invalidCells.some(c => c.rowIndex === rowIndex && c.field === field)
938
+ function isCellInvalid(rowKey: string, field: string): boolean {
939
+ return invalidCells.some(c => c.rowKey === rowKey && c.field === field)
632
940
  }
633
941
 
634
942
  // ============ Draft Row Management Functions ============
635
- // These allow external control over draft rows
943
+ // External-facing helpers (not currently called from inside the grid). Drafts are now
944
+ // keyed by the same stable id as everything else, so these accept a row key (string).
636
945
 
637
- /**
638
- * Get the draft row for a given row index.
639
- * Returns undefined if no draft exists.
640
- */
641
- function getRowDraft(rowIndex: number): T | undefined {
642
- return draftRows.get(rowIndex)
946
+ function getRowDraft(rowKey: string): T | undefined {
947
+ return draftRows.get(rowKey)
643
948
  }
644
949
 
645
- /**
646
- * Check if a row has a draft (has been edited).
647
- */
648
- function hasRowDraft(rowIndex: number): boolean {
649
- return draftRows.has(rowIndex)
950
+ function hasRowDraft(rowKey: string): boolean {
951
+ return draftRows.has(rowKey)
650
952
  }
651
953
 
652
- /**
653
- * Discard the draft for a row, reverting cell displays to original values.
654
- * Also clears any invalid cell markers for the row.
655
- */
656
- function discardRowDraft(rowIndex: number): void {
657
- draftRows.delete(rowIndex)
658
- // Remove invalid cell markers for this row
659
- invalidCells = invalidCells.filter(c => c.rowIndex !== rowIndex)
954
+ function discardRowDraft(rowKey: string): void {
955
+ draftRows.delete(rowKey)
956
+ invalidCells = invalidCells.filter(c => c.rowKey !== rowKey)
660
957
  }
661
958
 
662
- /**
663
- * Get all row indices that have drafts.
664
- */
665
- function getDraftRowIndices(): number[] {
959
+ function getDraftRowKeys(): string[] {
666
960
  return Array.from(draftRows.keys())
667
961
  }
668
962
 
669
- /**
670
- * Discard all drafts and invalid cell markers.
671
- */
672
963
  function discardAllDrafts(): void {
673
964
  draftRows.clear()
674
965
  invalidCells = []
@@ -699,13 +990,14 @@
699
990
  const oldValue = item[column.field as keyof T]
700
991
 
701
992
  // Update draft row
702
- const draftRow = draftRows.get(rowIndex)
993
+ const rowKey = getRowId(item, rowIndex)
994
+ const draftRow = draftRows.get(rowKey)
703
995
  if (draftRow) {
704
996
  ;(draftRow as any)[field] = newValue
705
997
  }
706
998
 
707
999
  // Remove from invalid cells if it was invalid
708
- removeInvalidCell(rowIndex, field)
1000
+ removeInvalidCell(rowKey, field)
709
1001
 
710
1002
  // Always fire onrowchange with isValid: true
711
1003
  onrowchange?.({
@@ -773,17 +1065,18 @@
773
1065
  isValidating = false
774
1066
 
775
1067
  // Update draft row with the new value (valid or invalid - preserves user input)
776
- const draftRow = draftRows.get(rowIndex)
1068
+ const rowKey = getRowId(item, rowIndex)
1069
+ const draftRow = draftRows.get(rowKey)
777
1070
  if (draftRow) {
778
1071
  ;(draftRow as any)[field] = finalValue
779
1072
  }
780
1073
 
781
1074
  // Update invalid cells tracking
782
1075
  if (isValid) {
783
- removeInvalidCell(rowIndex, field)
1076
+ removeInvalidCell(rowKey, field)
784
1077
  currentCellError = null
785
1078
  } else {
786
- addInvalidCell(rowIndex, field, validationErrorMsg || "Invalid value")
1079
+ addInvalidCell(rowKey, field, validationErrorMsg || "Invalid value")
787
1080
  currentCellError = validationErrorMsg
788
1081
  onvalidationerror?.({ row: item, rowIndex, field, error: validationErrorMsg || "Invalid value" })
789
1082
  }
@@ -827,6 +1120,18 @@
827
1120
  }
828
1121
 
829
1122
  function handleCellDblClick(e: MouseEvent, rowIndex: number, colIndex: number, column: Column<T>, item: T) {
1123
+ // Tree-toggle on dblclick wins on the tree column when enabled and the row has
1124
+ // children. Chevron's own ondblclick stops propagation, so this only fires for
1125
+ // dblclicks elsewhere in the cell. Leaves fall through to the edit logic below.
1126
+ if (
1127
+ isTreeMode &&
1128
+ column.isTree &&
1129
+ treeDoubleClickBehavior === "toggle" &&
1130
+ rowHasChildren(item)
1131
+ ) {
1132
+ toggleExpand(getRowPath(item))
1133
+ return
1134
+ }
830
1135
  if (!isCellEditable(column)) return
831
1136
  const trigger = getColumnEditTrigger(column)
832
1137
  // Double-click should work in both "dblclick" and "navigate" modes
@@ -1665,6 +1970,7 @@
1665
1970
  <input
1666
1971
  type="text"
1667
1972
  class="filter-input"
1973
+ class:invalid={filterValidity[String(column.field)] === false}
1668
1974
  placeholder="Filter..."
1669
1975
  value={filters[String(column.field)] || ""}
1670
1976
  oninput={(e) => handleFilter(String(column.field), e.currentTarget.value)}
@@ -1742,8 +2048,9 @@
1742
2048
  {/if}
1743
2049
  {#each columns as column, colIndex}
1744
2050
  {@const cellField = String(column.field)}
1745
- {@const cellInvalid = isCellInvalid(rowIndex, cellField)}
1746
- {@const cellError = getCellValidationError(rowIndex, cellField)}
2051
+ {@const cellRowKey = getRowId(item, rowIndex)}
2052
+ {@const cellInvalid = isCellInvalid(cellRowKey, cellField)}
2053
+ {@const cellError = getCellValidationError(cellRowKey, cellField)}
1747
2054
  <!-- svelte-ignore a11y_click_events_have_key_events a11y_no_noninteractive_element_interactions -->
1748
2055
  <td
1749
2056
  data-row={rowIndex}
@@ -1758,6 +2065,7 @@
1758
2065
  onkeydown={(e) => handleNavigationKeyDown(e, rowIndex, colIndex, column, item)}
1759
2066
  title={cellError || undefined}
1760
2067
  >
2068
+ {#snippet cellContent()}
1761
2069
  {#if isEditing(rowIndex, cellField)}
1762
2070
  {#if column.editor === "custom"}
1763
2071
  <!-- Custom editor - handled via callback, show indicator -->
@@ -1837,6 +2145,46 @@
1837
2145
  {/if}
1838
2146
  </div>
1839
2147
  {/if}
2148
+ {/snippet}
2149
+ {#if isTreeMode && column.isTree}
2150
+ {@const _path = getRowPath(item)}
2151
+ {@const _level = getRowLevel(item)}
2152
+ {@const _hasChildren = rowHasChildren(item)}
2153
+ {@const _expanded = isPathExpanded(_path)}
2154
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
2155
+ <div
2156
+ class="tree-cell"
2157
+ style={`padding-inline-start: ${_level * 1.25}rem`}
2158
+ onmousedown={(e) => {
2159
+ // Suppress the default text-selection extension that the second click of
2160
+ // a dblclick triggers — only when we're actually going to handle the
2161
+ // dblclick as a tree-toggle. e.detail counts clicks within the dblclick
2162
+ // interval, so > 1 == "this is the second+ click of a multi-click".
2163
+ if (e.detail > 1 && treeDoubleClickBehavior === "toggle" && _hasChildren) {
2164
+ e.preventDefault()
2165
+ }
2166
+ }}
2167
+ >
2168
+ {#if _hasChildren}
2169
+ <!-- svelte-ignore a11y_consider_explicit_label -->
2170
+ <button
2171
+ type="button"
2172
+ class="tree-chevron"
2173
+ class:expanded={_expanded}
2174
+ onclick={(e) => { e.stopPropagation(); toggleExpand(_path) }}
2175
+ ondblclick={(e) => e.stopPropagation()}
2176
+ title={_expanded ? "Collapse" : "Expand"}
2177
+ >
2178
+
2179
+ </button>
2180
+ {:else}
2181
+ <span class="tree-leaf-spacer" aria-hidden="true"></span>
2182
+ {/if}
2183
+ <div class="tree-cell-body">{@render cellContent()}</div>
2184
+ </div>
2185
+ {:else}
2186
+ {@render cellContent()}
2187
+ {/if}
1840
2188
  </td>
1841
2189
  {/each}
1842
2190
  {#if fillerColumn}
@@ -2002,9 +2350,12 @@
2002
2350
 
2003
2351
  /* Filler column: an empty trailing cell whose sole job is to absorb any leftover
2004
2352
  horizontal space so preceding columns keep their defined / auto widths instead
2005
- of stretching. No content, no padding, no interaction it just exists. */
2353
+ of stretching. `width: 100%` is the classic absorb-leftover trick in auto
2354
+ table-layout — other columns claim their content/defined widths first and the
2355
+ filler swallows whatever remains, instead of an arbitrary unspecified column
2356
+ ballooning to fill the table. */
2006
2357
  .filler-column {
2007
- width: auto;
2358
+ width: 100%;
2008
2359
  padding: 0;
2009
2360
  background: transparent;
2010
2361
  }
@@ -2084,6 +2435,27 @@
2084
2435
  box-shadow: 0 0 0 1px var(--accent-fill-rest, #0078d4);
2085
2436
  }
2086
2437
 
2438
+ /* Invalid state — surfaced when a column's `filter` callback returned null on the
2439
+ probe row (i.e. user typed something syntactically incomplete like `>`). Themes
2440
+ that override `--error-foreground` / `--error-fill-rest` get the right red. */
2441
+ .filter-input.invalid {
2442
+ border-color: var(--error-foreground, #d13438);
2443
+ }
2444
+
2445
+ .filter-input.invalid:focus {
2446
+ border-color: var(--error-foreground, #d13438);
2447
+ box-shadow: 0 0 0 1px var(--error-foreground, #d13438);
2448
+ }
2449
+
2450
+ [data-theme="dark"] .filter-input.invalid {
2451
+ border-color: var(--error-foreground, #f87c86);
2452
+ }
2453
+
2454
+ [data-theme="dark"] .filter-input.invalid:focus {
2455
+ border-color: var(--error-foreground, #f87c86);
2456
+ box-shadow: 0 0 0 1px var(--error-foreground, #f87c86);
2457
+ }
2458
+
2087
2459
  tbody tr {
2088
2460
  border-bottom: 1px solid var(--neutral-stroke-layer-rest, #e0e0e0);
2089
2461
  }
@@ -2108,6 +2480,60 @@
2108
2480
  font-style: italic;
2109
2481
  }
2110
2482
 
2483
+ /* Tree column: indentation + expand/collapse chevron.
2484
+ Use inline-flex (not flex) and don't set flex:1 on the body — otherwise the wrapper
2485
+ signals "fill all available space" to the table's auto layout and steals width from
2486
+ sibling columns. */
2487
+ .tree-cell {
2488
+ display: inline-flex;
2489
+ align-items: center;
2490
+ gap: 4px;
2491
+ }
2492
+
2493
+ .tree-cell-body {
2494
+ min-width: 0;
2495
+ }
2496
+
2497
+ .tree-chevron {
2498
+ all: unset;
2499
+ display: inline-flex;
2500
+ align-items: center;
2501
+ justify-content: center;
2502
+ width: 16px;
2503
+ height: 16px;
2504
+ border-radius: 2px;
2505
+ font-size: 9px;
2506
+ color: var(--neutral-foreground-hint, #707070);
2507
+ cursor: pointer;
2508
+ flex-shrink: 0;
2509
+ transition: transform 0.12s ease, background 0.1s ease;
2510
+ }
2511
+
2512
+ .tree-chevron:hover {
2513
+ background: var(--neutral-fill-secondary-hover, #f0f0f0);
2514
+ color: var(--neutral-foreground-rest, #242424);
2515
+ }
2516
+
2517
+ .tree-chevron.expanded {
2518
+ transform: rotate(90deg);
2519
+ }
2520
+
2521
+ .tree-leaf-spacer {
2522
+ display: inline-block;
2523
+ width: 16px;
2524
+ height: 16px;
2525
+ flex-shrink: 0;
2526
+ }
2527
+
2528
+ [data-theme="dark"] .tree-chevron {
2529
+ color: var(--neutral-foreground-hint, #a0a0a0);
2530
+ }
2531
+
2532
+ [data-theme="dark"] .tree-chevron:hover {
2533
+ background: var(--neutral-fill-secondary-hover, #3a3a3a);
2534
+ color: var(--neutral-foreground-rest, #e0e0e0);
2535
+ }
2536
+
2111
2537
  .pagination {
2112
2538
  display: flex;
2113
2539
  align-items: center;
@@ -24,6 +24,7 @@ declare function $$render<T>(): {
24
24
  format?: ((value: any, row: T) => string) | undefined;
25
25
  template?: ((row: T) => string) | undefined;
26
26
  snippet?: Snippet<[T]> | undefined;
27
+ filter?: ((filterValue: string, row: T) => boolean | null) | undefined;
27
28
  editable?: boolean;
28
29
  editor?: "number" | "select" | "text" | "custom" | "autocomplete" | "combobox" | "checkbox" | "date";
29
30
  editTrigger?: "button" | "click" | "dblclick" | "always" | "navigate";
@@ -97,6 +98,7 @@ declare function $$render<T>(): {
97
98
  cancel: () => void;
98
99
  }) => void) | undefined;
99
100
  showEditButton?: boolean;
101
+ isTree?: boolean;
100
102
  }[];
101
103
  sortable?: boolean;
102
104
  filterable?: boolean;
@@ -108,6 +110,10 @@ declare function $$render<T>(): {
108
110
  * horizontal space. Pair with per-column `width` / `minWidth` / `maxWidth` / `autoWidth` when you
109
111
  * want columns to keep predefined widths instead of stretching to justify across the table. */
110
112
  fillerColumn?: boolean;
113
+ /** Default `min-width` applied to every column header that does not specify its own `minWidth`.
114
+ * Useful with `fillerColumn` to stop content-sized columns from collapsing to a one-word column.
115
+ * Accepts any CSS length (e.g. `"100px"`, `"8rem"`). */
116
+ columnMinWidth?: string;
111
117
  class?: string;
112
118
  style?: string;
113
119
  cellTemplate?: SlotType;
@@ -116,7 +122,7 @@ declare function $$render<T>(): {
116
122
  dropdownShowOnFocus?: boolean;
117
123
  checkboxAlwaysEditable?: boolean;
118
124
  invalidCells?: {
119
- rowIndex: number;
125
+ rowKey: string;
120
126
  field: string;
121
127
  error: string;
122
128
  }[];
@@ -181,6 +187,7 @@ declare function $$render<T>(): {
181
187
  format?: ((value: any, row: T) => string) | undefined;
182
188
  template?: ((row: T) => string) | undefined;
183
189
  snippet?: Snippet<[T]> | undefined;
190
+ filter?: ((filterValue: string, row: T) => boolean | null) | undefined;
184
191
  editable?: boolean;
185
192
  editor?: "number" | "select" | "text" | "custom" | "autocomplete" | "combobox" | "checkbox" | "date";
186
193
  editTrigger?: "button" | "click" | "dblclick" | "always" | "navigate";
@@ -254,6 +261,7 @@ declare function $$render<T>(): {
254
261
  cancel: () => void;
255
262
  }) => void) | undefined;
256
263
  showEditButton?: boolean;
264
+ isTree?: boolean;
257
265
  };
258
266
  cellValue: unknown;
259
267
  }) => string);
@@ -283,6 +291,7 @@ declare function $$render<T>(): {
283
291
  format?: ((value: any, row: T) => string) | undefined;
284
292
  template?: ((row: T) => string) | undefined;
285
293
  snippet?: Snippet<[T]> | undefined;
294
+ filter?: ((filterValue: string, row: T) => boolean | null) | undefined;
286
295
  editable?: boolean;
287
296
  editor?: "number" | "select" | "text" | "custom" | "autocomplete" | "combobox" | "checkbox" | "date";
288
297
  editTrigger?: "button" | "click" | "dblclick" | "always" | "navigate";
@@ -356,6 +365,7 @@ declare function $$render<T>(): {
356
365
  cancel: () => void;
357
366
  }) => void) | undefined;
358
367
  showEditButton?: boolean;
368
+ isTree?: boolean;
359
369
  };
360
370
  cellValue: unknown;
361
371
  }) => boolean) | undefined;
@@ -384,6 +394,7 @@ declare function $$render<T>(): {
384
394
  format?: ((value: any, row: T) => string) | undefined;
385
395
  template?: ((row: T) => string) | undefined;
386
396
  snippet?: Snippet<[T]> | undefined;
397
+ filter?: ((filterValue: string, row: T) => boolean | null) | undefined;
387
398
  editable?: boolean;
388
399
  editor?: "number" | "select" | "text" | "custom" | "autocomplete" | "combobox" | "checkbox" | "date";
389
400
  editTrigger?: "button" | "click" | "dblclick" | "always" | "navigate";
@@ -457,6 +468,7 @@ declare function $$render<T>(): {
457
468
  cancel: () => void;
458
469
  }) => void) | undefined;
459
470
  showEditButton?: boolean;
471
+ isTree?: boolean;
460
472
  };
461
473
  cellValue: unknown;
462
474
  }) => boolean) | undefined;
@@ -487,6 +499,7 @@ declare function $$render<T>(): {
487
499
  format?: ((value: any, row: T) => string) | undefined;
488
500
  template?: ((row: T) => string) | undefined;
489
501
  snippet?: Snippet<[T]> | undefined;
502
+ filter?: ((filterValue: string, row: T) => boolean | null) | undefined;
490
503
  editable?: boolean;
491
504
  editor?: "number" | "select" | "text" | "custom" | "autocomplete" | "combobox" | "checkbox" | "date";
492
505
  editTrigger?: "button" | "click" | "dblclick" | "always" | "navigate";
@@ -560,6 +573,7 @@ declare function $$render<T>(): {
560
573
  cancel: () => void;
561
574
  }) => void) | undefined;
562
575
  showEditButton?: boolean;
576
+ isTree?: boolean;
563
577
  };
564
578
  cellValue: unknown;
565
579
  }) => void | Promise<void>) | undefined;
@@ -589,6 +603,7 @@ declare function $$render<T>(): {
589
603
  format?: ((value: any, row: T) => string) | undefined;
590
604
  template?: ((row: T) => string) | undefined;
591
605
  snippet?: Snippet<[T]> | undefined;
606
+ filter?: ((filterValue: string, row: T) => boolean | null) | undefined;
592
607
  editable?: boolean;
593
608
  editor?: "number" | "select" | "text" | "custom" | "autocomplete" | "combobox" | "checkbox" | "date";
594
609
  editTrigger?: "button" | "click" | "dblclick" | "always" | "navigate";
@@ -662,9 +677,20 @@ declare function $$render<T>(): {
662
677
  cancel: () => void;
663
678
  }) => void) | undefined;
664
679
  showEditButton?: boolean;
680
+ isTree?: boolean;
665
681
  };
666
682
  cellValue: unknown;
667
683
  }) => void) | undefined;
684
+ idMember?: keyof T | undefined;
685
+ treePathMember?: keyof T | undefined;
686
+ treeLevelMember?: keyof T | undefined;
687
+ treeParentMember?: keyof T | undefined;
688
+ treeSeparator?: string;
689
+ treeDataSorted?: boolean;
690
+ expandedPaths?: Set<string>;
691
+ defaultExpandDepth?: number;
692
+ treeDoubleClickBehavior?: "none" | "toggle";
693
+ onfilterchange?: (filters: Record<string, string>) => void;
668
694
  onrowchange?: ((detail: {
669
695
  row: T;
670
696
  draftRow: T;
@@ -731,7 +757,7 @@ declare function $$render<T>(): {
731
757
  }) => void) | undefined;
732
758
  };
733
759
  exports: {};
734
- bindings: "invalidCells";
760
+ bindings: "invalidCells" | "expandedPaths";
735
761
  slots: {};
736
762
  events: {};
737
763
  };
@@ -739,7 +765,7 @@ declare class __sveltets_Render<T> {
739
765
  props(): ReturnType<typeof $$render<T>>['props'];
740
766
  events(): ReturnType<typeof $$render<T>>['events'];
741
767
  slots(): ReturnType<typeof $$render<T>>['slots'];
742
- bindings(): "invalidCells";
768
+ bindings(): "invalidCells" | "expandedPaths";
743
769
  exports(): {};
744
770
  }
745
771
  interface $$IsomorphicComponent {
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "1.0.0-rc14";
1
+ export declare const VERSION = "1.0.0-rc15";
package/dist/version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  // Auto-generated by scripts/pre-package.js — do NOT edit by hand.
2
2
  // In dev this file holds whatever value was committed last; on each `npm run package`
3
3
  // the pre-package script overwrites it with the current package.json version.
4
- export const VERSION = "1.0.0-rc14";
4
+ export const VERSION = "1.0.0-rc15";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-fluentui",
3
- "version": "1.0.0-rc14",
3
+ "version": "1.0.0-rc15",
4
4
  "description": "A Svelte wrapper library for Microsoft FluentUI web components",
5
5
  "license": "MIT",
6
6
  "author": "KeenMate",