@ibobbyts/svelte-ui-utils 0.3.1 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -26,6 +26,7 @@
26
26
  export let minWidth: string | undefined = undefined;
27
27
  export let maxWidth: string | undefined = undefined;
28
28
  export let className: string | undefined = undefined;
29
+ export let portal = false;
29
30
  export let onChange: DropdownChangeHandler | undefined = undefined;
30
31
  export let onTriggerClick: DropdownTriggerClickHandler | undefined = undefined;
31
32
 
@@ -36,8 +37,13 @@
36
37
  let activeValue: DropdownValue = value;
37
38
  let dropdownElement: HTMLSpanElement | undefined;
38
39
  let buttonElement: HTMLButtonElement | undefined;
40
+ let menuElement: HTMLDivElement | undefined;
39
41
  let resolvedPlacement: Exclude<DropdownPlacement, 'auto'> = placement === 'up' ? 'up' : 'down';
40
42
  let viewportPanelMaxHeight: string | undefined = undefined;
43
+ let portalMenuTop = '-9999px';
44
+ let portalMenuLeft: string | undefined = undefined;
45
+ let portalMenuRight: string | undefined = undefined;
46
+ let portalMenuWidth: string | undefined = undefined;
41
47
  let removeOpenViewportListeners: (() => void) | undefined = undefined;
42
48
 
43
49
  $: resolvedOptions = optionGroups === undefined ? options : optionGroups.flatMap((group) => group.options);
@@ -147,6 +153,9 @@
147
153
 
148
154
  function handleFocusout(event: FocusEvent) {
149
155
  const nextTarget = event.relatedTarget;
156
+ if (portal && nextTarget instanceof Node && menuElement?.contains(nextTarget)) {
157
+ return;
158
+ }
150
159
  if (nextTarget instanceof Node && event.currentTarget instanceof Node && event.currentTarget.contains(nextTarget)) {
151
160
  return;
152
161
  }
@@ -204,6 +213,7 @@
204
213
 
205
214
  updateResolvedPlacement(rect);
206
215
  if (!fitViewport) {
216
+ updatePortalPosition(rect);
207
217
  return;
208
218
  }
209
219
 
@@ -212,6 +222,39 @@
212
222
  ? rect.top - viewportMargin - menuGap
213
223
  : window.innerHeight - rect.bottom - viewportMargin - menuGap;
214
224
  viewportPanelMaxHeight = `${Math.max(0, Math.floor(available))}px`;
225
+ updatePortalPosition(rect);
226
+ }
227
+
228
+ function updatePortalPosition(rect: DOMRect) {
229
+ if (!portal || typeof window === 'undefined') {
230
+ return;
231
+ }
232
+
233
+ const menuHeight = menuElement?.getBoundingClientRect().height ?? 0;
234
+ portalMenuTop = `${Math.round(
235
+ resolvedPlacement === 'up' ? rect.top - menuHeight - menuGap : rect.bottom + menuGap
236
+ )}px`;
237
+ if (menuAlign === 'right') {
238
+ portalMenuLeft = undefined;
239
+ portalMenuRight = `${Math.round(window.innerWidth - rect.right)}px`;
240
+ } else {
241
+ portalMenuLeft = `${Math.round(rect.left)}px`;
242
+ portalMenuRight = undefined;
243
+ }
244
+ portalMenuWidth = fitContent ? undefined : `${Math.round(rect.width)}px`;
245
+ }
246
+
247
+ function portalMenu(node: HTMLDivElement, enabled: boolean) {
248
+ if (enabled && typeof document !== 'undefined') {
249
+ document.body.appendChild(node);
250
+ }
251
+ return {
252
+ destroy() {
253
+ if (node.parentNode) {
254
+ node.parentNode.removeChild(node);
255
+ }
256
+ }
257
+ };
215
258
  }
216
259
 
