slim-select 3.6.1 → 4.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.
package/README.md CHANGED
@@ -1,51 +1,59 @@
1
1
  # Slim Select
2
2
 
3
- ## [slimselectjs.com](https://slimselectjs.com)
3
+ **The select dropdown, reimagined.**
4
+
5
+ A lightweight, dependency-free replacement for the native `<select>` — single and multi-select, search, optgroups,
6
+ remote data, modal mode on mobile, and full theming through CSS variables. Drop it into any stack, or use the official
7
+ Vue and React wrappers.
4
8
 
5
- Advanced select dropdown
9
+ ## [slimselectjs.com](https://slimselectjs.com)
6
10
 
7
11
  [![NPM Downloads](https://img.shields.io/npm/dt/slim-select.svg)](https://www.npmjs.com/package/slim-select)
8
- ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/brianvoe/slim-select/vitest.yml?logo=vitest&label=unit%20tests) ![Tests](https://img.shields.io/badge/tests-314%20passing-brightgreen) [![slim-select](https://snyk.io/advisor/npm-package/slim-select/badge.svg)](https://snyk.io/advisor/npm-package/slim-select)
12
+ ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/brianvoe/slim-select/vitest.yml?logo=vitest&label=unit%20tests)
13
+ ![Tests](https://img.shields.io/badge/tests-523%20passing-brightgreen)
14
+ [![slim-select](https://snyk.io/advisor/npm-package/slim-select/badge.svg)](https://snyk.io/advisor/npm-package/slim-select)
9
15
 
10
- ## Support
16
+ ![](https://raw.githubusercontent.com/brianvoe/slim-select/master/docs/slimselect.gif)
11
17
 
12
- [![](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86)](https://github.com/sponsors/brianvoe)
18
+ ## Documentation
13
19
 
14
- <a href="https://www.buymeacoffee.com/brianvoe" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy Me A Coffee" style="height: auto !important;width: auto !important;" ></a>
20
+ Full docs, live demos, and copy-paste examples: **[slimselectjs.com](https://slimselectjs.com)**
15
21
 
16
- ## [Documentation and Examples](https://slimselectjs.com)
22
+ - [Get started](https://slimselectjs.com/get-started)
23
+ - [Settings](https://slimselectjs.com/settings)
24
+ - [Methods](https://slimselectjs.com/methods)
25
+ - [Events](https://slimselectjs.com/events)
26
+ - [Styling](https://slimselectjs.com/style)
17
27
 
18
- See [website](https://slimselectjs.com) for the full list of [settings](https://slimselectjs.com/settings), [methods](https://slimselectjs.com/methods) and [event callbacks](https://slimselectjs.com/events)
28
+ ## Features
19
29
 
20
- ![](https://raw.githubusercontent.com/brianvoe/slim-select/master/docs/slimselect.gif)
30
+ **Select, upgraded**
21
31
 
22
- ## Features
32
+ - Single and multi-select with tags, placeholders, and deselect
33
+ - Search with highlighting, remote/API search, and user-addable options
34
+ - Optgroups with select-all and closable accordion groups
35
+ - HTML options, tooltips, min/max selection limits, and disabled options
36
+ - Modal mode (`off` | `on` | `mobile`) — centered panel with backdrop; mobile-friendly by default
37
+
38
+ **Looks like your product**
39
+
40
+ - Theme entirely with `--ss-*` CSS custom properties — no fighting class specificity
41
+ - Import plain CSS or SCSS; inherit styles and classes from the native `<select>`
42
+
43
+ **Built to ship**
44
+
45
+ - Zero runtime dependencies
46
+ - ~66KB JS (~16KB gzip) · ~12KB CSS (~2KB gzip)
47
+ - TypeScript types included
48
+ - WCAG 2.1 Level AA accessibility (ARIA, keyboard, screen reader support)
49
+ - `prefers-reduced-motion` respected
50
+ - 477 unit tests + 46 Playwright E2E tests
23
51
 
24
- - No Dependencies
25
- - JS: 48kb - 10kb gzip
26
- - CSS: 11kb - 2kb gzip
27
- - Single Select
28
- - Multi Select
29
- - User Addable Options
30
- - Html Options
31
- - Settable Data
32
- - Callback Events
33
- - Label Support
34
- - Placeholders
35
- - Search
36
- - Disable Options
37
- - Light CSS
38
- - Light Color Scheme
39
- - Style and Class Inheritance
40
- - Clean Animations
41
- - Performant
42
- - Typescript
43
- - ARIA Accessibility (WCAG 2.1 Level AA compliant)
44
-
45
- ## Frameworks
46
-
47
- - [Vue](#vue)
48
- - [React](#react)
52
+ **Framework ready**
53
+
54
+ - Vanilla JS
55
+ - [Vue 3](#vue) component with `v-model`
56
+ - [React](#react) component with hooks and ref access
49
57
 
50
58
  ## Installation
51
59
 
@@ -53,19 +61,18 @@ See [website](https://slimselectjs.com) for the full list of [settings](https://
53
61
  npm install slim-select
54
62
  ```
55
63
 
56
- ### or
64
+ ### CDN
57
65
 
58
66
  ```html
59
67
  <script src="https://unpkg.com/slim-select@latest/dist/slimselect.js"></script>
60
68
  <link rel="stylesheet" href="https://unpkg.com/slim-select@latest/dist/slimselect.css" />
61
69
  ```
62
70
 
63
- ## Simple Usage
71
+ ## Quick start
64
72
 
65
73
  ```javascript
66
74
  import SlimSelect from 'slim-select'
67
- import 'slim-select/styles' // optional css import method
68
- import 'slim-select/scss' // optional scss import method
75
+ import 'slim-select/styles' // or: import 'slim-select/scss'
69
76
 
70
77
  new SlimSelect({
71
78
  select: '#selectElement'
@@ -78,133 +85,151 @@ new SlimSelect({
78
85
  </select>
79
86
  ```
80
87
 
81
- ## Data
88
+ ## Styling
82
89
 
83
- Data is an array of objects that represent both option and optgroups.
90
+ Slim Select is styled with CSS variables. Set them on a wrapper around your select (or on `:root` for a global theme):
84
91
 
85
- See below for list of data types
92
+ ```css
93
+ .my-form {
94
+ --ss-primary-color: #2563eb;
95
+ --ss-bg-color: #ffffff;
96
+ --ss-font-color: #1e293b;
97
+ --ss-border-color: #e2e8f0;
98
+ --ss-border-radius: 8px;
99
+ --ss-main-height: 44px;
100
+ }
101
+ ```
102
+
103
+ See the full token list and live themes on the [Style docs](https://slimselectjs.com/style).
104
+
105
+ ## Data
106
+
107
+ Pass an array of options and optgroups instead of (or in addition to) native `<option>` elements:
86
108
 
87
109
  ```javascript
88
110
  new SlimSelect({
89
111
  select: '#selectElement',
90
112
 
91
- // Array of Option objects
92
- data: [{ text: 'Value 1', value: 'value1' }],
93
-
94
- // or
95
-
96
- // Array of Optgroups and/or Options
97
- data: [{ label: 'Optgroup Label', options: { text: 'Value 1', value: 'value1' } }]
113
+ data: [
114
+ { text: 'Value 1', value: 'value1' },
115
+ {
116
+ label: 'Group label',
117
+ options: [
118
+ { text: 'Value 2', value: 'value2' },
119
+ { text: 'Value 3', value: 'value3' }
120
+ ]
121
+ }
122
+ ]
98
123
  })
99
124
  ```
100
125
 
101
- ## Data Types
126
+ ### Data types
102
127
 
103
128
  ```javascript
104
129
  // <optgroup>
105
- var optgroup = {
130
+ const optgroup = {
106
131
  label: 'label', // Required
107
- selectAll: false, // Optional - default false
108
- closable: 'off', // Optional - default 'off' - 'off', 'open', 'close'
109
- options: [] // Required - value is an array of options
132
+ selectAll: false, // Optional — default false
133
+ closable: 'off', // Optional — 'off' | 'open' | 'close'
134
+ options: [] // Required — array of options
110
135
  }
111
136
 
112
137
  // <option>
113
- var option = {
138
+ const option = {
114
139
  text: 'text', // Required
115
- value: 'value', // Optional - value will be set by text if not set
116
- html: '<b>Html</b>', // Optional - if set, used for display purposes
117
- selected: false, // Optional - default is false
118
- display: true, // Optional - default is true
119
- disabled: false, // Optional - default is false
120
- mandatory: false, // Optional - default is false
121
- placeholder: false, // Optional - default is false
122
- class: '', // Optional - default is not set
123
- style: '', // Optional - default is not set
124
- data: {} // Optional - If you have data attributes
140
+ value: 'value', // Optional — defaults to text
141
+ html: '<b>Html</b>', // Optional — used for display when set
142
+ selected: false,
143
+ display: true,
144
+ disabled: false,
145
+ mandatory: false,
146
+ placeholder: false,
147
+ class: '',
148
+ style: '',
149
+ data: {} // Plain object for data-* attributes
125
150
  }
126
151
  ```
127
152
 
128
153
  ## Settings
129
154
 
130
- Settings are optional fields that customize how SlimSelect operates. All values shown are defaults.
155
+ All fields are optional. Values shown are defaults.
131
156
 
132
- [Full Settings Documentation](https://slimselectjs.com/settings)
157
+ [Full settings documentation](https://slimselectjs.com/settings)
133
158
 
134
159
  ```javascript
135
160
  new SlimSelect({
136
161
  select: '#selectElement',
137
162
 
138
163
  settings: {
139
- disabled: false, // Disable the select
140
- alwaysOpen: false, // Keep dropdown always open
141
- showSearch: true, // Show search input
142
- focusSearch: true, // Auto focus search on open
143
- keepSearch: false, // Keep search input value when dropdown closes
144
- ariaLabel: 'Combobox', // ARIA label for accessibility
145
- searchPlaceholder: 'Search', // Search input placeholder
146
- searchText: 'No Results', // Text when no results found
147
- searchingText: 'Searching...', // Text while searching
148
- searchHighlight: false, // Highlight search terms
149
- closeOnSelect: true, // Close dropdown after selection
150
- contentLocation: document.body, // Where to append dropdown
151
- contentPosition: 'absolute', // CSS position: absolute, relative, fixed
152
- contentWidth: '', // Content width: "500px" exact, ">500px" min-width, "<500px" max-width
153
- openPosition: 'auto', // Open direction: auto, up, down
154
- placeholderText: 'Select Value', // Placeholder text
155
- allowDeselect: false, // Allow deselecting in single select
156
- hideSelected: false, // Hide selected options in dropdown
157
- keepOrder: false, // Keep user click order (not DOM order) for getSelected
158
- showOptionTooltips: false, // Show tooltips on options
159
- minSelected: 0, // Minimum selections (multi-select)
160
- maxSelected: 1000, // Maximum selections (multi-select)
161
- timeoutDelay: 200, // Delay for callbacks (ms)
162
- maxValuesShown: 20, // Max values shown before message
163
- maxValuesMessage: '{number} selected', // Message when max values exceeded
164
- addableText: 'Press "Enter" to add {value}' // Text for addable option
164
+ disabled: false,
165
+ alwaysOpen: false,
166
+ showSearch: true,
167
+ focusSearch: true,
168
+ keepSearch: false,
169
+ ariaLabel: 'Combobox',
170
+ searchPlaceholder: 'Search...',
171
+ searchText: 'No Results',
172
+ searchingText: 'Searching...',
173
+ resultsText: '{count} results available',
174
+ deselectText: 'Clear',
175
+ removeText: 'Remove',
176
+ searchHighlight: false,
177
+ closeOnSelect: true,
178
+ contentLocation: document.body,
179
+ contentPosition: 'absolute', // 'absolute' | 'relative' | 'fixed'
180
+ contentWidth: '', // e.g. '500px', '>500px', '<500px'
181
+ openPosition: 'auto', // 'auto' | 'up' | 'down'
182
+ placeholderText: 'Select Value',
183
+ allowDeselect: false,
184
+ hideSelected: false,
185
+ multiString: false,
186
+ keepOrder: false,
187
+ showOptionTooltips: false,
188
+ minSelected: 0,
189
+ maxSelected: 1000,
190
+ timeoutDelay: 200,
191
+ maxValuesShown: 20,
192
+ maxValuesMessage: '{number} selected',
193
+ addableText: 'Press "Enter" to add {value}',
194
+ modal: 'mobile', // 'off' | 'on' | 'mobile'
195
+ modalTitle: '' // Header above the option list in modal view
165
196
  }
166
197
  })
167
198
  ```
168
199
 
169
200
  ## Events
170
201
 
171
- Events are function callbacks for when certain actions happen
172
-
173
- [Full Events Documentation](https://slimselectjs.com/events)
202
+ [Full events documentation](https://slimselectjs.com/events)
174
203
 
175
204
  ```javascript
176
205
  new SlimSelect({
177
206
  select: '#selectElement',
178
207
 
179
208
  events: {
180
- // Custom search function - return Promise or data array
181
- search: (searchValue: string, currentData: (Option | Optgroup)[]) => Promise<(Partial<Option> | Partial<Optgroup>)[]> | (Partial<Option> | Partial<Optgroup>)[],
182
-
183
- // Filter function for search - return true to show option
209
+ // Remote/API search — return a Promise or data array
210
+ // searchValue: current input text
211
+ // selected: currently selected Option[]
212
+ // catalog: baseline list restored when search clears
213
+ search: (
214
+ searchValue: string,
215
+ selected: Option[],
216
+ catalog?: (Option | Optgroup)[]
217
+ ) =>
218
+ Promise<(Partial<Option> | Partial<Optgroup>)[]> |
219
+ (Partial<Option> | Partial<Optgroup>)[],
220
+
221
+ // Local filter when events.search is not set
184
222
  searchFilter: (option: Option, search: string) => boolean,
185
223
 
186
- // Allow user to add options - return new option or error
224
+ // User-added options — return the new option or an Error
187
225
  addable: (value: string) => Promise<Partial<Option> | string> | Partial<Option> | string | Error,
188
226
 
189
- // Before selection changes - return false to prevent change
190
227
  beforeChange: (newVal: Option[], oldVal: Option[]) => boolean | void,
191
-
192
- // After selection changes
193
228
  afterChange: (newVal: Option[]) => void,
194
-
195
- // Before dropdown opens
196
229
  beforeOpen: () => void,
197
-
198
- // After dropdown opens
199
230
  afterOpen: () => void,
200
-
201
- // Before dropdown closes
202
231
  beforeClose: () => void,
203
-
204
- // After dropdown closes
205
232
  afterClose: () => void,
206
-
207
- // Error handler
208
233
  error: (err: Error) => void
209
234
  }
210
235
  })
@@ -212,40 +237,34 @@ new SlimSelect({
212
237
 
213
238
  ## Methods
214
239
 
215
- SlimSelect provides methods to programmatically control the select
216
-
217
- [Full Methods Documentation](https://slimselectjs.com/methods)
240
+ [Full methods documentation](https://slimselectjs.com/methods)
218
241
 
219
242
  ```javascript
220
243
  const slim = new SlimSelect({ select: '#selectElement' })
221
244
 
222
- slim.enable() // Enable the select
223
- slim.disable() // Disable the select
224
- slim.getData() // Get current data array
225
- slim.setData(data) // Set new data array
226
- slim.getSelected() // Get selected values as string[]
227
- slim.setSelected(['value1', 'value2']) // Set selected by values
228
- slim.addOption(option) // Add a single option
229
- slim.open() // Open the dropdown
230
- slim.close() // Close the dropdown
231
- slim.search('searchValue') // Programmatically search
232
- slim.destroy() // Destroy the instance
245
+ slim.enable()
246
+ slim.disable()
247
+ slim.getData()
248
+ slim.setData(data)
249
+ slim.getSelected() // string[]
250
+ slim.setSelected(['value1', 'value2'])
251
+ slim.addOption(option)
252
+ slim.open()
253
+ slim.close()
254
+ slim.search('searchValue')
255
+ slim.destroy()
233
256
  ```
234
257
 
235
258
  ## Vue
236
259
 
237
- SlimSelect has official Vue 3 component support with full reactivity.
238
-
239
- For more Vue examples and advanced usage, see the [Vue documentation](https://slimselectjs.com/vue).
260
+ Official Vue 3 component with `v-model` and full TypeScript support.
240
261
 
241
- ### Installation
262
+ [Vue documentation](https://slimselectjs.com/vue)
242
263
 
243
264
  ```bash
244
265
  npm install slim-select
245
266
  ```
246
267
 
247
- ### Usage
248
-
249
268
  ```vue
250
269
  <script lang="ts">
251
270
  import SlimSelect from 'slim-select/vue'
@@ -271,20 +290,18 @@ export default {
271
290
  </template>
272
291
  ```
273
292
 
274
- ## React
293
+ > **Note:** Pass options via the `:data` prop. Native `<option>` slot children are not supported in the Vue wrapper.
275
294
 
276
- SlimSelect has official React component support with hooks.
295
+ ## React
277
296
 
278
- For more React examples and advanced usage, see the [documentation](https://slimselectjs.com).
297
+ Official React component with hooks and ref access to the underlying instance.
279
298
 
280
- ### Installation
299
+ [React documentation](https://slimselectjs.com/react)
281
300
 
282
301
  ```bash
283
302
  npm install slim-select
284
303
  ```
285
304
 
286
- ### Usage
287
-
288
305
  ```tsx
289
306
  import { useState } from 'react'
290
307
  import SlimSelect from 'slim-select/react'
@@ -302,7 +319,9 @@ function MyComponent() {
302
319
  }
303
320
  ```
304
321
 
305
- ### Advanced Usage with Ref
322
+ > **Note:** Pass options via the `data` prop. Native `<option>` children are not supported in the React wrapper.
323
+
324
+ ### Ref access
306
325
 
307
326
  ```tsx
308
327
  import { useRef } from 'react'
@@ -312,16 +331,17 @@ import 'slim-select/styles'
312
331
  function MyComponent() {
313
332
  const slimRef = useRef<SlimSelectRef>(null)
314
333
 
315
- const handleClick = () => {
316
- // Access SlimSelect methods via ref
317
- slimRef.current?.slimSelect?.open()
318
- }
319
-
320
334
  return (
321
335
  <>
322
336
  <SlimSelect ref={slimRef} data={options} />
323
- <button onClick={handleClick}>Open Dropdown</button>
337
+ <button onClick={() => slimRef.current?.slimSelect?.open()}>Open</button>
324
338
  </>
325
339
  )
326
340
  }
327
341
  ```
342
+
343
+ ## Support
344
+
345
+ [![](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86)](https://github.com/sponsors/brianvoe)
346
+
347
+ <a href="https://www.buymeacoffee.com/brianvoe" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy Me A Coffee" style="height: auto !important;width: auto !important;" ></a>
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Animation timing helpers.
3
+ *
4
+ * CSS still runs the motion — these keep lifecycle and DOM cleanup aligned with
5
+ * --ss-animation-timing so we are not guessing with hardcoded setTimeout values.
6
+ */
7
+ /** Extra ms added to CSS duration for transitionend/animationend safety-net timeouts. */
8
+ export declare const ANIMATION_TIMEOUT_BUFFER_MS = 50;
9
+ /** .ss-content open/close transitions — wait for both before lifecycle continues. */
10
+ export declare const CONTENT_PANEL_TRANSITION_PROPERTIES: readonly ["transform", "opacity"];
11
+ /** Parse a CSS time value (e.g. "0.2s", "200ms") into milliseconds. */
12
+ export declare function parseAnimationDuration(cssValue: string, fallback?: number): number;
13
+ /** Read --ss-animation-timing from an element's computed styles. */
14
+ export declare function getAnimationDuration(element: HTMLElement, fallback?: number): number;
15
+ /**
16
+ * Safety-net timeout for animation waits.
17
+ * Derived from --ss-animation-timing (+ buffer). An explicit settings.timeoutDelay
18
+ * is treated as a minimum — never shorter than the CSS-driven duration.
19
+ */
20
+ export declare function getAnimationTimeout(element: HTMLElement, userTimeout?: number, fallback?: number): number;
21
+ /** Respect user OS accessibility preference and jsdom (no matchMedia). */
22
+ export declare function prefersReducedMotion(): boolean;
23
+ /**
24
+ * Wait for CSS transition(s) to finish, with a timeout fallback.
25
+ *
26
+ * Pass a single property name to resolve on that transitionend, or an array to
27
+ * wait until every listed property has fired (e.g. transform + opacity on .ss-content).
28
+ * Optional AbortSignal cleans up listeners when lifecycle cancels a rapid toggle.
29
+ */
30
+ export declare function waitForTransitionEnd(element: HTMLElement, propertyNames?: string | readonly string[], timeoutMs?: number, signal?: AbortSignal): Promise<void>;
31
+ /**
32
+ * Wait for a CSS @keyframes animation to finish, with a timeout fallback.
33
+ *
34
+ * Used when removing multi-select value chips (.ss-value-out). jsdom does not
35
+ * fire animationend, so the timeout wins in unit tests.
36
+ */
37
+ export declare function waitForAnimationEnd(element: HTMLElement, animationName?: string, timeoutMs?: number, signal?: AbortSignal): Promise<void>;
package/dist/classes.d.ts CHANGED
@@ -36,6 +36,11 @@ export default class CssClasses {
36
36
  option: string;
37
37
  optionDelete: string;
38
38
  highlighted: string;
39
+ modalOverlay: string;
40
+ modalDialog: string;
41
+ modalTitle: string;
42
+ modalClose: string;
43
+ modalContent: string;
39
44
  mainOpen: string;
40
45
  close: string;
41
46
  selected: string;
@@ -0,0 +1,26 @@
1
+ import { hasClassInTree } from './helpers';
2
+ export interface GlobalEventHandlers {
3
+ onDocumentClick: (e: Event) => void;
4
+ onWindowResize: () => void;
5
+ onWindowScroll: () => void;
6
+ onVisibilityChange: () => void;
7
+ }
8
+ export default class GlobalEvents {
9
+ private handlers;
10
+ private attached;
11
+ constructor(handlers: GlobalEventHandlers);
12
+ /** Attach long-lived listeners for the SlimSelect instance lifetime. */
13
+ attach(options: {
14
+ listenScroll: boolean;
15
+ }): void;
16
+ /** Attached after open animation completes — closes on outside click. */
17
+ attachDocumentClick(): void;
18
+ detachDocumentClick(): void;
19
+ detach(options: {
20
+ listenScroll: boolean;
21
+ }): void;
22
+ private resizeHandler;
23
+ private scrollHandler;
24
+ private visibilityHandler;
25
+ }
26
+ export { hasClassInTree };
package/dist/helpers.d.ts CHANGED
@@ -1,5 +1,24 @@
1
+ import { Optgroup, Option } from './store';
2
+ import { ModalSetting } from './settings';
3
+ type DataItem = Partial<Option> | Partial<Optgroup>;
4
+ /** Copy option data attributes into a plain object (not a live DOMStringMap). */
5
+ export declare function copyOptionData(data?: {
6
+ [key: string]: string;
7
+ } | DOMStringMap | null): {
8
+ [key: string]: string;
9
+ };
10
+ /** Label text only — excludes nested form controls (e.g. wrapped select options). */
11
+ export declare function getLabelElementText(label: HTMLLabelElement): string;
12
+ /** Associated label text for a select, or its aria-label when no label is linked. */
13
+ export declare function getAssociatedLabelText(select: HTMLSelectElement): string;
1
14
  export declare function generateID(): string;
2
15
  export declare function hasClassInTree(element: HTMLElement, className: string): HTMLElement | null;
3
16
  export declare function debounce<T extends (...args: any[]) => void>(func: T, wait?: number, immediate?: boolean): () => void;
4
17
  export declare function isEqual(a: any, b: any): boolean;
18
+ /** Compare selected id sets regardless of order (multi-select). */
19
+ export declare function selectedIdsEqual(a: string[], b: string[]): boolean;
20
+ /** Compare option/optgroup arrays without JSON.stringify. */
21
+ export declare function dataStructureEqual(a: DataItem[], b: DataItem[]): boolean;
5
22
  export declare function kebabCase(str: string): string;
23
+ export declare function shouldUseModalView(modal: ModalSetting, viewportWidth?: number): boolean;
24
+ export {};
package/dist/index.d.ts CHANGED
@@ -1,9 +1,12 @@
1
1
  import { default as CssClasses } from './classes';
2
+ import { default as Lifecycle } from './lifecycle';
2
3
  import { default as Render } from './render';
3
4
  import { default as Select } from './select';
4
- import { default as Settings } from './settings';
5
+ import { default as Settings, MODAL_MOBILE_BREAKPOINT } from './settings';
5
6
  import { default as Store, Option, Optgroup } from './store';
6
- export { Settings, Option, Optgroup };
7
+ import { default as SyncCoordinator } from './sync';
8
+ export { Settings, Option, Optgroup, MODAL_MOBILE_BREAKPOINT };
9
+ export type { ModalSetting } from './settings';
7
10
  export type { Main, Content, Search } from './render';
8
11
  export interface Config {
9
12
  select: string | Element;
@@ -13,7 +16,7 @@ export interface Config {
13
16
  events?: Events;
14
17
  }
15
18
  export interface Events {
16
- search?: (searchValue: string, currentData: (Option | Optgroup)[]) => Promise<(Partial<Option> | Partial<Optgroup>)[]> | (Partial<Option> | Partial<Optgroup>)[];
19
+ search?: (searchValue: string, selected: Option[], catalog?: (Option | Optgroup)[]) => Promise<(Partial<Option> | Partial<Optgroup>)[]> | (Partial<Option> | Partial<Optgroup>)[];
17
20
  searchFilter?: (option: Option, search: string) => boolean;
18
21
  addable?: (value: string) => Promise<Partial<Option> | string> | Partial<Option> | string | false | null | undefined | Error;
19
22
  beforeChange?: (newVal: Option[], oldVal: Option[]) => boolean | void;
@@ -31,8 +34,11 @@ export default class SlimSelect {
31
34
  select: Select;
32
35
  store: Store;
33
36
  render: Render;
34
- private openTimeout;
35
- private closeTimeout;
37
+ sync: SyncCoordinator;
38
+ lifecycle: Lifecycle;
39
+ private globalEvents;
40
+ /** Invalidates in-flight API search responses when the query changes or clears. */
41
+ private searchGeneration;
36
42
  events: Events;
37
43
  constructor(config: Config);
38
44
  enable(): void;
@@ -45,9 +51,9 @@ export default class SlimSelect {
45
51
  open(): void;
46
52
  close(eventType?: string | null): void;
47
53
  search(value: string): void;
54
+ private clearSearch;
55
+ private runLocalSearch;
56
+ private runApiSearch;
48
57
  destroy(): void;
49
- private windowResize;
50
- private windowScroll;
51
58
  private documentClick;
52
- private windowVisibilityChange;
53
59
  }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Open/close state machine for SlimSelect.
3
+ *
4
+ * Replaces scattered openTimeout/closeTimeout in index.ts with a single flow:
5
+ * closed → opening → open → closing → closed
6
+ *
7
+ * Maps to settings consumers expect:
8
+ * - isOpen: true during opening + open
9
+ * - isFullOpen: true only in open state (after animation / afterOpen)
10
+ *
11
+ * Lifecycle waits for render.waitForAnimation() before firing afterOpen/afterClose.
12
+ * When no animation waiter is provided, falls back to settings.timeoutDelay.
13
+ */
14
+ export type LifecycleState = 'closed' | 'opening' | 'open' | 'closing';
15
+ export interface LifecycleHandlers {
16
+ beforeOpen?: () => void;
17
+ afterOpen?: () => void;
18
+ beforeClose?: () => void;
19
+ afterClose?: () => void;
20
+ /** Fires when fully open — used to attach document click-outside listener. */
21
+ onOpenReady?: () => void;
22
+ /** Fires when fully closed — used to detach document click-outside listener. */
23
+ onCloseReady?: () => void;
24
+ }
25
+ export interface LifecycleOptions {
26
+ /** Fallback when waitForAnimation is not provided (no CSS element yet). */
27
+ timeoutDelay: number;
28
+ waitForAnimation?: (phase: 'open' | 'close', signal?: AbortSignal) => Promise<void>;
29
+ }
30
+ export default class Lifecycle {
31
+ private handlers;
32
+ private options;
33
+ state: LifecycleState;
34
+ private pendingTimer;
35
+ /** Resolvers for in-flight waitForPhase promises — cancelled on rapid toggle. */
36
+ private waitResolvers;
37
+ /** Incremented on cancelPending(); stale async completions check this before firing handlers. */
38
+ private generation;
39
+ private animationAbort;
40
+ constructor(handlers: LifecycleHandlers, options: LifecycleOptions);
41
+ get isOpen(): boolean;
42
+ get isFullOpen(): boolean;
43
+ requestOpen(): Promise<void>;
44
+ requestClose(): Promise<void>;
45
+ /** Cancel timers and resolve waiting phases — called when open/close race each other. */
46
+ cancelPending(): void;
47
+ destroy(): void;
48
+ /**
49
+ * Wait for the CSS animation to finish (or timeoutDelay when no waiter exists).
50
+ * cancelPending() resolves the wait early for rapid open/close toggles.
51
+ */
52
+ private waitForPhase;
53
+ }