@ibobbyts/svelte-ui-utils 0.2.13 → 0.3.1

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
@@ -10,19 +10,19 @@ Install from the public npm registry. No GitHub Packages token is required.
10
10
  With Bun:
11
11
 
12
12
  ```bash
13
- bun add @ibobbyts/svelte-ui-utils@0.2.3
13
+ bun add @ibobbyts/svelte-ui-utils@0.3.1
14
14
  ```
15
15
 
16
16
  Canonical Bun pull address:
17
17
 
18
18
  ```text
19
- @ibobbyts/svelte-ui-utils@0.2.3
19
+ @ibobbyts/svelte-ui-utils@0.3.1
20
20
  ```
21
21
 
22
22
  With npm:
23
23
 
24
24
  ```bash
25
- npm install @ibobbyts/svelte-ui-utils@0.2.3
25
+ npm install @ibobbyts/svelte-ui-utils@0.3.1
26
26
  ```
27
27
 
28
28
  The repository does not track `dist/`; releases and local integration builds run
@@ -82,7 +82,8 @@ Dropdown menus default to `placement="auto"` and open toward whichever side of
82
82
  the trigger has more available viewport space. The direction is recalculated
83
83
  while the menu is open when the viewport is resized or a scroll container
84
84
  moves. Set `placement="up"` or `placement="down"` to force a direction. Use
85
- `fitViewport={true}` as well to constrain the panel height to the chosen side.
85
+ `fitViewport={false}` to disable the default panel-height constraint for the
86
+ chosen side.
86
87
 
87
88
  ## DropdownSearch
88
89
 
@@ -363,7 +364,6 @@ prop is available on all dialog wrapper components.
363
364
  options={pageSizeOptions}
364
365
  ariaLabel="Rows"
365
366
  placement="down"
366
- menuAlign="left"
367
367
  onChange={(next) => {
368
368
  pageSize = next;
369
369
  }}
@@ -372,19 +372,86 @@ prop is available on all dialog wrapper components.
372
372
 
373
373
  `Dropdown` is a controlled select-like component for simple option lists. Use
374
374
  `placement="up"` when the menu should open above the trigger, such as bottom
375
- pagination bars. Use `menuAlign="left"` when the expanded menu should share the
376
- trigger's left edge; the default is `right` for backward compatibility.
377
- `fitContent` can be combined with `menuAlign="left"` to size the menu to its
378
- longest option while keeping its left edge aligned. `DataTable` uses this same
379
- component for its page-size picker.
380
-
381
- The dropdown panel defaults to the viewport height (`100vh`) and remains
382
- scrollable when its contents exceed that height. When `fitViewport` is enabled,
383
- the component replaces that default with the space available above or below the
384
- trigger.
385
-
386
- The original flat `options` API and all default behavior remain unchanged. New
387
- callers may opt into grouped options with `optionGroups`; each group can have an
375
+ pagination bars. The expanded menu shares the trigger's left edge by default;
376
+ use `menuAlign="right"` to align their right edges instead. `fitContent` sizes
377
+ the menu to its longest option while keeping the selected edge aligned.
378
+ `DataTable` uses this same component for its page-size picker.
379
+
380
+ The dropdown panel is constrained by default to the space available above or
381
+ below the trigger and remains scrollable when its contents exceed that height.
382
+ Set `fitViewport={false}` to fall back to the stylesheet's viewport-height
383
+ limit (`100vh`).
384
+
385
+ ### Allowing a Dropdown menu to extend outside a modal
386
+
387
+ `fitViewport` chooses the opening direction and limits the panel height against
388
+ the browser viewport. It does not portal the menu to `document.body`, so an
389
+ ancestor with `overflow: auto` or `overflow: hidden` can still clip the menu or
390
+ include it in the ancestor's scrollable overflow area.
391
+
392
+ For a modal whose content otherwise fits without scrolling, use a full-width
393
+ positioning boundary around the Dropdown and release the modal overflow only
394
+ while the menu exists:
395
+
396
+ ```svelte
397
+ <div class="app-modal">
398
+ <div class="app-modal__body">
399
+ <div class="dropdown-overflow-boundary">
400
+ <Dropdown
401
+ value={selectedValue}
402
+ options={options}
403
+ onChange={(next) => {
404
+ selectedValue = next;
405
+ }}
406
+ />
407
+ </div>
408
+ </div>
409
+ </div>
410
+ ```
411
+
412
+ ```css
413
+ .dropdown-overflow-boundary {
414
+ position: relative;
415
+ }
416
+
417
+ /* Position the absolute menu against the full-width boundary, not the
418
+ intrinsic-width Dropdown trigger. */
419
+ .dropdown-overflow-boundary .suu-dropdown {
420
+ position: static;
421
+ }
422
+
423
+ .dropdown-overflow-boundary .suu-dropdown__menu {
424
+ width: max-content;
425
+ min-width: 0;
426
+ max-width: 100%;
427
+ }
428
+
429
+ .dropdown-overflow-boundary .suu-dropdown__option {
430
+ white-space: normal;
431
+ overflow-wrap: anywhere;
432
+ }
433
+
434
+ /* :has() scopes the overflow exception to the time the menu is open. */
435
+ .app-modal:has(.dropdown-overflow-boundary .suu-dropdown__menu),
436
+ .app-modal:has(.dropdown-overflow-boundary .suu-dropdown__menu) .app-modal__body {
437
+ overflow: visible;
438
+ }
439
+ ```
440
+
441
+ This keeps the trigger at its intrinsic width, lets the menu grow toward its
442
+ aligned edge up to the modal content width, wraps longer options, and allows
443
+ the menu to paint outside the modal. The default `fitViewport={true}` still
444
+ sets the panel's upper or lower limit from the browser viewport and keeps an
445
+ oversized option list scrollable.
446
+
447
+ This CSS pattern intentionally does not implement a portal. If the modal body
448
+ must remain independently scrollable while the menu is open, use a portal or
449
+ floating-layer component instead; releasing `overflow` would change that
450
+ scrolling contract. `:has()` also requires a browser that supports the CSS
451
+ relational pseudo-class.
452
+
453
+ The original flat `options` API remains available. New callers may opt into
454
+ grouped options with `optionGroups`; each group can have an
388
455
  optional accessible label and its options keep the same keyboard and disabled