217
260
  function enableOpenViewportTracking() {
@@ -267,18 +310,25 @@
267
310
  on:click={handleTriggerClick}
268
311
  on:keydown={handleKeydown}
269
312
  >
270
- <span>{selectedText}</span>
313
+ <span class="suu-dropdown__label">{selectedText}</span>
271
314
  <span class="suu-dropdown__chevron" aria-hidden="true"></span>
272
315
  </button>
273
316
 
274
317
  {#if open}
275
318
  <div
319
+ bind:this={menuElement}
320
+ use:portalMenu={portal}
276
321
  class="suu-dropdown__menu"
322
+ class:suu-dropdown__menu--portal={portal}
277
323
  class:suu-dropdown__menu--up={resolvedPlacement === 'up'}
278
324
  class:suu-dropdown__menu--down={resolvedPlacement === 'down'}
279
325
  class:suu-dropdown__menu--left={menuAlign === 'left'}
280
326
  class:suu-dropdown__menu--right={menuAlign === 'right'}
281
327
  class:suu-dropdown__menu--fit-content={fitContent}
328
+ style:--suu-dropdown-menu-top={portalMenuTop}
329
+ style:--suu-dropdown-menu-left={portalMenuLeft}
330
+ style:--suu-dropdown-menu-right={portalMenuRight}
331
+ style:--suu-dropdown-menu-width={portalMenuWidth}
282
332
  >
283
333
  <div class="suu-dropdown__panel" role="listbox" aria-label={ariaLabel}>
284
334
  {#if optionGroups === undefined}
@@ -301,7 +351,7 @@
301
351
  }}
302
352
  on:click={() => selectOption(option)}
303
353
  >
304
- {option.label}
354
+ <span class="suu-dropdown__label">{option.label}</span>
305
355
  </button>
306
356
  {/each}
307
357
  {:else}
@@ -329,7 +379,7 @@
329
379
  }}
330
380
  on:click={() => selectOption(option)}
331
381
  >
332
- {option.label}
382
+ <span class="suu-dropdown__label">{option.label}</span>
333
383
  </button>
334
384
  {/each}
335
385
  </div>
@@ -27,6 +27,7 @@ declare const Dropdown: $$__sveltets_2_IsomorphicComponent<{
27
27
  minWidth?: string | undefined;
28
28
  maxWidth?: string | undefined;
29
29
  className?: string | undefined;
30
+ portal?: boolean;
30
31
  onChange?: DropdownChangeHandler | undefined;
31
32
  onTriggerClick?: DropdownTriggerClickHandler | undefined;
32
33
  }, {
@@ -1 +1 @@
1
- {"version":3,"file":"Dropdown.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/dropdown/Dropdown.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACR,qBAAqB,EACrB,iBAAiB,EACjB,cAAc,EACd,mBAAmB,EACnB,iBAAiB,EACjB,2BAA2B,EAC3B,aAAa,EACd,MAAM,YAAY,CAAC;AA6RtB,UAAU,kCAAkC,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,OAAO,GAAG,EAAE,EAAE,QAAQ,GAAG,MAAM;IACpM,KAAK,OAAO,EAAE,OAAO,QAAQ,EAAE,2BAA2B,CAAC,KAAK,CAAC,GAAG,OAAO,QAAQ,EAAE,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,QAAQ,CAAA;KAAE,GAAG,OAAO,CAAC;IACjK,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,GAAG;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,KAAK,CAAA;KAAC,GAAG,OAAO,GAAG;QAAE,IAAI,CAAC,EAAE,GAAG,CAAC;QAAC,GAAG,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC;IAC9G,YAAY,CAAC,EAAE,QAAQ,CAAC;CAC3B;AAKD,QAAA,MAAM,QAAQ;SAVuW,MAAM,GAAG,SAAS;YAAU,aAAa;cAAY,cAAc,EAAE;mBAAiB,mBAAmB,EAAE,GAAG,SAAS;gBAAc,MAAM,GAAG,SAAS;gBAAc,iBAAiB;gBAAc,iBAAiB;;;;YAAwG,MAAM,GAAG,SAAS;eAAa,MAAM,GAAG,SAAS;eAAa,MAAM,GAAG,SAAS;gBAAc,MAAM,GAAG,SAAS;eAAa,qBAAqB,GAAG,SAAS;qBAAmB,2BAA2B,GAAG,SAAS;;;kBAU7yB,CAAC;AAC5E,KAAK,QAAQ,GAAG,YAAY,CAAC,OAAO,QAAQ,CAAC,CAAC;AAChD,eAAe,QAAQ,CAAC"}
1
+ {"version":3,"file":"Dropdown.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/dropdown/Dropdown.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACR,qBAAqB,EACrB,iBAAiB,EACjB,cAAc,EACd,mBAAmB,EACnB,iBAAiB,EACjB,2BAA2B,EAC3B,aAAa,EACd,MAAM,YAAY,CAAC;AAwUtB,UAAU,kCAAkC,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,OAAO,GAAG,EAAE,EAAE,QAAQ,GAAG,MAAM;IACpM,KAAK,OAAO,EAAE,OAAO,QAAQ,EAAE,2BAA2B,CAAC,KAAK,CAAC,GAAG,OAAO,QAAQ,EAAE,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,QAAQ,CAAA;KAAE,GAAG,OAAO,CAAC;IACjK,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,GAAG;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,KAAK,CAAA;KAAC,GAAG,OAAO,GAAG;QAAE,IAAI,CAAC,EAAE,GAAG,CAAC;QAAC,GAAG,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC;IAC9G,YAAY,CAAC,EAAE,QAAQ,CAAC;CAC3B;AAKD,QAAA,MAAM,QAAQ;SAVwX,MAAM,GAAG,SAAS;YAAU,aAAa;cAAY,cAAc,EAAE;mBAAiB,mBAAmB,EAAE,GAAG,SAAS;gBAAc,MAAM,GAAG,SAAS;gBAAc,iBAAiB;gBAAc,iBAAiB;;;;YAAwG,MAAM,GAAG,SAAS;eAAa,MAAM,GAAG,SAAS;eAAa,MAAM,GAAG,SAAS;gBAAc,MAAM,GAAG,SAAS;;eAAqC,qBAAqB,GAAG,SAAS;qBAAmB,2BAA2B,GAAG,SAAS;;;kBAUt1B,CAAC;AAC5E,KAAK,QAAQ,GAAG,YAAY,CAAC,OAAO,QAAQ,CAAC,CAAC;AAChD,eAAe,QAAQ,CAAC"}
package/dist/style.css CHANGED
@@ -1128,7 +1128,8 @@
1128
1128
  gap: var(--suu-dropdown-space);
1129
1129
  align-items: center;
1130
1130
  justify-content: space-between;
1131
- height: 42px;
1131
+ min-height: 42px;
1132
+ height: auto;
1132
1133
  padding: 0 var(--suu-dropdown-space);
1133
1134
  border: 1px solid var(--suu-color-border);
1134
1135
  border-radius: var(--suu-radius);
@@ -1137,6 +1138,21 @@
1137
1138
  cursor: pointer;
1138
1139
  font: inherit;
1139
1140
  line-height: 1;
1141
+ text-align: left;
1142
+ }
1143
+
1144
+ .suu-dropdown__label {
1145
+ flex: 1 1 auto;
1146
+ min-width: 0;
1147
+ overflow: hidden;
1148
+ text-overflow: ellipsis;
1149
+ display: -webkit-box;
1150
+ -webkit-box-orient: vertical;
1151
+ -webkit-line-clamp: 2;
1152
+ line-clamp: 2;
1153
+ white-space: normal;
1154
+ overflow-wrap: break-word;
1155
+ line-height: 1.2;
1140
1156
  }
