@quaffui/quaff 1.0.0-beta1 → 1.0.0-beta12

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 (216) hide show
  1. package/README.md +2 -0
  2. package/dist/classes/QScrollObserver.svelte.d.ts +4 -4
  3. package/dist/classes/QScrollObserver.svelte.js +26 -13
  4. package/dist/components/avatar/QAvatar.svelte +4 -0
  5. package/dist/components/avatar/QAvatar.svelte.d.ts +4 -14
  6. package/dist/components/breadcrumbs/QBreadcrumbs.scss +9 -5
  7. package/dist/components/breadcrumbs/QBreadcrumbs.svelte +46 -16
  8. package/dist/components/breadcrumbs/QBreadcrumbs.svelte.d.ts +24 -12
  9. package/dist/components/breadcrumbs/QBreadcrumbsEl.scss +22 -3
  10. package/dist/components/breadcrumbs/QBreadcrumbsEl.svelte +50 -38
  11. package/dist/components/breadcrumbs/QBreadcrumbsEl.svelte.d.ts +4 -14
  12. package/dist/components/breadcrumbs/props.d.ts +4 -4
  13. package/dist/components/button/QBtn.scss +3 -1
  14. package/dist/components/button/QBtn.svelte +38 -22
  15. package/dist/components/button/QBtn.svelte.d.ts +4 -14
  16. package/dist/components/button/props.d.ts +9 -2
  17. package/dist/components/card/QCard.svelte +9 -5
  18. package/dist/components/card/QCard.svelte.d.ts +4 -14
  19. package/dist/components/card/QCardActions.svelte +4 -0
  20. package/dist/components/card/QCardActions.svelte.d.ts +4 -14
  21. package/dist/components/card/QCardSection.svelte +2 -0
  22. package/dist/components/card/QCardSection.svelte.d.ts +4 -14
  23. package/dist/components/checkbox/QCheckbox.svelte +6 -4
  24. package/dist/components/checkbox/QCheckbox.svelte.d.ts +4 -14
  25. package/dist/components/checkbox/props.d.ts +1 -1
  26. package/dist/components/chip/QChip.scss +3 -1
  27. package/dist/components/chip/QChip.svelte +24 -14
  28. package/dist/components/chip/QChip.svelte.d.ts +4 -14
  29. package/dist/components/codeBlock/QCodeBlock.svelte +8 -0
  30. package/dist/components/codeBlock/QCodeBlock.svelte.d.ts +4 -14
  31. package/dist/components/dialog/QDialog.scss +17 -0
  32. package/dist/components/dialog/QDialog.svelte +34 -9
  33. package/dist/components/dialog/QDialog.svelte.d.ts +8 -21
  34. package/dist/components/drawer/QDrawer.scss +2 -2
  35. package/dist/components/drawer/QDrawer.svelte +124 -69
  36. package/dist/components/drawer/QDrawer.svelte.d.ts +8 -21
  37. package/dist/components/drawer/props.d.ts +3 -3
  38. package/dist/components/expansion-item/QExpansionItem.scss +59 -0
  39. package/dist/components/expansion-item/QExpansionItem.svelte +319 -0
  40. package/dist/components/expansion-item/QExpansionItem.svelte.d.ts +8 -0
  41. package/dist/components/expansion-item/docs.d.ts +2 -0
  42. package/dist/components/expansion-item/docs.js +17 -0
  43. package/dist/components/expansion-item/props.d.ts +129 -0
  44. package/dist/components/expansion-item/props.js +1 -0
  45. package/dist/components/footer/QFooter.scss +1 -1
  46. package/dist/components/footer/QFooter.svelte +32 -28
  47. package/dist/components/footer/QFooter.svelte.d.ts +4 -14
  48. package/dist/components/header/QHeader.scss +1 -1
  49. package/dist/components/header/QHeader.svelte +41 -33
  50. package/dist/components/header/QHeader.svelte.d.ts +4 -14
  51. package/dist/components/icon/QIcon.svelte +6 -4
  52. package/dist/components/icon/QIcon.svelte.d.ts +4 -14
  53. package/dist/components/index.d.ts +3 -1
  54. package/dist/components/index.js +3 -1
  55. package/dist/components/input/QInput.svelte +139 -17
  56. package/dist/components/input/QInput.svelte.d.ts +4 -14
  57. package/dist/components/input/docs.js +2 -2
  58. package/dist/components/input/mask.d.ts +10 -0
  59. package/dist/components/input/mask.js +204 -0
  60. package/dist/components/input/props.d.ts +37 -4
  61. package/dist/components/layout/QLayout.svelte +248 -93
  62. package/dist/components/layout/QLayout.svelte.d.ts +67 -15
  63. package/dist/components/layout/props.d.ts +1 -1
  64. package/dist/components/list/QItem.scss +7 -4
  65. package/dist/components/list/QItem.svelte +44 -24
  66. package/dist/components/list/QItem.svelte.d.ts +17 -13
  67. package/dist/components/list/QItemSection.scss +24 -3
  68. package/dist/components/list/QItemSection.svelte +19 -21
  69. package/dist/components/list/QItemSection.svelte.d.ts +4 -14
  70. package/dist/components/list/QList.scss +17 -4
  71. package/dist/components/list/QList.svelte +30 -8
  72. package/dist/components/list/QList.svelte.d.ts +17 -13
  73. package/dist/components/list/props.d.ts +3 -3
  74. package/dist/components/menu/QMenu.scss +37 -0
  75. package/dist/components/menu/QMenu.svelte +314 -0
  76. package/dist/components/menu/QMenu.svelte.d.ts +8 -0
  77. package/dist/components/menu/docs.d.ts +2 -0
  78. package/dist/components/menu/docs.js +27 -0
  79. package/dist/components/menu/props.d.ts +48 -0
  80. package/dist/components/menu/props.js +1 -0
  81. package/dist/components/progress/QCircularProgress.svelte +17 -14
  82. package/dist/components/progress/QCircularProgress.svelte.d.ts +4 -14
  83. package/dist/components/progress/QLinearProgress.svelte +15 -15
  84. package/dist/components/progress/QLinearProgress.svelte.d.ts +4 -14
  85. package/dist/components/radio/QRadio.svelte +6 -4
  86. package/dist/components/radio/QRadio.svelte.d.ts +4 -14
  87. package/dist/components/radio/props.d.ts +1 -1
  88. package/dist/components/railbar/QRailbar.scss +1 -1
  89. package/dist/components/railbar/QRailbar.svelte +36 -35
  90. package/dist/components/railbar/QRailbar.svelte.d.ts +4 -14
  91. package/dist/components/select/QSelect.svelte +316 -102
  92. package/dist/components/select/QSelect.svelte.d.ts +4 -14
  93. package/dist/components/select/filter.d.ts +13 -0
  94. package/dist/components/select/filter.js +73 -0
  95. package/dist/components/select/index.scss +28 -27
  96. package/dist/components/select/option.d.ts +9 -0
  97. package/dist/components/select/option.js +59 -0
  98. package/dist/components/select/props.d.ts +40 -7
  99. package/dist/components/separator/QSeparator.scss +2 -0
  100. package/dist/components/separator/QSeparator.svelte +9 -8
  101. package/dist/components/separator/QSeparator.svelte.d.ts +4 -14
  102. package/dist/components/switch/QSwitch.scss +12 -6
  103. package/dist/components/switch/QSwitch.svelte +7 -1
  104. package/dist/components/switch/QSwitch.svelte.d.ts +4 -14
  105. package/dist/components/table/QTable.svelte +31 -19
  106. package/dist/components/table/QTable.svelte.d.ts +4 -14
  107. package/dist/components/table/index.scss +1 -1
  108. package/dist/components/tabs/QTab.scss +2 -0
  109. package/dist/components/tabs/QTab.svelte +19 -22
  110. package/dist/components/tabs/QTab.svelte.d.ts +4 -14
  111. package/dist/components/tabs/QTabs.svelte +59 -32
  112. package/dist/components/tabs/QTabs.svelte.d.ts +18 -18
  113. package/dist/components/toolbar/QToolbar.svelte +2 -0
  114. package/dist/components/toolbar/QToolbar.svelte.d.ts +4 -14
  115. package/dist/components/toolbar/QToolbarTitle.svelte +2 -0
  116. package/dist/components/toolbar/QToolbarTitle.svelte.d.ts +4 -14
  117. package/dist/components/tooltip/QTooltip.svelte +48 -38
  118. package/dist/components/tooltip/QTooltip.svelte.d.ts +29 -17
  119. package/dist/components/tooltip/QTooltipBase.svelte +18 -8
  120. package/dist/components/tooltip/QTooltipBase.svelte.d.ts +4 -14
  121. package/dist/composables/index.d.ts +2 -0
  122. package/dist/composables/index.js +2 -0
  123. package/dist/composables/useColor.d.ts +1 -0
  124. package/dist/composables/useColor.js +19 -0
  125. package/dist/composables/useRevealScrollObserver.svelte.d.ts +9 -0
  126. package/dist/composables/useRevealScrollObserver.svelte.js +25 -0
  127. package/dist/composables/useRouterLink.d.ts +3 -2
  128. package/dist/composables/useRouterLink.js +2 -2
  129. package/dist/css/_components.scss +2 -0
  130. package/dist/css/classes/_grid.scss +12 -1
  131. package/dist/css/index.css +1 -1
  132. package/dist/css/mixins/_design.scss +1 -1
  133. package/dist/css/mixins/_field.scss +3 -2
  134. package/dist/css/mixins/_table.scss +1 -1
  135. package/dist/css/mixins/_toolbar.scss +1 -1
  136. package/dist/css/shared/q-field.scss +7 -6
  137. package/dist/css/theme/_page.scss +8 -6
  138. package/dist/css/theme/_reset.scss +2 -1
  139. package/dist/helpers/clickOutside.js +5 -4
  140. package/dist/helpers/ripple.js +5 -6
  141. package/dist/helpers/version.d.ts +1 -1
  142. package/dist/helpers/version.js +1 -1
  143. package/dist/{components/private/ContextReseter.svelte → internal/ContextResetter.svelte} +2 -3
  144. package/dist/internal/ContextResetter.svelte.d.ts +8 -0
  145. package/dist/{components/private → internal}/QIconSnippet.svelte +2 -2
  146. package/dist/internal/QIconSnippet.svelte.d.ts +10 -0
  147. package/dist/utils/context.d.ts +49 -32
  148. package/dist/utils/context.js +82 -33
  149. package/dist/utils/dom.d.ts +6 -0
  150. package/dist/utils/dom.js +33 -0
  151. package/dist/utils/events.d.ts +0 -24
  152. package/dist/utils/events.js +0 -24
  153. package/package.json +44 -38
  154. package/dist/classes/QContext.svelte.d.ts +0 -42
  155. package/dist/classes/QContext.svelte.js +0 -63
  156. package/dist/components/avatar/docs.props.d.ts +0 -3
  157. package/dist/components/avatar/docs.props.js +0 -87
  158. package/dist/components/breadcrumbs/docs.props.d.ts +0 -5
  159. package/dist/components/breadcrumbs/docs.props.js +0 -144
  160. package/dist/components/button/docs.props.d.ts +0 -3
  161. package/dist/components/button/docs.props.js +0 -227
  162. package/dist/components/card/docs.props.d.ts +0 -7
  163. package/dist/components/card/docs.props.js +0 -89
  164. package/dist/components/checkbox/docs.props.d.ts +0 -3
  165. package/dist/components/checkbox/docs.props.js +0 -41
  166. package/dist/components/chip/docs.props.d.ts +0 -3
  167. package/dist/components/chip/docs.props.js +0 -137
  168. package/dist/components/codeBlock/docs.props.d.ts +0 -3
  169. package/dist/components/codeBlock/docs.props.js +0 -83
  170. package/dist/components/dialog/docs.props.d.ts +0 -3
  171. package/dist/components/dialog/docs.props.js +0 -65
  172. package/dist/components/drawer/docs.props.d.ts +0 -3
  173. package/dist/components/drawer/docs.props.js +0 -149
  174. package/dist/components/footer/docs.props.d.ts +0 -3
  175. package/dist/components/footer/docs.props.js +0 -65
  176. package/dist/components/header/docs.props.d.ts +0 -7
  177. package/dist/components/header/docs.props.js +0 -131
  178. package/dist/components/icon/docs.props.d.ts +0 -3
  179. package/dist/components/icon/docs.props.js +0 -107
  180. package/dist/components/input/docs.props.d.ts +0 -3
  181. package/dist/components/input/docs.props.js +0 -162
  182. package/dist/components/layout/docs.props.d.ts +0 -3
  183. package/dist/components/layout/docs.props.js +0 -81
  184. package/dist/components/list/docs.props.d.ts +0 -11
  185. package/dist/components/list/docs.props.js +0 -434
  186. package/dist/components/private/ContextReseter.svelte.d.ts +0 -14
  187. package/dist/components/private/QApi.svelte +0 -296
  188. package/dist/components/private/QApi.svelte.d.ts +0 -14
  189. package/dist/components/private/QDocs.svelte +0 -155
  190. package/dist/components/private/QDocs.svelte.d.ts +0 -14
  191. package/dist/components/private/QDocsSection.svelte +0 -62
  192. package/dist/components/private/QDocsSection.svelte.d.ts +0 -14
  193. package/dist/components/private/QIconSnippet.svelte.d.ts +0 -14
  194. package/dist/components/private/index.d.ts +0 -6
  195. package/dist/components/private/index.js +0 -6
  196. package/dist/components/progress/docs.props.d.ts +0 -5
  197. package/dist/components/progress/docs.props.js +0 -314
  198. package/dist/components/radio/docs.props.d.ts +0 -3
  199. package/dist/components/radio/docs.props.js +0 -53
  200. package/dist/components/railbar/docs.props.d.ts +0 -3
  201. package/dist/components/railbar/docs.props.js +0 -47
  202. package/dist/components/select/docs.props.d.ts +0 -3
  203. package/dist/components/select/docs.props.js +0 -198
  204. package/dist/components/separator/docs.props.d.ts +0 -5
  205. package/dist/components/separator/docs.props.js +0 -196
  206. package/dist/components/switch/docs.props.d.ts +0 -3
  207. package/dist/components/switch/docs.props.js +0 -119
  208. package/dist/components/table/docs.props.d.ts +0 -3
  209. package/dist/components/table/docs.props.js +0 -94
  210. package/dist/components/tabs/docs.props.d.ts +0 -5
  211. package/dist/components/tabs/docs.props.js +0 -86
  212. package/dist/components/toolbar/docs.props.d.ts +0 -5
  213. package/dist/components/toolbar/docs.props.js +0 -68
  214. package/dist/components/tooltip/docs.props.d.ts +0 -3
  215. package/dist/components/tooltip/docs.props.js +0 -77
  216. package/dist/utils/types.json +0 -31