389
456
  semantics. `width`, `minWidth`, `maxWidth`, and `className` are optional styling
390
457
  hooks. `onTriggerClick` runs before the dropdown toggles, so a nested control
@@ -640,7 +707,7 @@ consumer project and installs the released npm version without registry-specific
640
707
  tokens:
641
708
 
642
709
  ```bash
643
- bun add @ibobbyts/svelte-ui-utils@0.2.0 svelte
710
+ bun add @ibobbyts/svelte-ui-utils@0.3.1 svelte
644
711
  ```
645
712
 
646
713
  Use it after each release when a Bun-based project will consume the package.
@@ -18,8 +18,8 @@
18
18
  export let optionGroups: DropdownOptionGroup[] | undefined = undefined;
19
19
  export let ariaLabel: string | undefined = undefined;
20
20
  export let placement: DropdownPlacement = 'auto';
21
- export let menuAlign: DropdownMenuAlign = 'right';
22
- export let fitViewport = false;
21
+ export let menuAlign: DropdownMenuAlign = 'left';
22
+ export let fitViewport = true;
23
23
  export let fitContent = false;
24
24
  export let disabled = false;
25
25
  export let width: string | undefined = undefined;
@@ -76,32 +76,35 @@
76
76
  }
77
77
  </script>
78
78
 