1141
1157
 
1142
1158
  .suu-dropdown--sized .suu-dropdown__button {
@@ -1155,6 +1171,7 @@
1155
1171
  }
1156
1172
 
1157
1173
  .suu-dropdown__chevron {
1174
+ flex: 0 0 auto;
1158
1175
  width: 0.58em;
1159
1176
  height: 0.58em;
1160
1177
  border-right: 2px solid currentcolor;
@@ -1176,17 +1193,26 @@
1176
1193
  box-shadow: 0 18px 42px rgb(15 23 42 / 0.16);
1177
1194
  }
1178
1195
 
1179
- .suu-dropdown__menu--left {
1196
+ .suu-dropdown__menu--portal {
1197
+ position: fixed;
1198
+ top: var(--suu-dropdown-menu-top);
1199
+ right: var(--suu-dropdown-menu-right, auto);
1200
+ left: var(--suu-dropdown-menu-left, auto);
1201
+ width: var(--suu-dropdown-menu-width, max-content);
1202
+ z-index: 1200;
1203
+ }
1204
+
1205
+ .suu-dropdown__menu--left:not(.suu-dropdown__menu--portal) {
1180
1206
  right: auto;
1181
1207
  left: 0;
1182
1208
  }
1183
1209
 
1184
- .suu-dropdown__menu--right {
1210
+ .suu-dropdown__menu--right:not(.suu-dropdown__menu--portal) {
1185
1211
  right: 0;
1186
1212
  left: auto;
1187
1213
  }
1188
1214
 
1189
- .suu-dropdown__menu--up {
1215
+ .suu-dropdown__menu--up:not(.suu-dropdown__menu--portal) {
1190
1216
  top: auto;
1191
1217
  bottom: calc(100% + 6px);
1192
1218
  }
@@ -1205,6 +1231,16 @@
1205
1231
  white-space: nowrap;
1206
1232
  }
1207
1233
 
1234
+ .suu-dropdown__menu--fit-content .suu-dropdown__option .suu-dropdown__label {
1235
+ overflow: visible;
1236
+ text-overflow: clip;
1237
+ display: block;
1238
+ -webkit-line-clamp: unset;
1239
+ line-clamp: unset;
1240
+ white-space: nowrap;
1241
+ overflow-wrap: normal;
1242
+ }
1243
+
1208
1244
  .suu-dropdown__panel {
1209
1245
  max-height: var(--suu-dropdown-panel-max-height, 100vh);
1210
1246
  overflow-y: auto;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ibobbyts/svelte-ui-utils",
3
- "version": "0.3.1",
3
+ "version": "0.3.4",
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",