@@ -1,11 +1,9 @@
1
1
  <script lang="ts">
2
- import { getContext, onDestroy, onMount, untrack } from "svelte";
3
- import { QContext } from "../../classes/QContext.svelte";
4
- import { QLayoutCtxName } from "../../utils";
5
- import type { QLayoutProps } from "../layout/props";
6
- import type { DrawerContext } from "../layout/QLayout.svelte";
2
+ import { onMount } from "svelte";
3
+ import { leftRailbarCtx, rightRailbarCtx } from "../layout/QLayout.svelte";
7
4
  import type { QRailbarProps } from "./props";
8
5
 
6
+ // #region: --- Props
9
7
  let {
10
8
  width = 88,
11
9
  side = "left",
@@ -13,50 +11,53 @@
13
11
  children,
14
12
  ...props
15
13
  }: QRailbarProps = $props();
14
+ // #endregion: --- Props
16
15
 
17
- const railbarCtx = QContext.get<DrawerContext>(QLayoutCtxName.railbar[side]);
18
- const layoutView = getContext<{ value: NonNullable<QLayoutProps["view"]> }>(
19
- QLayoutCtxName.view,
20
- );
21
-
16
+ // #region: --- Non-reactive variables
22
17
  let railbarEl: HTMLElement;
18
+ // #endregion: --- Non-reactive variables
23
19
 
