lithesome 0.23.7 → 0.24.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.
@@ -14,8 +14,8 @@
14
14
  disabled = $bindable(false),
15
15
  portalTarget = 'body',
16
16
  floatingConfig = {},
17
- value = $bindable(),
18
- multiple = false,
17
+ value = $bindable(null!),
18
+ unselectable = false,
19
19
  onValueChanged
20
20
  }: SelectProps = $props();
21
21
 
@@ -40,7 +40,7 @@
40
40
  ),
41
41
  portalTarget: stateValue(() => portalTarget),
42
42
  floatingConfig: stateValue(() => floatingConfig),
43
- multiple: stateValue(() => multiple)
43
+ unselectable: stateValue(() => unselectable)
44
44
  });
45
45
  </script>
46
46
 
@@ -1,4 +1,4 @@
1
1
  import type { SelectProps } from '../../types/index.js';
2
- declare const Select: import("svelte").Component<SelectProps<any>, {}, "disabled" | "visible" | "value">;
2
+ declare const Select: import("svelte").Component<SelectProps, {}, "disabled" | "visible" | "value">;
3
3
  type Select = ReturnType<typeof Select>;
4
4
  export default Select;
@@ -9,9 +9,11 @@
9
9
  let {
10
10
  id = parseId(uid),
11
11
  ref = $bindable(),
12
- placeholder = $bindable(''),
12
+ placeholder = $bindable('Select an option...'),
13
+ children,
14
+ custom,
13
15
  ...props
14
- }: SelectValueProps & Record<string, any> = $props();
16
+ }: SelectValueProps<typeof ctx.props> & Record<string, any> = $props();
15
17
 
16
18
  let ctx = useSelectValue({
17
19
  id: stateValue(() => id),
@@ -20,6 +22,6 @@
20
22
  });
21
23
  </script>
22
24
 
23
- <Element bind:ref {ctx} {...props} as="span">
24
- {ctx.label}
25
+ <Element bind:ref {custom} {ctx} {...props} as="span">
26
+ {ctx.state.placeholderVisible ? placeholder : ctx.state.selectedLabels.join(', ')}
25
27
  </Element>
@@ -1,5 +1,8 @@
1
1
  import type { SelectValueProps } from '../../types/index.js';
2
- type $$ComponentProps = SelectValueProps & Record<string, any>;
3
- declare const SelectValue: import("svelte").Component<$$ComponentProps, {}, "ref" | "placeholder">;
2
+ declare const SelectValue: import("svelte").Component<SelectValueProps<{
3
+ id: string;
4
+ "data-select-value": string;
5
+ 'data-placeholder': true | undefined;
6
+ }> & Record<string, any>, {}, "ref" | "placeholder">;
4
7
  type SelectValue = ReturnType<typeof SelectValue>;
5
8
  export default SelectValue;
