svelte-select-5 6.0.0

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 (123) hide show
  1. package/.claude/settings.local.json +20 -0
  2. package/.prettierignore +2 -0
  3. package/.prettierrc +17 -0
  4. package/CHANGELOG.md +571 -0
  5. package/LICENSE +9 -0
  6. package/MIGRATION_GUIDE.md +98 -0
  7. package/README.md +316 -0
  8. package/docs/generate_theming_variables_md.cjs +53 -0
  9. package/docs/theming_variables.md +113 -0
  10. package/jsconfig.json +3 -0
  11. package/package.json +77 -0
  12. package/rollup.config.js +33 -0
  13. package/src/app.html +29 -0
  14. package/src/global.d.ts +1 -0
  15. package/src/lib/ChevronIcon.svelte +22 -0
  16. package/src/lib/ClearIcon.svelte +22 -0
  17. package/src/lib/LoadingIcon.svelte +33 -0
  18. package/src/lib/Select.svelte +1345 -0
  19. package/src/lib/filter.js +38 -0
  20. package/src/lib/get-items.js +25 -0
  21. package/src/lib/index.js +1 -0
  22. package/src/lib/tailwind.css +130 -0
  23. package/src/post-prepare.cjs +6 -0
  24. package/src/remove-styles.cjs +22 -0
  25. package/src/routes/+layout.js +1 -0
  26. package/src/routes/+layout.svelte +263 -0
  27. package/src/routes/+page.js +5 -0
  28. package/src/routes/+page.svelte +0 -0
  29. package/src/routes/examples/+page.svelte +84 -0
  30. package/src/routes/examples/advanced/create-item/+page.svelte +36 -0
  31. package/src/routes/examples/advanced/create-item-multiple/+page.svelte +38 -0
  32. package/src/routes/examples/advanced/floating-ui/+page.svelte +22 -0
  33. package/src/routes/examples/advanced/form-action/+page.server.js +10 -0
  34. package/src/routes/examples/advanced/form-action/+page.svelte +20 -0
  35. package/src/routes/examples/advanced/limit-multi-value/+page.svelte +31 -0
  36. package/src/routes/examples/advanced/long-item/+page.svelte +38 -0
  37. package/src/routes/examples/advanced/multi-item-checkboxes/+page.svelte +49 -0
  38. package/src/routes/examples/advanced/style-props/+page.svelte +14 -0
  39. package/src/routes/examples/advanced/virtual-list/+page.svelte +95 -0
  40. package/src/routes/examples/events/blur/+page.svelte +19 -0
  41. package/src/routes/examples/events/change/+page.svelte +16 -0
  42. package/src/routes/examples/events/clear/+page.svelte +18 -0
  43. package/src/routes/examples/events/error/+page.svelte +17 -0
  44. package/src/routes/examples/events/filter/+page.svelte +16 -0
  45. package/src/routes/examples/events/focus/+page.svelte +16 -0
  46. package/src/routes/examples/events/hoverItem/+page.svelte +16 -0
  47. package/src/routes/examples/events/input/+page.svelte +16 -0
  48. package/src/routes/examples/events/loaded/+page.svelte +23 -0
  49. package/src/routes/examples/props/class/+page.svelte +17 -0
  50. package/src/routes/examples/props/clearFilterTextOnBlur/+page.svelte +13 -0
  51. package/src/routes/examples/props/clearable/+page.svelte +13 -0
  52. package/src/routes/examples/props/closeListOnChange/+page.svelte +12 -0
  53. package/src/routes/examples/props/container-styles/+page.svelte +11 -0
  54. package/src/routes/examples/props/debounce-wait/+page.svelte +19 -0
  55. package/src/routes/examples/props/disabled/+page.svelte +15 -0
  56. package/src/routes/examples/props/filter-text/+page.svelte +14 -0
  57. package/src/routes/examples/props/floating-config/+page.svelte +42 -0
  58. package/src/routes/examples/props/focused/+page.svelte +16 -0
  59. package/src/routes/examples/props/group-header-selectable/+page.svelte +18 -0
  60. package/src/routes/examples/props/hide-empty-state/+page.svelte +8 -0
  61. package/src/routes/examples/props/id/+page.svelte +15 -0
  62. package/src/routes/examples/props/input-attributes/+page.svelte +11 -0
  63. package/src/routes/examples/props/item-id/+page.svelte +15 -0
  64. package/src/routes/examples/props/items/+page.svelte +15 -0
  65. package/src/routes/examples/props/just-value/+page.svelte +16 -0
  66. package/src/routes/examples/props/label/+page.svelte +15 -0
  67. package/src/routes/examples/props/list-auto-width/+page.svelte +21 -0
  68. package/src/routes/examples/props/list-offset/+page.svelte +21 -0
  69. package/src/routes/examples/props/list-open/+page.svelte +31 -0
  70. package/src/routes/examples/props/loadOptions/+page.svelte +16 -0
  71. package/src/routes/examples/props/loading/+page.svelte +15 -0
  72. package/src/routes/examples/props/multiFullItemClearable/+page.svelte +12 -0
  73. package/src/routes/examples/props/multiple/+page.svelte +12 -0
  74. package/src/routes/examples/props/name/+page.svelte +13 -0
  75. package/src/routes/examples/props/placeholder/+page.svelte +14 -0
  76. package/src/routes/examples/props/placeholder-always-show/+page.svelte +11 -0
  77. package/src/routes/examples/props/required/+page.svelte +14 -0
  78. package/src/routes/examples/props/searchable/+page.svelte +15 -0
  79. package/src/routes/examples/props/show-chevron/+page.svelte +15 -0
  80. package/src/routes/examples/props/value/+page.svelte +19 -0
  81. package/src/routes/examples/slots/chevron-icon/+page.svelte +16 -0
  82. package/src/routes/examples/slots/clear-icon/+page.svelte +21 -0
  83. package/src/routes/examples/slots/empty/+page.svelte +18 -0
  84. package/src/routes/examples/slots/input-hidden/+page.server.js +10 -0
  85. package/src/routes/examples/slots/input-hidden/+page.svelte +22 -0
  86. package/src/routes/examples/slots/item/+page.svelte +15 -0
  87. package/src/routes/examples/slots/list/+page.svelte +49 -0
  88. package/src/routes/examples/slots/list-append/+page.svelte +16 -0
  89. package/src/routes/examples/slots/list-prepend/+page.svelte +16 -0
  90. package/src/routes/examples/slots/loading-icon/+page.svelte +29 -0
  91. package/src/routes/examples/slots/multi-clear-icon/+page.svelte +16 -0
  92. package/src/routes/examples/slots/prepend/+page.svelte +22 -0
  93. package/src/routes/examples/slots/required/+page.svelte +31 -0
  94. package/src/routes/examples/slots/selection/+page.svelte +25 -0
  95. package/static/nav-icon.svg +2 -0
  96. package/static/svelte-select.png +0 -0
  97. package/svelte-select.png +0 -0
  98. package/svelte.config.js +10 -0
  99. package/tailwind.config.cjs +4 -0
  100. package/test/public/favicon.ico +0 -0
  101. package/test/public/index.html +15 -0
  102. package/test/src/ChevronSlotTest.svelte +19 -0
  103. package/test/src/ClearIconSlotTest.svelte +12 -0
  104. package/test/src/CustomItem.svelte +78 -0
  105. package/test/src/GroupHeaderNotSelectable.svelte +17 -0
  106. package/test/src/HoverItemIndexTest.svelte +21 -0
  107. package/test/src/InputHiddenSlotTest.svelte +12 -0
  108. package/test/src/ItemHeightTest.svelte +7 -0
  109. package/test/src/ItemSlotTest.svelte +11 -0
  110. package/test/src/ListSlotTest.svelte +14 -0
  111. package/test/src/LoadOptionsGroup.svelte +21 -0
  112. package/test/src/MultiItemColor.svelte +7 -0
  113. package/test/src/OuterListTest.svelte +16 -0
  114. package/test/src/PrependSlotTest.svelte +12 -0
  115. package/test/src/Select/ParentContainer.svelte +11 -0
  116. package/test/src/SelectionSlotMultipleTest.svelte +12 -0
  117. package/test/src/SelectionSlotTest.svelte +12 -0
  118. package/test/src/TestClearIcon.svelte +1 -0
  119. package/test/src/TestIcon.svelte +15 -0
  120. package/test/src/env.js +1 -0
  121. package/test/src/test-utils.js +124 -0
  122. package/test/src/tests.js +3745 -0
  123. package/vite.config.js +9 -0