24
- onMount(() => {
25
- if (railbarCtx) {
26
- setTimeout(() => {
27
- railbarEl.style.transition = "top 0.3s, bottom 0.3s, transform 0.3s";
28
- }, 100);
29
- }
30
- });
31
-
32
- onDestroy(() => {
33
- untrack(() => railbarCtx)?.updateEntries({
34
- width: 0,
35
- takesSpace: false,
36
- ready: false,
37
- });
38
- });
39
-
40
- $effect.pre(() => {
41
- untrack(() => railbarCtx)?.updateEntries({
42
- width,
43
- takesSpace: railbarEl?.style.display !== "none" || false,
44
- ready: true,
45
- });
46
- });
20
+ // #region: --- Derived values
21
+ const railbarCtxToUse = $derived(
22
+ side === "left" ? leftRailbarCtx : rightRailbarCtx,
23
+ );
24
+ const railbarCtx = $derived(railbarCtxToUse.get());
47
25
 
48
26
  const offsetTop = $derived.by(() => {
49
27
  const charPos = side === "left" ? 0 : 2;
50
- return layoutView?.value.charAt(charPos) === "h";
28
+ return railbarCtx?.view.charAt(charPos) === "h";
51
29
  });
52
30
  const offsetBottom = $derived.by(() => {
53
31
  const charPos = side === "left" ? 8 : 10;
54
- return layoutView?.value.charAt(charPos) === "f";
32
+ return railbarCtx?.view.charAt(charPos) === "f";
55
33
  });