@@ -7,10 +7,12 @@ declare class SelectRoot extends Floating {
7
7
  $$: RootProps;
8
8
  hoveredIndex: number;
9
9
  options: HTMLElement[];
10
- selectedOptions: HTMLElement[];
11
10
  sharedIds: SvelteMap<"content" | "trigger", string>;
12
11
  mounted: boolean;
12
+ searchable: boolean;
13
+ serachTerm: string;
13
14
  HoveredOption: HTMLElement | undefined;
15
+ SelectedOptions: HTMLElement[];
14
16
  constructor(props: RootProps);
15
17
  /**
16
18
  * Toggle the visible state of the content
@@ -25,10 +27,6 @@ declare class SelectRoot extends Floating {
25
27
  * and reset variables to reduce duplicate entries.
26
28
  */
27
29
  close: () => void;
28
- /**
29
- * Tells the components that checks have been completed, render as normal.
30
- */
31
- doneMounting: () => void;
32
30
  /**
33
31
  * Get all non-disabled options from the content.
34
32
  */
@@ -38,10 +36,6 @@ declare class SelectRoot extends Floating {
38
36
  * @param value The value to search for.
39
37
  */
40
38
  getElementByValue: (value: JsonValue) => HTMLElement | null;
41
- /**
42
- * Get either the hovered or first selected option id.
43
- */
44
- getHoveredOrFirstSelectedId: () => Promise<string | null>;
45
39
  /**
46
40
  * Append the option to the parent array, used for keeping track of options.
47
41
  * @param value The value of the option. This must be unique.
@@ -64,10 +58,6 @@ declare class SelectRoot extends Floating {
64
58
  * Handles if singular or mulitple values.
65
59
  */
66
60
  setSelected: () => void;
67
- /**
68
- * Sets the trigger label to the selected value, only if it's found in the options array.
69
- */
70
- setInitialSelected: () => Promise<void>;
71
61
  state: SelectState;
72
62
  }
73
63
  type TriggerProps = GetInternalProps<SelectTriggerProps>;
@@ -127,7 +117,6 @@ declare class SelectOption {
127
117
  _root: SelectRoot;
128
118
  Hovered: boolean;
129
119
  Selected: boolean;
130
- Label: string;
131
120
  constructor(root: SelectRoot, props: OptionProps);
132
121
  props: {
133
122
  id: string;
@@ -148,7 +137,6 @@ declare class SelectValue {
148
137
  _root: SelectRoot;
149
138
  PlaceholderVisible: boolean;
150
139
  constructor(root: SelectRoot, props: ValueProps);
151
- label: string;
152
140
  props: {
153
141
  id: string;
154
142
  "data-select-value": string;
@@ -3,28 +3,48 @@ import { SvelteMap } from 'svelte/reactivity';
3
3
  import { outside } from '../../attachments/outside.js';
4
4
  import { portal } from '../../attachments/portal.js';
5
5
  import { addEvents, attach, buildContext, calculateIndex, createAttributes, floating, Floating, KEYS, removeDisabledElements, visuallyHidden } from '../../internals/index.js';
6
- const { attrs, selectors } = createAttributes('select', ['root', 'trigger', 'content', 'arrow', 'option', 'value']);
6
+ const { attrs, selectors } = createAttributes('select', [
7
+ 'root',
8
+ 'trigger',
9
+ 'content',
10
+ 'arrow',
11
+ 'option',
12
+ 'value',
13
+ 'search'
14
+ ]);
7
15
  class SelectRoot extends Floating {
8
16
  $$;
9
17
  hoveredIndex = $state(-1);
10
18
  options = $state([]);
11
- selectedOptions = $state([]);
12
19
  sharedIds = new SvelteMap();
13
20
  mounted = $state(false);
21
+ searchable = $state(false);
22
+ serachTerm = $state('');
14
23
  HoveredOption = $derived.by(() => this.options[this.hoveredIndex] || undefined);
24
+ SelectedOptions = $derived(this.options.filter((opt) => {
25
+ if (!opt.dataset.value)
26
+ return false;
27
+ return Array.isArray(this.$$.value.val)
28
+ ? this.$$.value.val.includes(opt.dataset.value)
29
+ : this.$$.value.val === opt.dataset.value;
30
+ }));
15
31
  constructor(props) {
16
32
  super();
17
33
  this.$$ = props;
18
- if (this.$$.value.val)
19
- this.setInitialSelected();
20
- else
21
- this.doneMounting();
34
+ onMount(async () => {
35
+ await tick();
36
+ this.mounted = true;
37
+ this.$$.visible.val = false;
38
+ });
22
39
  }
23
40
  /**
24
41
  * Toggle the visible state of the content
25
42
  */
26
43
  toggle = () => {
27
- this.$$.visible.val = !this.$$.visible.val;
44
+ if (this.$$.visible.val)
45
+ this.close();
46
+ else
47
+ this.open();
28
48
  };
29
49
  /**
30
50
  * Set the visible state of the content to true
@@ -38,16 +58,8 @@ class SelectRoot extends Floating {
38
58
  */
39
59
  close = () => {
40
60
  this.$$.visible.val = false;
41
- this.options = [];
42
61
  this.hoveredIndex = -1;
43
62
  };
44
- /**
45
- * Tells the components that checks have been completed, render as normal.
46
- */
47
- doneMounting = () => {
48
- this.mounted = true;
49
- this.$$.visible.val = false;
50
- };
51
63
  /**
52
64
  * Get all non-disabled options from the content.
53
65
  */
@@ -63,14 +75,6 @@ class SelectRoot extends Floating {
63
75
  getElementByValue = (value) => {
64
76
  return document.querySelector(`#${this.sharedIds.get('content')} ${selectors.option}[data-value="${value}"]`);
65
77
  };
66
- /**
67
- * Get either the hovered or first selected option id.
68
- */
69
- getHoveredOrFirstSelectedId = async () => {
70
- await tick();
71
- const selectedOption = this.HoveredOption || this.selectedOptions[0];
72
- return selectedOption.id || null;
73
- };
74
78
  /**
75
79
  * Append the option to the parent array, used for keeping track of options.
76
80
  * @param value The value of the option. This must be unique.
@@ -103,38 +107,22 @@ class SelectRoot extends Floating {
103
107
  * Handles if singular or mulitple values.
104
108
  */
105
109
  setSelected = () => {
106
- if (!this.HoveredOption)
110
+ if (!this.HoveredOption || !this.HoveredOption.dataset.value)
107
111
  return;
108
- if (this.$$.multiple.val) {
109
- if (this.selectedOptions.find((el) => el.dataset.value === this.HoveredOption?.dataset.value)) {
110
- this.selectedOptions = this.selectedOptions.filter((el) => el.dataset.value !== this.HoveredOption?.dataset.value);
111
- }
112
- else {
113
- this.selectedOptions.push(this.HoveredOption);
114
- }
112
+ const newVal = this.HoveredOption.dataset.value;
113
+ if (Array.isArray(this.$$.value.val)) {
114
+ if (this.$$.value.val.includes(newVal))
115
+ this.$$.value.val = this.$$.value.val.filter((el) => el !== newVal);
116
+ else
117
+ this.$$.value.val.push(newVal);
115
118
  }
116
119
  else {
117
- this.selectedOptions[0] = this.HoveredOption;
120
+ if (this.$$.unselectable.val && this.$$.value.val === newVal)
121
+ this.$$.value.val = '';
122
+ else
123
+ this.$$.value.val = newVal;
124
+ this.close();
118
125
  }
119
- if (!this.$$.multiple.val)
120
- this.$$.visible.val = false;
121
- this.$$.value.val = this.$$.multiple.val
122
- ? this.selectedOptions.map((el) => el.dataset.value)
123
- : this.selectedOptions[0].dataset.value;
124
- };
125
- /**
126
- * Sets the trigger label to the selected value, only if it's found in the options array.
127
- */
128
- setInitialSelected = async () => {
129
- await tick();
130
- const value = this.$$.value.val;
131
- this.selectedOptions = this.options.filter((el) => {
132
- if (!Array.isArray(value) && el.dataset.value === value)
133
- return el;
134
- else if (Array.isArray(value) && value.includes(el.dataset.value))
135
- return el;
136
- });
137
- this.doneMounting();
138
126
  };
139
127
  state = $derived.by(() => ({
140
128
  visible: this.$$.visible.val
@@ -158,18 +146,17 @@ class SelectTrigger {
158
146
  ...attach((node) => {
159
147
  this._root.trigger = node;
160
148
  // Such a hacky way, but it works :\
161
- if (this._root.HoveredOption || this._root.selectedOptions[0]) {
162
- tick().then(async () => {
149
+ if (this._root.HoveredOption || this._root.SelectedOptions[0]) {
150
+ tick().then(() => {
163
151
  if (this._root.$$.visible.val) {
164
- const id = await this._root.getHoveredOrFirstSelectedId();
152
+ const { id } = this._root.HoveredOption || this._root.SelectedOptions[0];
165
153
  if (id)
166
154
  node.setAttribute('aria-activedescendant', id);
167
155
  }
168
- else {
169
- node.removeAttribute('aria-activedescendant');
170
- }
171
156
  });
172
157
  }
158
+ if (!this._root.$$.visible.val)
159
+ node.removeAttribute('aria-activedescendant');
173
160
  return addEvents(node, {
174
161
  click: () => {
175
162
  if (this._root.$$.disabled.val)
@@ -194,7 +181,7 @@ class SelectTrigger {
194
181
  e.preventDefault();
195
182
  if (this._root.HoveredOption && this._root.$$.visible.val) {
196
183
  this._root.HoveredOption?.click();
197
- if (!this._root.$$.multiple.val)
184
+ if (!Array.isArray(this._root.$$.value.val))
198
185
  this._root.close();
199
186
  }
200
187
  else {
@@ -218,14 +205,17 @@ class SelectContent {
218
205
  this._root = root;
219
206
  this.$$ = props;
220
207
  this._root.sharedIds.set('content', this.$$.id.val);
208
+ // Get element references but wait for label to be populated.
221
209
  $effect(() => {
222
- // Get element references
223
- if (this._root.$$.visible.val)
224
- this._root.getAvilableOptions();
210
+ if (this._root.$$.visible.val) {
211
+ tick().then(() => {
212
+ this._root.getAvilableOptions();
213
+ });
214
+ }
225
215
  });
216
+ // Set first selected value as highlighted option
226
217
  $effect(() => {
227
- // Set first selected value as highlighted option
228
- if (this._root.$$.visible.val && this._root.selectedOptions.length && !this._root.HoveredOption) {
218
+ if (this._root.$$.visible.val && this._root.SelectedOptions.length && !this._root.HoveredOption) {
229
219
  this._root.hoveredIndex = this._root.options.findIndex((el) => el.dataset.selected === 'true');
230
220
  }
231
221
  });
@@ -275,8 +265,7 @@ class SelectOption {
275
265
  $$;
276
266
  _root;
277
267
  Hovered = $derived.by(() => this._root.HoveredOption?.dataset.value === this.$$.value.val);
278
- Selected = $derived.by(() => !!this._root.selectedOptions.find((el) => el.dataset.value === this.$$.value.val));
279
- Label = $derived.by(() => this.$$.label.val);
268
+ Selected = $derived.by(() => !!this._root.SelectedOptions.find((el) => el.dataset.value === this.$$.value.val));
280
269
  constructor(root, props) {
281
270
  this._root = root;
282
271
  this.$$ = props;
@@ -290,11 +279,8 @@ class SelectOption {
290
279
  tabindex: 0,
291
280
  'aria-selected': this.Selected,
292
281
  'data-value': this.$$.value.val,
293
- 'data-label': this.Label,
282
+ 'data-label': this.$$.ref.val && !this.$$.label.val ? this.$$.ref.val.textContent.trim() : this.$$.label.val,
294
283
  ...attach((node) => {
295
- if (!this.$$.label.val && this.$$.ref.val) {
296
- this.Label = this.$$.ref.val.textContent.trim();
297
- }
298
284
  // Set the hovered index to the active item, if that item is "selected".
299
285
  if (this._root.$$.value.val === this.$$.value.val) {
300
286
  this._root.hoveredIndex = this._root.options.findIndex((el) => el.dataset.value === this.$$.value.val);
@@ -321,14 +307,11 @@ class SelectOption {
321
307
  class SelectValue {
322
308
  $$;
323
309
  _root;
324
- PlaceholderVisible = $derived.by(() => this._root.selectedOptions.length === 0);
310
+ PlaceholderVisible = $derived.by(() => !this._root.$$.value.val.length);
325
311
  constructor(root, props) {
326
312
  this._root = root;
327
313
  this.$$ = props;
328
314
  }
329
- label = $derived.by(() => this.PlaceholderVisible
330
- ? this.$$.placeholder.val
331
- : this._root.selectedOptions.map((el) => el.dataset.label).join(','));
332
315
  props = $derived.by(() => ({
333
316
  id: this.$$.id.val,
334
317
  [attrs.value]: '',
@@ -336,7 +319,8 @@ class SelectValue {
336
319
  }));
337
320
  state = $derived.by(() => ({
338
321
  visible: this._root.$$.visible.val,
339
- placeholderVisible: this.PlaceholderVisible
322
+ placeholderVisible: this.PlaceholderVisible,
323
+ selectedLabels: this._root.SelectedOptions.map((el) => el.dataset.label)
340
324
  }));
341
325
  }
342
326
  //
@@ -1,20 +1,22 @@
1
- import type { FloatingContent, Props, PropsNoChildren, PropsNoRender } from '../../internals/index.js';
2
- export interface SelectProps<V extends string | string[] = any> extends PropsNoRender<SelectState>, FloatingContent {
1
+ import type { FloatingContent, Props, PropsNoRender } from '../../internals/index.js';
2
+ export interface SelectProps extends PropsNoRender<SelectState>, FloatingContent {
3
3
  /**
4
4
  * The currently selected option(s).
5
5
  *
6
6
  * ### `$bindable`
7
7
  */
8
- value?: V;
8
+ value?: string | string[];
9
9
  /**
10
- * Allows multiple options to be selected at once.
10
+ * Allows a non-array value to be unselected.
11
+ *
12
+ * @default false
11
13
  */
12
- multiple?: boolean;
14
+ unselectable?: boolean;
13
15
  /**
14
16
  * Fires whenever the `value` prop changes.
15
17
  * @param value The new value
16
18
  */
17
- onValueChanged?: (value: V) => void;
19
+ onValueChanged?: (value: string | string[]) => void;
18
20
  }
19
21
  export interface SelectState {
20
22
  /**
@@ -74,7 +76,7 @@ export interface SelectOptionState {
74
76
  */
75
77
  selected: boolean;
76
78
  }
77
- export interface SelectValueProps extends PropsNoChildren<HTMLSpanElement, SelectValueState> {
79
+ export interface SelectValueProps<P = any> extends Props<HTMLSpanElement, P, SelectValueState> {
78
80
  /**
79
81
  * The value displayed when no option(s) is selected.
80
82
  */
@@ -89,4 +91,8 @@ export interface SelectValueState {
89
91
  * True if no options are selected.
90
92
  */
91
93
  placeholderVisible: boolean;
94
+ /**
95
+ * The currently selected options.
96
+ */
97
+ selectedLabels: string[];
92
98
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lithesome",
3
- "version": "0.23.7",
3
+ "version": "0.24.0",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "bun --bun vite build && npm run prepack",