@@ -0,0 +1,15 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>svelte-select tests</title>
8
+ </head>
9
+ <body>
10
+ <div id="testTemplate"></div>
11
+ <div id="extra"></div>
12
+ <script type="module" src="/tests.js"></script>
13
+ </body>
14
+ </html>
15
+
@@ -0,0 +1,19 @@
1
+ <script>
2
+ import Select from '../../src/lib/Select.svelte';
3
+
4
+ let items = $state(['one', 'two']);
5
+ let value = $state(undefined);
6
+ let listOpen = $state(true);
7
+ </script>
8
+
9
+ <Select bind:items bind:value bind:listOpen showChevron>
10
+ {#snippet chevronIcon({ listOpen })}
11
+ <div>
12
+ {#if listOpen}
13
+ ⬆️
14
+ {:else}
15
+ ⬇️
16
+ {/if}
17
+ </div>
18
+ {/snippet}
19
+ </Select>
@@ -0,0 +1,12 @@
1
+ <script>
2
+ import Select from '../../src/lib/Select.svelte';
3
+
4
+ let items = $state(['one', 'two']);
5
+ let value = $state('one');
6
+ </script>
7
+
8
+ <Select bind:items bind:value>
9
+ {#snippet clearIcon()}
10
+ <div>x</div>
11
+ {/snippet}
12
+ </Select>
@@ -0,0 +1,78 @@
1
+ <script>
2
+ let { active = false, first = false, hover = false, item = undefined } = $props();
3
+
4
+ let itemClasses = $derived.by(() => {
5
+ const classes = [];
6
+ if (active) {
7
+ classes.push('active');
8
+ }
9
+ if (first) {
10
+ classes.push('first');
11
+ }
12
+ if (hover) {
13
+ classes.push('hover');
14
+ }
15
+ return classes.join(' ');
16
+ });
17
+ </script>
18
+
19
+ <div class="customItem {itemClasses}">
20
+ <img src={item.image_url} alt={item.name} />
21
+ <div class="customItem_title">
22
+ <div class="customItem_name">{item.name}</div>
23
+ <div class="customItem_tagline">{item.tagline}</div>
24
+ </div>
25
+ </div>
26
+
27
+ <style>
28
+ .customItem {
29
+ display: flex;
30
+ align-items: center;
31
+ cursor: default;
32
+ height: 42px;
33
+ line-height: 42px;
34
+ padding: 0 16px;
35
+ text-overflow: ellipsis;
36
+ overflow: hidden;
37
+ white-space: nowrap;
38
+ }
39
+
40
+ .customItem:active {
41
+ background: #b9daff;
42
+ }
43
+
44
+ .customItem.active {
45
+ background: #007aff;
46
+ color: #fff;
47
+ }
48
+
49
+ .customItem.first {
50
+ border-radius: 4px 4px 0 0;
51
+ }
52
+
53
+ .customItem.hover:not(.active) {
54
+ background: #e7f2ff;
55
+ }
56
+
57
+ img {
58
+ width: 5px;
59
+ padding: 5px 0;
60
+ margin: 0 10px;
61
+ }
62
+
63
+ .customItem_title {
64
+ overflow: hidden;
65
+ text-overflow: ellipsis;
66
+ white-space: nowrap;
67
+ }
68
+
69
+ .customItem_name {
70
+ display: inline-block;
71
+ font-weight: 700;
72
+ margin-right: 10px;
73
+ }
74
+
75
+ .customItem_tagline {
76
+ display: inline-block;
77
+ }
78
+ </style>
@@ -0,0 +1,17 @@
1
+ <script>
2
+ import Select from '../../src/lib/Select.svelte';
3
+
4
+ let { filterText = $bindable('') } = $props();
5
+
6
+ let items = [
7
+ { value: 'chocolate', label: 'Chocolate', group: 'Sweet' },
8
+ { value: 'pizza', label: 'Pizza', group: 'Savory' },
9
+ { value: 'cake', label: 'Cake', group: 'Sweet', selectable: false },
10
+ { value: 'chips', label: 'Chips', group: 'Savory' },
11
+ { value: 'ice-cream', label: 'Ice Cream', group: 'Sweet' },
12
+ ];
13
+
14
+ let value = $state(undefined);
15
+ </script>
16
+
17
+ <Select {items} groupBy={(item) => item.group} bind:value bind:filterText />
@@ -0,0 +1,21 @@
1
+ <script>
2
+ import Select from '../../src/lib/Select.svelte';
3
+
4
+ let items = [];
5
+
6
+ for (let i = 0; i < 100; i++) {
7
+ items.push({ label: i, value: i, group: 'a' });
8
+ }
9
+
10
+ let value = $state(null);
11
+
12
+ let { hoverItemIndex = $bindable() } = $props();
13
+ </script>
14
+
15
+ <Select {items} bind:value groupBy={(i) => i.group} bind:hoverItemIndex />
16
+
17
+ {#if value}
18
+ <p>
19
+ Selected value: {value.label}
20
+ </p>
21
+ {/if}
@@ -0,0 +1,12 @@
1
+ <script>
2
+ import Select from '../../src/lib/Select.svelte';
3
+
4
+ let items = $state(['one', 'two']);
5
+ let value = $state('one');
6
+ </script>
7
+
8
+ <Select bind:items name="test" bind:value>
9
+ {#snippet inputHidden({ value })}
10
+ <input type="hidden" name="test" value={value.value} />
11
+ {/snippet}
12
+ </Select>
@@ -0,0 +1,7 @@
1
+ <script>
2
+ import Select from '../../src/lib/Select.svelte';
3
+
4
+ let items = $state(['one', 'two']);
5
+ </script>
6
+
7
+ <Select bind:items listOpen --item-height="50px" />
@@ -0,0 +1,11 @@
1
+ <script>
2
+ import Select from '../../src/lib/Select.svelte';
3
+
4
+ let items = $state(['one', 'two']);
5
+ </script>
6
+
7
+ <Select bind:items listOpen>
8
+ {#snippet item({ item })}
9
+ * {item.label} *
10
+ {/snippet}
11
+ </Select>
@@ -0,0 +1,14 @@
1
+ <script>
2
+ import Select from '../../src/lib/Select.svelte';
3
+
4
+ let items = $state(['one', 'two']);
5
+ let value = $state('one');
6
+ </script>
7
+
8
+ <Select bind:items bind:value listOpen>
9
+ {#snippet list({ filteredItems })}
10
+ {#each filteredItems as item}
11
+ {item.label}
12
+ {/each}
13
+ {/snippet}
14
+ </Select>
@@ -0,0 +1,21 @@
1
+ <script>
2
+ import Select from '../../src/lib/Select.svelte';
3
+
4
+ const items = [
5
+ { value: 'chocolate', label: 'Chocolate', group: 'Sweet' },
6
+ { value: 'pizza', label: 'Pizza', group: 'Savory' },
7
+ { value: 'cake', label: 'Cake', group: 'Sweet', selectable: false },
8
+ { value: 'chips', label: 'Chips', group: 'Savory' },
9
+ { value: 'ice-cream', label: 'Ice Cream', group: 'Sweet' },
10
+ ];
11
+
12
+ let { filterText = $bindable(), value = $bindable(undefined) } = $props();
13
+
14
+ async function loadOptions() {
15
+ return items.filter((i) => i.label.toLowerCase().includes(filterText.toLowerCase()));
16
+ }
17
+
18
+ const groupBy = (i) => i.group;
19
+ </script>
20
+
21
+ <Select {loadOptions} bind:filterText {groupBy} {value} />
@@ -0,0 +1,7 @@
1
+ <script>
2
+ import Select from '../../src/lib/Select.svelte';
3
+
4
+ let items = $state(['one', 'two']);
5
+ </script>
6
+
7
+ <Select multiple bind:items value="one" listOpen --multi-item-color="red" />
@@ -0,0 +1,16 @@
1
+ <script>
2
+ import Select from '../../src/lib/Select.svelte';
3
+
4
+ let items = $state(['one', 'two']);
5
+ let value = $state('one');
6
+ </script>
7
+
8
+ <Select bind:items bind:value listOpen>
9
+ {#snippet listPrepend()}
10
+ prepend
11
+ {/snippet}
12
+
13
+ {#snippet listAppend()}
14
+ append
15
+ {/snippet}
16
+ </Select>
@@ -0,0 +1,12 @@
1
+ <script>
2
+ import Select from '../../src/lib/Select.svelte';
3
+
4
+ let items = $state(['one', 'two']);
5
+ let value = $state(undefined);
6
+ </script>
7
+
8
+ <Select bind:items bind:value>
9
+ {#snippet prepend()}
10
+ <div class="before">Before it all</div>
11
+ {/snippet}
12
+ </Select>
@@ -0,0 +1,11 @@
1
+ <script>
2
+ import Select from '../../../src/lib/Select.svelte';
3
+
4
+ let { value = $bindable(), items } = $props();
5
+ </script>
6
+
7
+ <div class="container">
8
+ <Select bind:value {items}></Select>
9
+
10
+ <p class="result">{value.label}</p>
11
+ </div>
@@ -0,0 +1,12 @@
1
+ <script>
2
+ import Select from '../../src/lib/Select.svelte';
3
+
4
+ let items = $state(['one', 'two']);
5
+ let value = $state(['one', 'two']);
6
+ </script>
7
+
8
+ <Select bind:items bind:value multiple>
9
+ {#snippet selection({ selection, index })}
10
+ Index: {index} Slot: {selection.label}
11
+ {/snippet}
12
+ </Select>
@@ -0,0 +1,12 @@
1
+ <script>
2
+ import Select from '../../src/lib/Select.svelte';
3
+
4
+ let items = $state(['one', 'two']);
5
+ let value = $state('one');
6
+ </script>
7
+
8
+ <Select bind:items bind:value>
9
+ {#snippet selection({ selection })}
10
+ Slot: {selection.label}
11
+ {/snippet}
12
+ </Select>
@@ -0,0 +1 @@
1
+ <svg class="testClearIcon" width="100%" height="100%" viewBox="0 0 512 512"><path d="m437 75c-100-100-262-100-362 0-100 100-100 262 0 362 100 100 262 100 362 0 100-100 100-262 0-362z m-86 274c-1 1-1 1 0 0-5 4-10 6-15 7-6 0-11-2-15-6l-65-65-65 65c-4 4-9 6-15 6-5 0-10-2-14-6-8-8-8-21-1-29 0 0 0 0 1 0l65-65-65-65c-8-8-8-21 0-29 8-8 21-8 29 0l65 65 65-65c8-8 21-8 29 0 8 8 8 21 0 29l-65 65 65 65c8 8 8 21 1 28z" fill="currentColor"></path></svg>
@@ -0,0 +1,15 @@
1
+ <svg
2
+ id="testIcon"
3
+ width="16"
4
+ height="16"
5
+ viewBox="0 0 16 16"
6
+ xmlns="http://www.w3.org/2000/svg">
7
+ <path
8
+ d="M11.0526 0.294881L9.28328 2.06416L13.9577 6.71672L15.7051 4.94744C16.0983
9
+ 4.55427 16.0983 4.00819 15.7051 3.61502L12.4068 0.294881C11.9918 -0.0982935
10
+ 11.4457 -0.0982935 11.0526 0.294881ZM2.11877 11.2164L1.66007 13.4881L2.51195
11
+ 14.3618L4.78362 13.8812L2.11877 11.2164ZM0 15.5631L1.11399 10.2116L8.51877
12
+ 2.80683L13.1932 7.45939L5.76655 14.8642L0.371331 15.9563C0.349488 15.9782
13
+ 0.327645 15.9782 0.305802 15.9782C0.174744 15.9782 0 15.8471 0 15.5631Z"
14
+ fill="currentColor" />
15
+ </svg>
@@ -0,0 +1 @@
1
+ export const browser = true;
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Test utilities for Svelte 5 component testing
3
+ *
4
+ * This module provides backward-compatible helpers for testing Svelte 5 components
5
+ * using an API similar to Svelte 3/4.
6
+ */
7
+
8
+ import { mount, unmount, flushSync } from 'svelte';
9
+
10
+ /**
11
+ * Creates a test instance of a Svelte component with a backward-compatible API.
12
+ *
13
+ * In Svelte 5, components no longer have $set or direct property access.
14
+ * This wrapper provides those capabilities for testing purposes.
15
+ *
16
+ * @param {Component} Component - The Svelte component to instantiate
17
+ * @param {Object} options - Options for the component
18
+ * @param {HTMLElement} options.target - The DOM element to mount the component in
19
+ * @param {Object} options.props - Initial props for the component
20
+ * @returns {Object} A wrapper object with $set, $destroy, and property access
21
+ */
22
+ export function createTestComponent(Component, { target, props = {} } = {}) {
23
+ let currentProps = { ...props };
24
+ let instance = null;
25
+ let eventHandlers = {};
26
+
27
+ // Create a proxy to intercept property access
28
+ const handler = {
29
+ get(obj, prop) {
30
+ if (prop === '$set') {
31
+ return async function(newProps) {
32
+ // Merge new props
33
+ currentProps = { ...currentProps, ...newProps };
34
+
35
+ // Unmount and remount with new props
36
+ if (instance) {
37
+ unmount(instance);
38
+ }
39
+
40
+ // Re-apply event handlers as callback props
41
+ const propsWithEvents = { ...currentProps };
42
+ for (const [event, handler] of Object.entries(eventHandlers)) {
43
+ propsWithEvents[event] = handler;
44
+ }
45
+
46
+ instance = mount(Component, { target, props: propsWithEvents });
47
+ flushSync();
48
+
49
+ return new Promise(f => setTimeout(f, 0));
50
+ };
51
+ }
52
+
53
+ if (prop === '$destroy') {
54
+ return function() {
55
+ if (instance) {
56
+ unmount(instance);
57
+ instance = null;
58
+ }
59
+ };
60
+ }
61
+
62
+ if (prop === '$on') {
63
+ return function(eventName, handler) {
64
+ // Store event handlers as callback props
65
+ const callbackName = `on${eventName}`;
66
+ eventHandlers[callbackName] = handler;
67
+
68
+ // Re-mount with new event handler
69
+ currentProps[callbackName] = (detail) => handler({ detail });
70
+ if (instance) {
71
+ unmount(instance);
72
+ }
73
+ instance = mount(Component, { target, props: currentProps });
74
+ flushSync();
75
+ };
76
+ }
77
+
78
+ // Return current prop value
79
+ if (prop in currentProps) {
80
+ return currentProps[prop];
81
+ }
82
+
83
+ return undefined;
84
+ },
85
+
86
+ set(obj, prop, value) {
87
+ // Update prop and re-mount
88
+ currentProps[prop] = value;
89
+
90
+ if (instance) {
91
+ unmount(instance);
92
+ }
93
+
94
+ const propsWithEvents = { ...currentProps };
95
+ for (const [event, handler] of Object.entries(eventHandlers)) {
96
+ propsWithEvents[event] = handler;
97
+ }
98
+
99
+ instance = mount(Component, { target, props: propsWithEvents });
100
+ flushSync();
101
+
102
+ return true;
103
+ }
104
+ };
105
+
106
+ // Initial mount
107
+ const propsWithEvents = { ...currentProps };
108
+ instance = mount(Component, { target, props: propsWithEvents });
109
+ flushSync();
110
+
111
+ return new Proxy({}, handler);
112
+ }
113
+
114
+ /**
115
+ * Helper to wait for next tick
116
+ */
117
+ export function tick() {
118
+ return new Promise(f => setTimeout(f, 0));
119
+ }
120
+
121
+ /**
122
+ * Helper to flush pending updates synchronously
123
+ */
124
+ export { flushSync };