56
34
 
57
35
  const railbarWidthStyle = $derived(`--${side}-railbar-width: ${width}px`);
58
36
 
59
37
  const style = $derived(`${railbarWidthStyle};${props.style ?? ""}`);
38
+ // #endregion: --- Derived values
39
+
40
+ // #region: --- Lifecycle
41
+ onMount(() => {
42
+ railbarCtxToUse.updateEntries({
43
+ width,
44
+ takesSpace: railbarEl.style.display !== "none" || false,
45
+ ready: true,
46
+ });
47
+
48
+ setTimeout(() => {
49
+ railbarEl.style.transition = "top 0.3s, bottom 0.3s, transform 0.3s";
50
+ }, 100);
51
+
52
+ return () => {
53
+ railbarCtxToUse.updateEntries({
54
+ width: 0,
55
+ takesSpace: false,
56
+ ready: false,
57
+ });
58
+ };
59
+ });
60
+ // #endregion: --- Lifecycle
60
61
  </script>
61
62
 
62
63
  <nav
@@ -1,14 +1,4 @@
1
- import { SvelteComponentTyped } from "svelte";
2
- declare const __propDef: {
3
- props: Record<string, never>;
4
- events: {
5
- [evt: string]: CustomEvent<any>;
6
- };
7
- slots: {};
8
- };
9
- type QRailbarProps_ = typeof __propDef.props;
10
- export { QRailbarProps_ as QRailbarProps };
11
- export type QRailbarEvents = typeof __propDef.events;
12
- export type QRailbarSlots = typeof __propDef.slots;
13
- export default class QRailbar extends SvelteComponentTyped<QRailbarProps_, QRailbarEvents, QRailbarSlots> {
14
- }
1
+ import type { QRailbarProps } from "./props";
2
+ declare const QRailbar: import("svelte").Component<QRailbarProps, {}, "">;
3
+ type QRailbar = ReturnType<typeof QRailbar>;
4
+ export default QRailbar;
@@ -1,21 +1,30 @@
1
1
  <script lang="ts">