79
- <table class={`suu-sortable-table ${tableClass}`.trim()}>
80
- <thead><tr><th class="suu-sortable-table__drag-column" aria-hidden="true"></th>{@render header()}<th class="suu-sortable-table__remove-column" aria-hidden="true"></th></tr></thead>
81
- <tbody>
82
- {#each items as item, index (getId(item))}
83
- {@const id = getId(item)}
84
- {@const dragLabel = getDragLabel(item, index)}
85
- {@const removeLabel = getRemoveLabel(item, index)}
86
- <tr class={rowClass(item, index)} data-sortable-id={id} ondragover={(event) => dragOver(event, id)} ondrop={(event) => drop(event, id)}>
87
- <td class="suu-sortable-table__drag-cell">
88
- <button type="button" class="suu-sortable-table__drag-handle" disabled={disabled} draggable={!disabled} aria-label={dragLabel} title={dragLabel} ondragstart={(event) => startDrag(event, id)} ondragend={finishDrag}>⋮⋮</button>
89
- {#if dragAccessory}{@render dragAccessory(item, index)}{/if}
90
- </td>
91
- {@render children(item, index)}
92
- <td class="suu-sortable-table__remove-cell">
93
- {#if onRemove}
94
- <button type="button" class="suu-sortable-table__remove" disabled={disabled || (!allowRemoveLast && items.length === 1)} aria-label={removeLabel} title={removeLabel} onclick={() => onRemove?.(item)}>
95
- <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 3h6l1 2h4v2H4V5h4l1-2Zm-2 6h2v9H7V9Zm4 0h2v9h-2V9Zm4 0h2v9h-2V9ZM6 21V8h12v13H6Z" fill="currentColor"/></svg>
96
- </button>
97
- {/if}
98
- </td>
99
- </tr>
100
- {/each}
101
- </tbody>
102
- </table>
79
+ <div class="suu-sortable-table-wrap">
80
+ <table class={`suu-sortable-table ${tableClass}`.trim()}>
81
+ <thead><tr><th class="suu-sortable-table__drag-column" aria-hidden="true"></th>{@render header()}<th class="suu-sortable-table__remove-column" aria-hidden="true"></th></tr></thead>
82
+ <tbody>
83
+ {#each items as item, index (getId(item))}
84
+ {@const id = getId(item)}
85
+ {@const dragLabel = getDragLabel(item, index)}
86
+ {@const removeLabel = getRemoveLabel(item, index)}
87
+ <tr class={rowClass(item, index)} data-sortable-id={id} ondragover={(event) => dragOver(event, id)} ondrop={(event) => drop(event, id)}>
88
+ <td class="suu-sortable-table__drag-cell">
89
+ <button type="button" class="suu-sortable-table__drag-handle" disabled={disabled} draggable={!disabled} aria-label={dragLabel} title={dragLabel} ondragstart={(event) => startDrag(event, id)} ondragend={finishDrag}>⋮⋮</button>
90
+ {#if dragAccessory}{@render dragAccessory(item, index)}{/if}
91
+ </td>
92
+ {@render children(item, index)}
93
+ <td class="suu-sortable-table__remove-cell">
94
+ {#if onRemove}
95
+ <button type="button" class="suu-sortable-table__remove" disabled={disabled || (!allowRemoveLast && items.length === 1)} aria-label={removeLabel} title={removeLabel} onclick={() => onRemove?.(item)}>
96
+ <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 3h6l1 2h4v2H4V5h4l1-2Zm-2 6h2v9H7V9Zm4 0h2v9h-2V9Zm4 0h2v9h-2V9ZM6 21V8h12v13H6Z" fill="currentColor"/></svg>
97
+ </button>
98
+ {/if}
99
+ </td>
100
+ </tr>
101
+ {/each}
102
+ </tbody>
103
+ </table>
104
+ </div>
103
105
 
104
106
  <style>
107
+ .suu-sortable-table-wrap { overflow: auto; border: 1px solid var(--suu-color-border, #d1d5db); border-radius: var(--suu-radius, 8px); }
105
108
  .suu-sortable-table { width: 100%; border-collapse: collapse; }
106
109
  .suu-sortable-table__drag-column, .suu-sortable-table__remove-column { width: 38px; }
107
110
  .suu-sortable-table__drag-cell, .suu-sortable-table__remove-cell { width: 38px; padding: 6px; text-align: center; vertical-align: middle; }
@@ -111,6 +114,7 @@
111
114
  .suu-sortable-table__drag-handle:active { cursor: grabbing; }
112
115
  .suu-sortable-table__drag-handle:disabled, .suu-sortable-table__remove:disabled { cursor: default; opacity: .35; }
113
116
  .suu-sortable-table__row { position: relative; border-bottom: 1px solid var(--suu-color-border, #d1d5db); }
117
+ .suu-sortable-table__row:last-child { border-bottom: 0; }
114
118
  .suu-sortable-table :global(td) { border-bottom: 0; }
115
119
  .suu-sortable-table__row--dragging { opacity: .55; }
116
120
  .suu-sortable-table__row--drop-before :global(td) { box-shadow: inset 0 3px 0 var(--suu-color-accent, #2563eb); }
@@ -1 +1 @@
1
- {"version":3,"file":"SortableTable.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/sortable-table/SortableTable.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,KAAK,EAAE,iBAAiB,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AAChF,iBAAS,QAAQ,CAAC,IAAI,SAAS,iBAAiB;;gBAKpC,IAAI,EAAE;gBACN,CAAC,IAAI,EAAE,IAAI,KAAK,MAAM;mBACnB,OAAO;0BACA,OAAO;qBACZ,MAAM;sBACL,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,GAAG,IAAI;uBACvD,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM;yBACnC,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM;oBAC1C,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,0BAA0B,KAAK,IAAI;mBAC5D,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI;wBACf,OAAO,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;gBAC/B,OAAO;kBACL,OAAO,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;;;;;;EA0FoF;AACzH,cAAM,iBAAiB,CAAC,IAAI,SAAS,iBAAiB;IAClD,KAAK,IAAI,UAAU,CAAC,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC;IACnD,MAAM,IAAI,UAAU,CAAC,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC;IACrD,KAAK,IAAI,UAAU,CAAC,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC;IACnD,QAAQ;IACR,OAAO;CACV;AAED,UAAU,qBAAqB;IAC3B,KAAK,IAAI,SAAS,iBAAiB,EAAE,OAAO,EAAE,OAAO,QAAQ,EAAE,2BAA2B,CAAC,UAAU,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,QAAQ,EAAE,eAAe,CAAC,UAAU,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,CAAC,CAAA;KAAE,GAAG,UAAU,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IAC3a,CAAC,IAAI,SAAS,iBAAiB,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,GAAG,UAAU,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IAC9J,YAAY,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;CACjE;AACD,QAAA,MAAM,aAAa,EAAE,qBAAmC,CAAC;AACvC,KAAK,aAAa,CAAC,IAAI,SAAS,iBAAiB,IAAI,YAAY,CAAC,OAAO,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC;AAChG,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"SortableTable.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/sortable-table/SortableTable.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,KAAK,EAAE,iBAAiB,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AAChF,iBAAS,QAAQ,CAAC,IAAI,SAAS,iBAAiB;;gBAKpC,IAAI,EAAE;gBACN,CAAC,IAAI,EAAE,IAAI,KAAK,MAAM;mBACnB,OAAO;0BACA,OAAO;qBACZ,MAAM;sBACL,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,GAAG,IAAI;uBACvD,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM;yBACnC,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM;oBAC1C,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,0BAA0B,KAAK,IAAI;mBAC5D,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI;wBACf,OAAO,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;gBAC/B,OAAO;kBACL,OAAO,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;;;;;;EA4FoF;AACzH,cAAM,iBAAiB,CAAC,IAAI,SAAS,iBAAiB;IAClD,KAAK,IAAI,UAAU,CAAC,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC;IACnD,MAAM,IAAI,UAAU,CAAC,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC;IACrD,KAAK,IAAI,UAAU,CAAC,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC;IACnD,QAAQ;IACR,OAAO;CACV;AAED,UAAU,qBAAqB;IAC3B,KAAK,IAAI,SAAS,iBAAiB,EAAE,OAAO,EAAE,OAAO,QAAQ,EAAE,2BAA2B,CAAC,UAAU,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,QAAQ,EAAE,eAAe,CAAC,UAAU,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,CAAC,CAAA;KAAE,GAAG,UAAU,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IAC3a,CAAC,IAAI,SAAS,iBAAiB,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,GAAG,UAAU,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IAC9J,YAAY,CAAC,EAAE,UAAU,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;CACjE;AACD,QAAA,MAAM,aAAa,EAAE,qBAAmC,CAAC;AACvC,KAAK,aAAa,CAAC,IAAI,SAAS,iBAAiB,IAAI,YAAY,CAAC,OAAO,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC;AAChG,eAAe,aAAa,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ibobbyts/svelte-ui-utils",
3
- "version": "0.2.13",
3
+ "version": "0.3.1",
4
4
  "description": "Reusable Svelte 5 UI utilities for dialogs, toast notifications, dropdown search, and data tables.",
5
5
  "type": "module",
6
6
  "license": "MIT",