2
- import { onDestroy, onMount } from "svelte";
3
- import { browser } from "$app/environment";
4
- import { QIcon } from "../..";
5
- import type { QEvent } from "../../utils";
6
- import type {
7
- QSelectMultipleValue,
8
- QSelectOption,
9
- QSelectProps,
10
- } from "./props";
2
+ import { onDestroy, tick } from "svelte";
3
+ import { QIcon, QItem, QItemSection, QList, QMenu } from "../..";
4
+ import { isActivationKey, type QEvent } from "../../utils";
5
+ import {
6
+ createFilterRunner,
7
+ getFilteredOptions,
8
+ getInputValue,
9
+ } from "./filter";
10
+ import {
11
+ doValuesMatch,
12
+ getDisplayValue,
13
+ getInitialOptionIndex,
14
+ getNextValue,
15
+ getOptionLabel,
16
+ normalizeOptionIndex,
17
+ } from "./option";
18
+ import type { QSelectOption, QSelectProps } from "./props";
11
19
 
12
20
  type QSelectEvent<T> = QEvent<T, HTMLDivElement>;
13
21
 
22
+ // #region: --- Props
14
23
  let {
15
24
  options,
16
25
  multiple = false,
17
26
  dense = false,
18
- disable = false,
27
+ disabled = false,
19
28
  error = false,
20
29
  errorMessage = undefined,
21
30
  filled = false,
@@ -24,124 +33,296 @@
24
33
  outlined = false,
25
34
  rounded = false,
26
35
  displayValue,
27
- before = undefined,
28
- prepend = undefined,
29
- append = undefined,
30
- after = undefined,
36
+ emitValue = false,
37
+ useInput = false,
38
+ filterable = false,
39
+ inputDebounce = 300,
40
+ noOptionText = "No options",
41
+ onFilter,
42
+ before,
43
+ prepend,
44
+ append,
45
+ after,
31
46
  value = $bindable(),
32
47
  ...props
33
48
  }: QSelectProps = $props();
49
+ // #endregion: --- Props
34
50
 
35
- let focus = $state(false);
51
+ // #region: --- Non-reactive variables
52
+ const id = $props.id();
53
+ const listboxId = `q-select__listbox-${id}`;
54
+ // #endregion: --- Non-reactive variables
36
55
 
37
- const currentDisplayValue = $derived.by(() => {
38
- if (displayValue !== undefined) {
39
- return displayValue;
56
+ // #region: --- Reactive variables
57
+ let isFocused = $state(false);
58
+
59
+ let menuTarget = $state<HTMLLabelElement>();
60
+ let isMenuOpen = $state(false);
61
+ let focusedOptionIndex = $state(-1);
62
+ let snippetPrependWidth = $state(0);
63
+ let searchValue = $state("");
64
+ let isSearching = $state(false);
65
+ // #endregion: --- Reactive variables
66
+
67
+ const filterRunner = createFilterRunner({
68
+ onAbort: resetFocusedOption,
69
+ onDone: focusFirstOption,
70
+ });
71
+
72
+ // #region: --- Derived values
73
+ const currentDisplayValue = $derived(
74
+ getDisplayValue(value, options, multiple, displayValue),
75
+ );
76
+
77
+ const hasDisplayValue = $derived(
78
+ currentDisplayValue !== "" && currentDisplayValue !== undefined,
79
+ );
80
+ const isActive = $derived(hasDisplayValue || isFocused || isMenuOpen);
81
+
82
+ const visibleOptions = $derived(
83
+ getFilteredOptions(options, searchValue, useInput, filterable, !!onFilter),
84
+ );
85
+ const inputValue = $derived(
86
+ getInputValue(currentDisplayValue, searchValue, useInput, isSearching),
87
+ );
88
+
89
+ const isOptionSelectedByIndex = $derived(visibleOptions.map(isSelected));
90
+ const activeOptionId = $derived(
91
+ isMenuOpen && focusedOptionIndex >= 0
92
+ ? getOptionId(focusedOptionIndex)
93
+ : undefined,
94
+ );
95
+ // #endregion: --- Derived values
96
+
97
+ // #region: --- Effects
98
+ $effect(() => {
99
+ if (disabled) {
100
+ isMenuOpen = false;
40
101
  }
102
+ });
41
103
 
42
- if (!multiple) {
43
- return value;
104
+ $effect(() => {
105
+ if (!isMenuOpen) {
106
+ focusedOptionIndex = -1;
107
+ resetSearch();
44
108
  }
109
+ });
45
110
 
46
- return (value as QSelectMultipleValue).join(", ");
111
+ $effect(() => {
112
+ if (focusedOptionIndex >= visibleOptions.length) {
113
+ focusedOptionIndex = visibleOptions.length
114
+ ? visibleOptions.length - 1
115
+ : -1;
116
+ }
47
117
  });
118
+ // #endregion: --- Effects
48
119
 
49
- const active = $derived(currentDisplayValue || focus);
120
+ onDestroy(filterRunner.clear);
50
121
 
51
- let wrapper: HTMLDivElement | null = $state(null);
52
- let isMenuOpen = $state(false);
53
- let wasClicked = $state(false);
54
- let preventClose = $state(false);
122
+ // #region: --- Functions
123
+ function handleMousedown() {
124
+ if (disabled) {
125
+ return;
126
+ }
55
127
 
56
- function handleMousedown(e: QSelectEvent<MouseEvent>) {
57
- isMenuOpen = !isMenuOpen;
58
- wasClicked = true;
59
- props.onmousedown?.(e);
128
+ if (useInput) {
129
+ void showMenu();
130
+ } else if (isMenuOpen) {
131
+ hideMenu();
132
+ } else {
133
+ void showMenu();
134
+ }
60
135
  }
61
136
 
62
137
  function handleFocus(e: QSelectEvent<FocusEvent>) {
63
- focus = true;
64
- if (!wasClicked) {
65
- isMenuOpen = true;
66
- }
67
-
68
- wasClicked = false;
138
+ isFocused = true;
69
139
  props.onfocus?.(e);
70
140
  }
71
141
 
72
142
  function handleBlur(e: QSelectEvent<FocusEvent>) {
73
- focus = false;
74
-
75
- if (!multiple && !preventClose) {
76
- isMenuOpen = false;
77
- }
78
- preventClose = false;
143
+ isFocused = false;
79
144
  props.onblur?.(e);
80
145
  }
81
146
 
82
- const selectedOptions: boolean[] = $derived(
83
- options.map((option) => isSelected(option), value),
84
- );
147
+ function handleKeydown(e: QSelectEvent<KeyboardEvent>) {
148
+ if (disabled) {
149
+ return;
150
+ }
85
151
 
86
- let snippetPrependWidth = $state(0);
152
+ const isSelectKey = useInput ? e.code === "Enter" : isActivationKey(e);
87
153
 
88
- function isSelected(option: QSelectOption) {
89
- const optionValue = typeof option === "string" ? option : option.value;
90
- return multiple
91
- ? (value as QSelectMultipleValue).includes(optionValue)
92
- : value === optionValue;
93
- }
154
+ if (isSelectKey) {
155
+ e.preventDefault();
156
+ e.stopPropagation();
94
157
 
95
- function select(evt: MouseEvent, option: QSelectOption) {
96
- evt.preventDefault();
97
- const optionValue = typeof option === "string" ? option : option.value;
158
+ if (isMenuOpen && focusedOptionIndex !== -1) {
159
+ selectOption(visibleOptions[focusedOptionIndex]);
160
+ } else if (isMenuOpen) {
161
+ hideMenu();
162
+ } else {
163
+ void showMenu();
164
+ }
165
+ } else if (e.code === "ArrowDown" || e.code === "ArrowUp") {
166
+ e.preventDefault();
167
+ e.stopPropagation();
168
+ moveFocusedOption(e.code === "ArrowDown" ? 1 : -1);
169
+ } else if (!useInput && (e.code === "Home" || e.code === "End")) {
170
+ e.preventDefault();
171
+ e.stopPropagation();
172
+
173
+ if (!isMenuOpen) {
174
+ isMenuOpen = true;
175
+ }
98
176
 
99
- if (multiple) {
100
- const hasItem = (value as QSelectMultipleValue).some(
101
- (entry) => entry === optionValue,
177
+ void setFocusedOptionIndex(
178
+ e.code === "Home" ? 0 : visibleOptions.length - 1,
102
179
  );
180
+ } else if (e.code === "Escape" && isMenuOpen) {
181
+ e.preventDefault();
182
+ e.stopPropagation();
183
+ hideMenu();
184
+ } else if (e.code === "Tab") {
185
+ hideMenu();
186
+ }
103
187
 
104
- if (hasItem) {
105
- (value as QSelectMultipleValue) = (
106
- value as QSelectMultipleValue
107
- ).filter((val) => val !== optionValue);
108
- } else {
109
- (value as QSelectMultipleValue) = [
110
- ...(value as QSelectMultipleValue),
111
- optionValue,
112
- ];
113
- }
188
+ props.onkeydown?.(e);
189
+ }
114
190
 
191
+ function handleInput(e: Event) {
192
+ if (!useInput) {
115
193
  return;
116
194
  }
117
195
 
118
- value = optionValue;
196
+ searchValue = (e.currentTarget as HTMLInputElement).value;
197
+ isSearching = true;
198
+
199
+ if (!isMenuOpen) {
200
+ void showMenu(0);
201
+ } else {
202
+ void setFocusedOptionIndex(0);
203
+ }
204
+
205
+ filterRunner.schedule(searchValue, getExternalFilter(), inputDebounce);
206
+ }
207
+
208
+ async function showMenu(optionIndex = getInitialFocusedOptionIndex()) {
209
+ isMenuOpen = true;
210
+ await setFocusedOptionIndex(optionIndex);
211
+ }
212
+
213
+ function hideMenu() {
119
214
  isMenuOpen = false;
215
+ focusedOptionIndex = -1;
120
216
  }
121
217
 
122
- function handleClickOutside(event: MouseEvent) {
123
- if (wrapper && !wrapper.contains(event.target as Node)) {
124
- isMenuOpen = false;
218
+ function moveFocusedOption(offset: number) {
219
+ if (!visibleOptions.length) {
220
+ return;
125
221
  }
222
+
223
+ if (!isMenuOpen) {
224
+ void showMenu(getInitialFocusedOptionIndex(offset));
225
+ return;
226
+ }
227
+
228
+ const baseIndex =
229
+ focusedOptionIndex === -1
230
+ ? getInitialFocusedOptionIndex(offset)
231
+ : focusedOptionIndex;
232
+
233
+ void setFocusedOptionIndex(baseIndex + offset);
126
234
  }
127
235
 
128
- onMount(() => {
129
- if (browser) {
130
- window.document.addEventListener("click", handleClickOutside);
236
+ function getInitialFocusedOptionIndex(direction = 1) {
237
+ if (!visibleOptions.length) {
238
+ return -1;
131
239
  }
132
- });
133
240
 
134
- onDestroy(() => {
135
- if (browser) {
136
- document.removeEventListener("click", handleClickOutside);
241
+ return getInitialOptionIndex(visibleOptions, isSelected, direction);
242
+ }
243
+
244
+ async function setFocusedOptionIndex(optionIndex: number) {
245
+ focusedOptionIndex = normalizeOptionIndex(
246
+ optionIndex,
247
+ visibleOptions.length,
248
+ );
249
+
250
+ await tick();
251
+ scrollFocusedOptionIntoView();
252
+ }
253
+
254
+ function resetFocusedOption() {
255
+ focusedOptionIndex = -1;
256
+ }
257
+
258
+ function focusFirstOption() {
259
+ return setFocusedOptionIndex(0);
260
+ }
261
+
262
+ function scrollFocusedOptionIntoView() {
263
+ if (focusedOptionIndex === -1) {
264
+ return;
265
+ }
266
+
267
+ document
268
+ .getElementById(getOptionId(focusedOptionIndex))
269
+ ?.scrollIntoView({ block: "nearest" });
270
+ }
271
+
272
+ function getOptionId(optionIndex: number) {
273
+ return `${listboxId}__option-${optionIndex}`;
274
+ }
275
+
276
+ function isSelected(option: QSelectOption) {
277
+ return multiple
278
+ ? Array.isArray(value) && value.some((opt) => doValuesMatch(opt, option))
279
+ : doValuesMatch(value as QSelectOption, option);
280
+ }
281
+
282
+ function handleOptionMousedown(evt: MouseEvent) {
283
+ evt.preventDefault();
284
+ }
285
+
286
+ function handleOptionClick(
287
+ evt: MouseEvent,
288
+ option: QSelectOption,
289
+ optionIndex: number,
290
+ ) {
291
+ evt.preventDefault();
292
+ focusedOptionIndex = optionIndex;
293
+ selectOption(option);
294
+ }
295
+
296
+ function selectOption(option: QSelectOption) {
297
+ value = getNextValue(value, option, multiple, emitValue);
298
+
299
+ if (multiple) {
300
+ resetSearch();
301
+ return;
137
302
  }
138
- });
303
+
304
+ hideMenu();
305
+ }
306
+
307
+ function resetSearch() {
308
+ if (!isSearching && !searchValue) {
309
+ return;
310
+ }
311
+
312
+ searchValue = "";
313
+ isSearching = false;
314
+ filterRunner.schedule("", getExternalFilter(), inputDebounce);
315
+ }
316
+
317
+ function getExternalFilter() {
318
+ return useInput ? onFilter : undefined;
319
+ }
320
+ // #endregion: --- Functions
139
321
 
140
322
  // q-field here, q-select in classes
141
323
  </script>
142
324
 
143
325
  <div
144
- bind:this={wrapper}
145
326
  {...props}
146
327
  class={[
147
328
  "q-field",
@@ -153,14 +334,15 @@
153
334
  filled && "q-field--filled",
154
335
  (outlined || rounded) && "q-field--has-border",
155
336
  dense && "q-field--dense",
156
- active && "q-field--active",
157
- focus && "q-field--focus",
337
+ isActive && "q-field--active",
338
+ isFocused && "q-field--focus",
158
339
  label && "q-field--label",
159
340
  !!append && "q-field--snippet-append",
160
341
  !!prepend && "q-field--snippet-prepend",
161
- disable && "q-field--disable",
342
+ disabled && "q-field--disabled",
162
343
  error && "q-field--error",
163
344
  (hint || (error && errorMessage)) && "q-field--bottom-space",
345
+ useInput && "q-field--use-input",
164
346
  ]}
165
347
  style:--snippet-prepend-width="{snippetPrependWidth}px"
166
348
  data-quaff
@@ -172,7 +354,7 @@
172
354
  {/if}
173
355
 
174
356
  <div class="q-field__inner">
175
- <label class="q-field__wrapper">
357
+ <label bind:this={menuTarget} class="q-field__wrapper">
176
358
  {#if prepend}
177
359
  <div
178
360
  class="q-field__snippet-prepend"
@@ -184,14 +366,24 @@
184
366
 
185
367
  <input
186
368
  class="q-field__input"
187
- value={currentDisplayValue}
369
+ value={inputValue}
188
370
  placeholder=""
371
+ role="combobox"
372
+ aria-autocomplete={useInput ? "list" : "none"}
373
+ aria-controls={listboxId}
374
+ aria-expanded={isMenuOpen}
375
+ aria-haspopup="listbox"
376
+ aria-label={label}
377
+ aria-activedescendant={activeOptionId}
378
+ aria-readonly={useInput ? undefined : "true"}
189
379
  onfocus={handleFocus}
190
380
  onblur={handleBlur}
381
+ oninput={handleInput}
191
382
  onmousedown={handleMousedown}
192
- disabled={disable}
193
- tabindex={disable === true ? -1 : 0}
194
- readonly
383
+ onkeydown={handleKeydown}
384
+ {disabled}
385
+ tabindex={disabled === true ? -1 : 0}
386
+ readonly={!useInput}
195
387
  />
196
388
 
197
389
  <span class="q-field__label">{label}</span>
@@ -204,23 +396,45 @@
204
396
  ? 'q-select__arrow-toggle--has-append'
205
397
  : ''}"
206
398
  name={`arrow_drop_${isMenuOpen ? "up" : "down"}`}
399
+ onmousedown={handleMousedown}
207
400
  />
208
401
  </div>
209
402
  </label>
210
403
 
211
- <div class="q-select__menu {isMenuOpen ? 'q-select__menu--active' : ''}">
212
- {#each options as option, idx (idx)}
213
- <a
214
- href={multiple ? "javascript:void(0)" : undefined}
215
- class="q-select__option {selectedOptions[idx]
216
- ? 'q-select__option--selected'
217
- : ''}"
218
- onmousedown={() => (preventClose = true)}
219
- onclick={(e) => select(e, option)}
220
- >{typeof option === "string" ? option : option.value}</a
221
- >
222
- {/each}
223
- </div>
404
+ <QMenu
405
+ bind:value={isMenuOpen}
406
+ target={menuTarget}
407
+ fit
408
+ autoClose={!multiple}
409
+ class="q-select__menu"
410
+ id={listboxId}
411
+ role="listbox"
412
+ aria-multiselectable={multiple ? "true" : undefined}
413
+ >
414
+ <QList dense>
415
+ {#each visibleOptions as option, idx (idx)}
416
+ <QItem
417
+ clickable
418
+ active={isOptionSelectedByIndex[idx]}
419
+ activeClass="q-select__option--selected"
420
+ class="q-select__option {focusedOptionIndex === idx
421
+ ? 'q-select__option--focused'
422
+ : ''}"
423
+ id={getOptionId(idx)}
424
+ role="option"
425
+ tabindex={-1}
426
+ aria-selected={isOptionSelectedByIndex[idx] ? "true" : "false"}
427
+ onmousedown={handleOptionMousedown}
428
+ onmousemove={() => (focusedOptionIndex = idx)}
429
+ onclick={(e) => handleOptionClick(e, option, idx)}
430
+ >
431
+ <QItemSection>{getOptionLabel(option, options)}</QItemSection>
432
+ </QItem>
433
+ {:else}
434
+ <div class="q-select__no-option">{noOptionText}</div>
435
+ {/each}
436
+ </QList>
437
+ </QMenu>
224
438
 
225
439
  {#if error && errorMessage}
226
440
  <div class="q-field__error">{errorMessage}</div>
@@ -1,14 +1,4 @@
1
- import { SvelteComponentTyped } from "svelte";
2
- declare const __propDef: {
3
- props: Record<string, never>;
4
- events: {
5
- [evt: string]: CustomEvent<any>;
6
- };
7
- slots: {};
8
- };
9
- type QSelectProps_ = typeof __propDef.props;
10
- export { QSelectProps_ as QSelectProps };
11
- export type QSelectEvents = typeof __propDef.events;
12
- export type QSelectSlots = typeof __propDef.slots;
13
- export default class QSelect extends SvelteComponentTyped<QSelectProps_, QSelectEvents, QSelectSlots> {
14
- }
1
+ import type { QSelectProps } from "./props";
2
+ declare const QSelect: import("svelte").Component<QSelectProps, {}, "value">;
3
+ type QSelect = ReturnType<typeof QSelect>;
4
+ export default QSelect;
@@ -0,0 +1,13 @@
1
+ import type { QSelectOption, QSelectProps } from "./props";
2
+ type QSelectOnFilter = QSelectProps["onFilter"];
3
+ type FilterRunnerOptions = {
4
+ onAbort: () => void;
5
+ onDone: () => void | Promise<void>;
6
+ };
7
+ export declare function getFilteredOptions(options: QSelectOption[], search: string, useInput: boolean, filterable: boolean, hasExternalFilter: boolean): QSelectOption[];
8
+ export declare function getInputValue(currentDisplayValue: string | number, search: string, useInput: boolean, isSearching: boolean): string | number;
9
+ export declare function createFilterRunner({ onAbort, onDone }: FilterRunnerOptions): {
10
+ clear: () => void;
11
+ schedule: (search: string, onFilter: QSelectOnFilter, debounce?: number) => void;
12
+ };
13
+ export {};