@witchcraft/ui 0.0.1 → 0.1.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 (155) hide show
  1. package/README.md +18 -28
  2. package/dist/module.d.mts +3 -1
  3. package/dist/module.d.ts +3 -1
  4. package/dist/module.json +2 -2
  5. package/dist/module.mjs +20 -11
  6. package/dist/runtime/assets/base.css +1 -1
  7. package/dist/runtime/assets/locales/en.json +2 -2
  8. package/dist/runtime/assets/tailwind.css +1 -1
  9. package/dist/runtime/assets/utils.css +1 -0
  10. package/dist/runtime/build/WitchcraftUiResolver.js +1 -1
  11. package/dist/runtime/components/Icon/Icon.vue +10 -5
  12. package/dist/runtime/components/LibButton/LibButton.vue +41 -46
  13. package/dist/runtime/components/LibCheckbox/LibCheckbox.vue +7 -3
  14. package/dist/runtime/components/LibColorInput/LibColorInput.vue +111 -36
  15. package/dist/runtime/components/LibColorPicker/LibColorPicker.stories.d.ts +2 -0
  16. package/dist/runtime/components/LibColorPicker/LibColorPicker.stories.js +26 -9
  17. package/dist/runtime/components/LibColorPicker/LibColorPicker.vue +242 -131
  18. package/dist/runtime/components/LibColorPicker/utils/safeConvertToHsva.d.ts +2 -0
  19. package/dist/runtime/components/LibColorPicker/utils/safeConvertToHsva.js +18 -0
  20. package/dist/runtime/components/LibColorPicker/utils/safeConvertToRgba.d.ts +2 -0
  21. package/dist/runtime/components/LibColorPicker/utils/safeConvertToRgba.js +17 -0
  22. package/dist/runtime/components/LibColorPicker/utils/toLowPrecisionRgbaString.d.ts +2 -0
  23. package/dist/runtime/components/LibColorPicker/utils/toLowPrecisionRgbaString.js +8 -0
  24. package/dist/runtime/components/LibColorPicker/utils/truncate.d.ts +1 -0
  25. package/dist/runtime/components/LibColorPicker/utils/truncate.js +5 -0
  26. package/dist/runtime/components/LibDarkModeSwitcher/LibDarkModeSwitcher.stories.js +1 -1
  27. package/dist/runtime/components/LibDarkModeSwitcher/LibDarkModeSwitcher.vue +11 -8
  28. package/dist/runtime/components/LibDatePicker/LibDatePicker.vue +4 -17
  29. package/dist/runtime/components/LibDatePicker/LibRangeDatePicker.vue +192 -131
  30. package/dist/runtime/components/LibDatePicker/LibSingleDatePicker.vue +183 -115
  31. package/dist/runtime/components/LibDatePicker/LibTimeZonePicker.vue +3 -3
  32. package/dist/runtime/components/LibDebug/LibDebug.vue +15 -5
  33. package/dist/runtime/components/LibDevOnly/LibDevOnly.vue +1 -3
  34. package/dist/runtime/components/LibFileInput/LibFileInput.vue +54 -28
  35. package/dist/runtime/components/{LibInput/LibInput.stories.d.ts → LibInputDeprecated/LibInputDeprecated.stories.d.ts} +6 -6
  36. package/dist/runtime/components/{LibInput/LibInput.stories.js → LibInputDeprecated/LibInputDeprecated.stories.js} +64 -19
  37. package/{src/runtime/components/LibInput/LibInput.vue → dist/runtime/components/LibInputDeprecated/LibInputDeprecated.vue} +40 -33
  38. package/dist/runtime/components/LibLabel/LibLabel.vue +2 -2
  39. package/dist/runtime/components/LibMultiValues/LibMultiValues.stories.d.ts +1 -1
  40. package/dist/runtime/components/LibMultiValues/LibMultiValues.stories.js +5 -4
  41. package/dist/runtime/components/LibMultiValues/LibMultiValues.vue +11 -12
  42. package/dist/runtime/components/LibNotifications/LibNotification.vue +19 -10
  43. package/dist/runtime/components/LibNotifications/LibNotifications.stories.js +2 -2
  44. package/dist/runtime/components/LibNotifications/LibNotifications.vue +20 -11
  45. package/dist/runtime/components/LibPagination/LibPagination.stories.js +2 -2
  46. package/dist/runtime/components/LibPagination/LibPagination.vue +19 -19
  47. package/dist/runtime/components/LibPalette/LibPalette.vue +3 -3
  48. package/dist/runtime/components/LibPopup/LibPopup.stories.js +2 -2
  49. package/dist/runtime/components/LibPopup/LibPopup.vue +30 -66
  50. package/dist/runtime/components/LibProgressBar/LibProgressBar.vue +3 -1
  51. package/dist/runtime/components/LibRecorder/LibRecorder.vue +2 -2
  52. package/dist/runtime/components/LibRoot/LibRoot.vue +14 -1
  53. package/dist/runtime/components/LibSimpleInput/LibSimpleInput.stories.js +1 -1
  54. package/dist/runtime/components/LibSimpleInput/LibSimpleInput.vue +5 -7
  55. package/dist/runtime/components/LibSuggestions/LibSuggestions.vue +42 -25
  56. package/dist/runtime/components/LibTable/LibTable.vue +8 -8
  57. package/dist/runtime/components/Scrolling.stories.d.ts +6 -0
  58. package/dist/runtime/components/Scrolling.stories.js +44 -0
  59. package/dist/runtime/components/Template/NAME.vue +1 -1
  60. package/dist/runtime/components/TestControls/TestControls.vue +1 -1
  61. package/dist/runtime/components/index.d.ts +12 -11
  62. package/dist/runtime/components/index.js +12 -11
  63. package/dist/runtime/components/shared/props.d.ts +81 -16
  64. package/dist/runtime/components/shared/storyHelpers/playInput.js +5 -5
  65. package/dist/runtime/components/shared/storyHelpers/playSuggestions.js +15 -11
  66. package/dist/runtime/composables/index.d.ts +5 -0
  67. package/dist/runtime/composables/index.js +5 -0
  68. package/dist/runtime/composables/useDivideAttrs.js +1 -0
  69. package/dist/runtime/composables/useDragWithThreshold.d.ts +71 -0
  70. package/dist/runtime/composables/useDragWithThreshold.js +40 -0
  71. package/dist/runtime/composables/usePreHydrationValue.d.ts +12 -0
  72. package/dist/runtime/composables/usePreHydrationValue.js +15 -0
  73. package/dist/runtime/composables/useSetupI18n.d.ts +2 -0
  74. package/dist/runtime/composables/useSetupI18n.js +5 -1
  75. package/dist/runtime/composables/useSuggestions.d.ts +7 -5
  76. package/dist/runtime/composables/useSuggestions.js +94 -57
  77. package/dist/runtime/directives/vResizableCols.js +3 -1
  78. package/dist/runtime/helpers/NotificationHandler.d.ts +5 -0
  79. package/dist/runtime/helpers/index.d.ts +3 -1
  80. package/dist/runtime/helpers/index.js +3 -1
  81. package/dist/runtime/types/index.d.ts +6 -0
  82. package/dist/runtime/utils/notifyIfError.d.ts +14 -0
  83. package/dist/runtime/utils/notifyIfError.js +29 -0
  84. package/package.json +18 -20
  85. package/src/module.ts +31 -12
  86. package/src/runtime/assets/base.css +10 -1
  87. package/src/runtime/assets/locales/en.json +2 -2
  88. package/src/runtime/assets/tailwind.css +1 -1
  89. package/src/runtime/assets/{style.css → utils.css} +86 -4
  90. package/src/runtime/build/WitchcraftUiResolver.ts +1 -1
  91. package/src/runtime/components/Icon/Icon.vue +10 -5
  92. package/src/runtime/components/LibButton/LibButton.vue +41 -46
  93. package/src/runtime/components/LibCheckbox/LibCheckbox.vue +7 -3
  94. package/src/runtime/components/LibColorInput/LibColorInput.vue +111 -36
  95. package/src/runtime/components/LibColorPicker/LibColorPicker.stories.ts +25 -4
  96. package/src/runtime/components/LibColorPicker/LibColorPicker.vue +242 -131
  97. package/src/runtime/components/LibColorPicker/utils/safeConvertToHsva.ts +25 -0
  98. package/src/runtime/components/LibColorPicker/utils/safeConvertToRgba.ts +23 -0
  99. package/src/runtime/components/LibColorPicker/utils/toLowPrecisionRgbaString.ts +13 -0
  100. package/src/runtime/components/LibColorPicker/utils/truncate.ts +6 -0
  101. package/src/runtime/components/LibDarkModeSwitcher/LibDarkModeSwitcher.stories.ts +1 -1
  102. package/src/runtime/components/LibDarkModeSwitcher/LibDarkModeSwitcher.vue +11 -8
  103. package/src/runtime/components/LibDatePicker/LibDatePicker.vue +4 -17
  104. package/src/runtime/components/LibDatePicker/LibRangeDatePicker.vue +192 -131
  105. package/src/runtime/components/LibDatePicker/LibSingleDatePicker.vue +183 -115
  106. package/src/runtime/components/LibDatePicker/LibTimeZonePicker.vue +3 -3
  107. package/src/runtime/components/LibDebug/LibDebug.vue +15 -5
  108. package/src/runtime/components/LibDevOnly/LibDevOnly.vue +1 -3
  109. package/src/runtime/components/LibFileInput/LibFileInput.vue +54 -28
  110. package/src/runtime/components/{LibInput/LibInput.stories.ts → LibInputDeprecated/LibInputDeprecated.stories.ts} +64 -19
  111. package/{dist/runtime/components/LibInput/LibInput.vue → src/runtime/components/LibInputDeprecated/LibInputDeprecated.vue} +40 -33
  112. package/src/runtime/components/LibLabel/LibLabel.vue +2 -2
  113. package/src/runtime/components/LibMultiValues/LibMultiValues.stories.ts +5 -4
  114. package/src/runtime/components/LibMultiValues/LibMultiValues.vue +11 -12
  115. package/src/runtime/components/LibNotifications/LibNotification.vue +19 -10
  116. package/src/runtime/components/LibNotifications/LibNotifications.stories.ts +2 -2
  117. package/src/runtime/components/LibNotifications/LibNotifications.vue +20 -11
  118. package/src/runtime/components/LibPagination/LibPagination.stories.ts +2 -2
  119. package/src/runtime/components/LibPagination/LibPagination.vue +19 -19
  120. package/src/runtime/components/LibPalette/LibPalette.vue +3 -3
  121. package/src/runtime/components/LibPopup/LibPopup.stories.ts +2 -2
  122. package/src/runtime/components/LibPopup/LibPopup.vue +30 -66
  123. package/src/runtime/components/LibProgressBar/LibProgressBar.vue +3 -1
  124. package/src/runtime/components/LibRecorder/LibRecorder.vue +2 -2
  125. package/src/runtime/components/LibRoot/LibRoot.vue +14 -1
  126. package/src/runtime/components/LibSimpleInput/LibSimpleInput.stories.ts +1 -1
  127. package/src/runtime/components/LibSimpleInput/LibSimpleInput.vue +5 -7
  128. package/src/runtime/components/LibSuggestions/LibSuggestions.vue +42 -25
  129. package/src/runtime/components/LibTable/LibTable.vue +8 -8
  130. package/src/runtime/components/Scrolling.stories.ts +58 -0
  131. package/src/runtime/components/Template/NAME.vue +1 -1
  132. package/src/runtime/components/TestControls/TestControls.vue +1 -1
  133. package/src/runtime/components/index.ts +12 -12
  134. package/src/runtime/components/shared/props.ts +82 -19
  135. package/src/runtime/components/shared/storyHelpers/playInput.ts +6 -5
  136. package/src/runtime/components/shared/storyHelpers/playSuggestions.ts +25 -11
  137. package/src/runtime/composables/index.ts +5 -0
  138. package/src/runtime/composables/useDarkMode.ts +2 -2
  139. package/src/runtime/composables/useDivideAttrs.ts +1 -0
  140. package/src/runtime/composables/useDragWithThreshold.ts +108 -0
  141. package/src/runtime/composables/usePreHydrationValue.ts +30 -0
  142. package/src/runtime/composables/useSetupI18n.ts +8 -2
  143. package/src/runtime/composables/useSuggestions.ts +92 -45
  144. package/src/runtime/directives/vResizableCols.ts +3 -1
  145. package/src/runtime/helpers/NotificationHandler.ts +5 -0
  146. package/src/runtime/helpers/index.ts +3 -1
  147. package/src/runtime/types/index.ts +5 -0
  148. package/src/runtime/utils/notifyIfError.ts +45 -0
  149. package/dist/runtime/assets/style.css +0 -1
  150. package/dist/runtime/helpers/addValue.d.ts +0 -1
  151. package/dist/runtime/helpers/addValue.js +0 -8
  152. package/src/runtime/helpers/addValue.ts +0 -10
  153. /package/dist/runtime/components/{reset.stories.d.ts → Reset.stories.d.ts} +0 -0
  154. /package/dist/runtime/components/{reset.stories.js → Reset.stories.js} +0 -0
  155. /package/src/runtime/components/{reset.stories.ts → Reset.stories.ts} +0 -0
@@ -0,0 +1,71 @@
1
+ import { type Ref } from "vue";
2
+ import type { Point } from "../types/index.js.js";
3
+ /**
4
+ * A composable for dragging items only after a certain threshold of movement has been reached.
5
+ *
6
+ * What to do with the actual pointer coordinates is up to you.
7
+ *
8
+ * The is compatible with the `useScrollNearContainerEdges` composable as well.
9
+ *
10
+ * ```ts
11
+ * const {
12
+ * pointerCoords,
13
+ * passedDragThreshold,
14
+ * startDragThresholdCheck,
15
+ * endDragThresholdCheck,
16
+ * checkDragThreshold,
17
+ * } = useDragWithThreshold({ threshold: ref(5) })
18
+ *
19
+ * function grabPointerDown(e: PointerEvent): void {
20
+ * startDragThresholdCheck(e)
21
+ * e.preventDefault()
22
+ *
23
+ * document.addEventListener("pointermove", grabPointerMove)
24
+ * document.addEventListener("pointerup", grabPointerUp)
25
+ * document.addEventListener("keyup", escapeDrag)
26
+ * }
27
+ * function grabPointerMove(e: PointerEvent): void {
28
+ * e.preventDefault()
29
+ * checkDragThreshold(e)
30
+ * if (passedDragThreshold.value) {
31
+ *
32
+ * }
33
+ * }
34
+ * function grabPointerUp(e: PointerEvent): void {
35
+ * if (passedDragThreshold.value) {
36
+ * // drag
37
+ * } else {
38
+ * // handleAsClick(e)
39
+ * }
40
+ * stopDrag()
41
+ * }
42
+ *
43
+ * function stopDrag(): void {
44
+ * endDragThresholdCheck()
45
+ * document.removeEventListener("keyup", escapeDrag)
46
+ * document.removeEventListener("pointermove", grabPointerMove)
47
+ * document.removeEventListener("pointerup", grabPointerUp)
48
+ * }
49
+ *
50
+ * function escapeDrag(e: KeyboardEvent): void {
51
+ * if (e.code === "Escape") stopDrag()
52
+ * }
53
+ * ```
54
+ */
55
+ export declare const useDragWithThreshold: ({ initialOffset, pointerCoords, threshold, }?: {
56
+ initialOffset?: Ref<Point | undefined>;
57
+ pointerCoords?: Ref<Point | undefined>;
58
+ threshold?: Ref<number>;
59
+ }) => {
60
+ initialOffset: Ref<Point | undefined, Point | undefined>;
61
+ pointerCoords: Ref<Point | undefined, Point | undefined>;
62
+ threshold: Ref<number, number>;
63
+ passedDragThreshold: Ref<boolean, boolean>;
64
+ getEventCoords: (e: {
65
+ clientX: number;
66
+ clientY: number;
67
+ }) => Point;
68
+ checkDragThreshold: (e: PointerEvent) => void;
69
+ startDragThresholdCheck: (e: PointerEvent) => void;
70
+ endDragThresholdCheck: () => void;
71
+ };
@@ -0,0 +1,40 @@
1
+ import { ref } from "vue";
2
+ export const useDragWithThreshold = ({
3
+ initialOffset = ref({ x: 0, y: 0 }),
4
+ pointerCoords = ref({ x: 0, y: 0 }),
5
+ threshold = ref(10)
6
+ } = {}) => {
7
+ const passedDragThreshold = ref(false);
8
+ function getDistance(p1, p2) {
9
+ const xDiff = p2.x - p1.x;
10
+ const yDiff = p2.y - p1.y;
11
+ return Math.sqrt(xDiff * xDiff + yDiff * yDiff);
12
+ }
13
+ function getEventCoords(e) {
14
+ return { x: e.clientX, y: e.clientY };
15
+ }
16
+ function checkDragThreshold(e) {
17
+ pointerCoords.value = getEventCoords(e);
18
+ passedDragThreshold.value ||= initialOffset.value !== void 0 && getDistance(initialOffset.value, pointerCoords.value) >= threshold.value;
19
+ }
20
+ function startDragThresholdCheck(e) {
21
+ passedDragThreshold.value = false;
22
+ initialOffset.value = getEventCoords(e);
23
+ pointerCoords.value = getEventCoords(e);
24
+ }
25
+ function endDragThresholdCheck() {
26
+ passedDragThreshold.value = false;
27
+ initialOffset.value = void 0;
28
+ pointerCoords.value = void 0;
29
+ }
30
+ return {
31
+ initialOffset,
32
+ pointerCoords,
33
+ threshold,
34
+ passedDragThreshold,
35
+ getEventCoords,
36
+ checkDragThreshold,
37
+ startDragThresholdCheck,
38
+ endDragThresholdCheck
39
+ };
40
+ };
@@ -0,0 +1,12 @@
1
+ import { type Ref } from "vue";
2
+ /**
3
+ *
4
+ * This takes the value of the element at the given id (onBeforeMount, i.e. pre-hydration if available), and sets the given ref to that value onMounted.
5
+ *
6
+ * This should be compatible with both nuxt and vue.
7
+ *
8
+ * Nore that while it's generic, you will need to provide a transform function to convert the string to the desired type.
9
+ *
10
+ * @experimental
11
+ */
12
+ export declare function usePreHydrationValue<T>(id: string, refVal: Ref<T>, transform?: (val: string) => T): void;
@@ -0,0 +1,15 @@
1
+ import { onBeforeMount, onMounted } from "vue";
2
+ export function usePreHydrationValue(id, refVal, transform = (val) => val) {
3
+ let temp = "";
4
+ onBeforeMount(() => {
5
+ const el = document.getElementById(id);
6
+ if (el?.value) {
7
+ temp = el.value;
8
+ }
9
+ });
10
+ onMounted(() => {
11
+ if (temp) {
12
+ refVal.value = transform(temp);
13
+ }
14
+ });
15
+ }
@@ -6,6 +6,8 @@ export type TranslationFunction = (key: string, replacements?: Record<string, an
6
6
  * Should be called only once. You can choose to await it or not (see the `useDummyMessageSetWhileLoading` option).
7
7
  *
8
8
  * A default function is available, see {@link defaultTranslationFunction}.
9
+ *
10
+ * To avoid hydration errors, on the server, the message loading is awaited.
9
11
  */
10
12
  export declare function useSetupI18n({ locale, useBuiltinTranslations, useDummyMessageSetWhileLoading, translationFunction, }: {
11
13
  locale: Ref<string>;
@@ -40,7 +40,11 @@ export async function useSetupI18n({
40
40
  messages.value = newMessages;
41
41
  }
42
42
  }
43
- void loadMessageSet(locale.value);
43
+ if (import.meta.server) {
44
+ await loadMessageSet(locale.value);
45
+ } else {
46
+ void loadMessageSet(locale.value);
47
+ }
44
48
  watch(locale, async () => {
45
49
  void loadMessageSet(locale.value);
46
50
  });
@@ -1,3 +1,4 @@
1
+ import { type AnyFunction } from "@alanscodelog/utils";
1
2
  import { type Ref } from "vue";
2
3
  import { type SuggestionsEmits, type SuggestionsOptions } from "../components/shared/props.js.js";
3
4
  /**
@@ -5,7 +6,7 @@ import { type SuggestionsEmits, type SuggestionsOptions } from "../components/sh
5
6
  *
6
7
  * Note that while object suggestions are supported, the `suggestionLabel` prop is required and $inputModel and $modelValue will still be string values (as returned by the suggestionLabel function).
7
8
  */
8
- export declare function useSuggestions<TSuggestion>($inputValue: Ref<string>, $modelValue: Ref<string>, emit: SuggestionsEmits, opts: SuggestionsOptions<TSuggestion>, debug?: boolean): {
9
+ export declare function useSuggestions<TSuggestion, TMultivalue extends boolean = false>($inputValue: Ref<string>, $modelValue: Ref<TMultivalue extends true ? string[] : string>, $open: Ref<boolean>, emit: SuggestionsEmits, opts: SuggestionsOptions<TSuggestion>, debug?: boolean): {
9
10
  list: import("vue").ComputedRef<any[] | undefined>;
10
11
  filtered: import("vue").ComputedRef<any[] | undefined>;
11
12
  active: Ref<number, number>;
@@ -16,11 +17,11 @@ export declare function useSuggestions<TSuggestion>($inputValue: Ref<string>, $m
16
17
  hasValidSuggestion: import("vue").ComputedRef<boolean>;
17
18
  openable: import("vue").ComputedRef<boolean | undefined>;
18
19
  getLabel: (item: any) => string;
19
- isOpen: Ref<boolean, boolean>;
20
+ $open: Ref<boolean, boolean>;
20
21
  open: () => void;
21
22
  close: () => void;
22
- enterSelected: () => void;
23
- enterSuggestion: (num: number) => void;
23
+ enterSelected: (doClose?: boolean) => void;
24
+ enterIndex: (num: number, doClose?: boolean) => void;
24
25
  toggle: () => void;
25
26
  cancel: () => void;
26
27
  select: (num: number) => void;
@@ -29,10 +30,11 @@ export declare function useSuggestions<TSuggestion>($inputValue: Ref<string>, $m
29
30
  first: () => void;
30
31
  last: () => void;
31
32
  };
32
- export declare function useSuggestionsInputAria(id: Ref<string>, isOpen: Ref<boolean>, activeSuggestion: Ref<number>, suggestions: Ref<any | undefined>): import("vue").ComputedRef<{
33
+ export declare function useSuggestionsInputAria(id: Ref<string>, $open: Ref<boolean>, activeSuggestion: Ref<number>, suggestions: Ref<any | undefined>): import("vue").ComputedRef<{
33
34
  "aria-autocomplete": "both" | undefined;
34
35
  "aria-controls": string | undefined;
35
36
  role: string | undefined;
36
37
  "aria-expanded": boolean | undefined;
37
38
  "aria-activedescendant": string | undefined;
38
39
  }>;
40
+ export declare function suggestionLabelGuard<TFunction extends AnyFunction>(item: any, suggestionLabeler: TFunction | undefined): asserts suggestionLabeler is TFunction;
@@ -1,33 +1,42 @@
1
1
  import { isBlank } from "@alanscodelog/utils/isBlank.js";
2
2
  import { isObject } from "@alanscodelog/utils/isObject.js";
3
+ import { pushIfNotIn } from "@alanscodelog/utils/pushIfNotIn.js";
4
+ import { removeIfIn } from "@alanscodelog/utils/removeIfIn.js";
3
5
  import { computed, ref, toRaw, watch } from "vue";
4
- export function useSuggestions($inputValue, $modelValue, emit, opts, debug = false) {
6
+ export function useSuggestions($inputValue, $modelValue, $open, emit, opts, debug = false) {
5
7
  if (typeof opts.suggestions?.[0] === "object" && !opts.suggestionLabel && !opts.suggestionsFilter) {
6
8
  throw new Error("`suggestionLabel` or `suggestionsFilter` must be passed if suggestions are objects.");
7
9
  }
8
- const isOpen = ref(false);
9
10
  const activeSuggestion = ref(-1);
10
- watch(isOpen, (val) => {
11
- emit("update:isOpen", val);
12
- });
13
11
  watch(activeSuggestion, (val) => {
14
12
  emit("update:activeSuggestion", val);
15
13
  });
16
- const suggestionKey = (item) => {
14
+ if (opts.suggestions) {
15
+ for (const suggestion of opts.suggestions) {
16
+ suggestionLabelGuard(suggestion, opts.suggestionLabel);
17
+ }
18
+ }
19
+ const getSuggestionLabel = (item) => {
20
+ suggestionLabelGuard(item, opts.suggestionLabel);
17
21
  if (isObject(item)) {
18
- if (opts.suggestionLabel) {
19
- return opts.suggestionLabel(item);
20
- }
21
- throw new Error("`suggestionLabel` must be passed if suggestions are objects.");
22
+ return opts.suggestionLabel(item);
22
23
  }
23
24
  return item;
24
25
  };
25
- const getSuggestionLabel = (item) => opts.suggestionLabel?.(item) ?? suggestionKey(item) ?? "";
26
- const defaultSuggestionsFilter = (input, items) => input === "" ? [...items] : items.filter((item) => getSuggestionLabel(item).toLowerCase().includes(input.toLowerCase()));
26
+ const defaultSuggestionsFilter = (input, items) => input === "" ? [...items] : items.filter((item) => {
27
+ if (Array.isArray($modelValue.value)) {
28
+ if ($modelValue.value.includes(getSuggestionLabel(item))) return true;
29
+ }
30
+ return getSuggestionLabel(item).toLowerCase().includes(input.toLowerCase());
31
+ });
27
32
  const suggestionsFilter = computed(() => opts.suggestionsFilter ?? defaultSuggestionsFilter);
28
33
  const suggestionsList = computed(() => {
29
34
  if (opts.suggestions) {
30
- const res = suggestionsFilter.value($inputValue.value, opts.suggestions);
35
+ const suggestions = [...opts.suggestions];
36
+ if (Array.isArray($modelValue.value) && !opts.showSelectedValues) {
37
+ pushIfNotIn(suggestions, $modelValue.value);
38
+ }
39
+ const res = suggestionsFilter.value($inputValue.value, suggestions);
31
40
  return res;
32
41
  }
33
42
  return void 0;
@@ -43,19 +52,30 @@ export function useSuggestions($inputValue, $modelValue, emit, opts, debug = fal
43
52
  if (opts.suggestions) {
44
53
  const res = suggestionAvailable.value ? suggestionsList.value : opts.suggestions;
45
54
  if (opts.restrictToSuggestions && !isValidSuggestion.value) return res;
46
- if (opts.preventDuplicateValues && opts.values) {
47
- return res.filter((suggestion) => !opts.values.includes(suggestionKey(suggestion)));
48
- }
49
55
  return res;
50
56
  }
51
57
  return void 0;
52
58
  });
53
- const closeSuggestions = () => {
59
+ function setValue(val) {
60
+ if (Array.isArray($modelValue.value)) {
61
+ if ($modelValue.value.includes(val)) {
62
+ removeIfIn($modelValue.value, val);
63
+ return true;
64
+ } else {
65
+ pushIfNotIn($modelValue.value, [val]);
66
+ }
67
+ } else {
68
+ $modelValue.value = val;
69
+ }
70
+ return false;
71
+ }
72
+ function closeSuggestions() {
73
+ if (!opts.canClose) return;
54
74
  if (debug) console.log("closeSuggestions");
55
- isOpen.value = false;
75
+ $open.value = false;
56
76
  activeSuggestion.value = -1;
57
- };
58
- const openSuggestions = () => {
77
+ }
78
+ function openSuggestions() {
59
79
  if (debug) console.log("openSuggestions", { openable: openable.value });
60
80
  if (!openable.value) return;
61
81
  if (activeSuggestion.value === -1) {
@@ -65,20 +85,22 @@ export function useSuggestions($inputValue, $modelValue, emit, opts, debug = fal
65
85
  activeSuggestion.value = 0;
66
86
  }
67
87
  }
68
- isOpen.value = true;
69
- };
70
- function enterSuggestion(num) {
88
+ $open.value = true;
89
+ }
90
+ function enterSuggestion(num, doClose = true) {
71
91
  if (num < -1 || num > (filteredSuggestions.value?.length ?? 0)) return;
72
92
  if (debug) console.log("enterSuggestion", num);
73
93
  if (filteredSuggestions.value === void 0) return;
74
94
  const suggestion = filteredSuggestions.value[num];
75
- const val = suggestionKey(suggestion);
76
- $modelValue.value = val;
77
- $inputValue.value = getSuggestionLabel(suggestion);
78
- closeSuggestions();
79
- emit("submit", val, toRaw(suggestion));
95
+ const val = getSuggestionLabel(suggestion);
96
+ const wasRemoved = setValue(val);
97
+ $inputValue.value = Array.isArray($modelValue.value) ? "" : getSuggestionLabel(suggestion);
98
+ if (doClose) {
99
+ closeSuggestions();
100
+ }
101
+ emit("submit", val, toRaw(suggestion), wasRemoved);
80
102
  }
81
- const enterSelected = () => {
103
+ function enterSelected(doClose = true) {
82
104
  if (activeSuggestion.value === -1) {
83
105
  if (!opts.restrictToSuggestions) {
84
106
  if (debug) console.log("enterSelected, unrestricted, emitting submit");
@@ -89,9 +111,9 @@ export function useSuggestions($inputValue, $modelValue, emit, opts, debug = fal
89
111
  return;
90
112
  }
91
113
  if (debug) console.log("enterSelected");
92
- enterSuggestion(activeSuggestion.value);
93
- };
94
- const selectSuggestion = (num) => {
114
+ enterSuggestion(activeSuggestion.value, doClose);
115
+ }
116
+ function selectSuggestion(num) {
95
117
  if (debug) console.log("selectSuggestion", num);
96
118
  if (num >= -1) {
97
119
  activeSuggestion.value = num;
@@ -99,37 +121,41 @@ export function useSuggestions($inputValue, $modelValue, emit, opts, debug = fal
99
121
  if (num === Infinity && (filteredSuggestions.value?.length ?? 0) > 0) {
100
122
  activeSuggestion.value = filteredSuggestions.value.length - 1;
101
123
  }
102
- };
103
- const toggleSuggestions = () => {
104
- isOpen.value ? closeSuggestions() : openSuggestions();
105
- };
106
- const prevSuggestion = () => {
124
+ }
125
+ function toggleSuggestions() {
126
+ $open.value ? closeSuggestions() : openSuggestions();
127
+ }
128
+ function prevSuggestion() {
107
129
  if (!filteredSuggestions.value) return;
108
130
  if (activeSuggestion.value > 0) {
109
131
  activeSuggestion.value--;
110
132
  } else if (filteredSuggestions.value) {
111
133
  activeSuggestion.value = filteredSuggestions.value.length - 1;
112
134
  }
113
- };
114
- const nextSuggestion = () => {
135
+ }
136
+ function nextSuggestion() {
115
137
  if (!filteredSuggestions.value) return;
116
138
  if (activeSuggestion.value >= filteredSuggestions.value.length - 1) {
117
139
  activeSuggestion.value = 0;
118
140
  } else {
119
141
  activeSuggestion.value++;
120
142
  }
121
- };
122
- const firstSuggestion = () => {
143
+ }
144
+ function firstSuggestion() {
123
145
  selectSuggestion(0);
124
- };
125
- const lastSuggestion = () => {
146
+ }
147
+ function lastSuggestion() {
126
148
  selectSuggestion(Infinity);
127
- };
128
- const cancel = () => {
149
+ }
150
+ function cancel() {
151
+ if (Array.isArray($modelValue.value)) {
152
+ $inputValue.value = "";
153
+ return;
154
+ }
129
155
  if (debug) console.log("cancel");
130
156
  $inputValue.value = getSuggestionLabel($modelValue.value);
131
157
  closeSuggestions();
132
- };
158
+ }
133
159
  watch(() => opts.canOpen, (val) => {
134
160
  if (!val) {
135
161
  if (debug) console.log("canOpen changed to false, closing suggestions");
@@ -149,10 +175,14 @@ export function useSuggestions($inputValue, $modelValue, emit, opts, debug = fal
149
175
  }
150
176
  });
151
177
  watch($modelValue, () => {
152
- $inputValue.value = getSuggestionLabel($modelValue.value);
178
+ if (Array.isArray($modelValue.value)) {
179
+ $inputValue.value = "";
180
+ } else {
181
+ $inputValue.value = getSuggestionLabel($modelValue.value);
182
+ }
153
183
  if (debug) console.log("modelValue changed");
154
184
  });
155
- const defaultSuggestionSelector = (suggestions, input) => {
185
+ function defaultSuggestionSelector(suggestions, input) {
156
186
  if (input.length === 0) return 0;
157
187
  let longestMatch;
158
188
  let ii = -1;
@@ -168,17 +198,17 @@ export function useSuggestions($inputValue, $modelValue, emit, opts, debug = fal
168
198
  }
169
199
  }
170
200
  return ii;
171
- };
201
+ }
172
202
  watch($inputValue, () => {
173
203
  if (debug) console.log("input changed:", $inputValue.value, "modelValue:", $modelValue.value);
174
- if (getSuggestionLabel($modelValue.value) === $inputValue.value) return;
204
+ if (!Array.isArray($modelValue.value) && getSuggestionLabel($modelValue.value) === $inputValue.value) return;
175
205
  if (suggestionAvailable.value) {
176
206
  if (debug) console.log("input changed, suggestion available, opening suggestions");
177
207
  openSuggestions();
178
208
  }
179
- if (!opts.restrictToSuggestions) {
209
+ if (!opts.restrictToSuggestions && !Array.isArray($modelValue.value)) {
180
210
  if (debug) console.log("input changed, unrestricted, setting modelValue");
181
- $modelValue.value = $inputValue.value;
211
+ setValue($inputValue.value);
182
212
  }
183
213
  if (exactlyMatchingSuggestion.value && suggestionsList.value) {
184
214
  if (debug) console.log("input changed, exactly matching, setting activeSuggestion");
@@ -200,11 +230,11 @@ export function useSuggestions($inputValue, $modelValue, emit, opts, debug = fal
200
230
  hasValidSuggestion: isValidSuggestion,
201
231
  openable,
202
232
  getLabel: getSuggestionLabel,
203
- isOpen,
233
+ $open,
204
234
  open: openSuggestions,
205
235
  close: closeSuggestions,
206
236
  enterSelected,
207
- enterSuggestion,
237
+ enterIndex: enterSuggestion,
208
238
  toggle: toggleSuggestions,
209
239
  cancel,
210
240
  select: selectSuggestion,
@@ -214,13 +244,20 @@ export function useSuggestions($inputValue, $modelValue, emit, opts, debug = fal
214
244
  last: lastSuggestion
215
245
  };
216
246
  }
217
- export function useSuggestionsInputAria(id, isOpen, activeSuggestion, suggestions) {
247
+ export function useSuggestionsInputAria(id, $open, activeSuggestion, suggestions) {
218
248
  const ariaInputProps = computed(() => ({
219
249
  "aria-autocomplete": suggestions !== void 0 ? "both" : void 0,
220
250
  "aria-controls": suggestions !== void 0 ? `suggestions-${id.value}` : void 0,
221
251
  role: suggestions ? "combobox" : void 0,
222
- "aria-expanded": suggestions !== void 0 ? isOpen.value : void 0,
223
- "aria-activedescendant": isOpen.value ? `suggestion-${id.value}-${activeSuggestion.value}` : void 0
252
+ "aria-expanded": suggestions !== void 0 ? $open.value : void 0,
253
+ "aria-activedescendant": $open.value ? `suggestion-${id.value}-${activeSuggestion.value}` : void 0
224
254
  }));
225
255
  return ariaInputProps;
226
256
  }
257
+ export function suggestionLabelGuard(item, suggestionLabeler) {
258
+ if (isObject(item)) {
259
+ if (!suggestionLabeler) {
260
+ throw new Error("`suggestionLabel` must be passed if suggestions are objects.");
261
+ }
262
+ }
263
+ }
@@ -213,7 +213,9 @@ const positionGrips = (el) => {
213
213
  const $el = getElInfo(el);
214
214
  for (const grip of $el.grips.keys()) {
215
215
  const col = $el.grips.get(grip);
216
- const colBox = getBox(getColEls(el)[col]);
216
+ const colEls = getColEls(el)[col];
217
+ if (!colEls) unreachable();
218
+ const colBox = getBox(colEls);
217
219
  const gripBox = getBox(grip);
218
220
  grip.style.left = `${xPos + colBox.width - gripBox.width / 2}px`;
219
221
  xPos += colBox.width;
@@ -28,11 +28,16 @@ export type RawNotificationEntry<TOptions extends string[] = ["Ok", "Cancel"], T
28
28
  message: string;
29
29
  title?: string;
30
30
  code?: string;
31
+ /** @default ["Ok", "Cancel"] */
31
32
  options?: TOptions;
33
+ /** @default false */
32
34
  requiresAction?: boolean;
33
35
  cancellable?: TCancellable;
36
+ /** @default "Ok" */
34
37
  default?: TOptions[number];
38
+ /** @default [] */
35
39
  dangerous?: TOptions[number][];
40
+ /** @default false if cancellable, otherwise the default timeout */
36
41
  timeout?: number | boolean;
37
42
  icon?: string;
38
43
  };
@@ -1,7 +1,9 @@
1
- export { addValue } from "./addValue.js.js";
2
1
  export { base64ToImg } from "./base64ToImg.js.js";
3
2
  export { copy } from "./copy.js.js";
3
+ export { createNoonUtcDate } from "./createNoonUtcDate.js.js";
4
4
  export { createRecorderHandler } from "./storybook.js.js";
5
+ export { defaultTranslationFunction } from "./defaultTranslationFunction.js.js";
6
+ export { getTimeZoneList } from "./getTimeZoneList.js.js";
5
7
  export { hasModifiers } from "./hasModifiers.js.js";
6
8
  export { NotificationHandler } from "./NotificationHandler.js.js";
7
9
  export { readFile } from "./readFile.js.js";
@@ -1,7 +1,9 @@
1
- export { addValue } from "./addValue.js";
2
1
  export { base64ToImg } from "./base64ToImg.js";
3
2
  export { copy } from "./copy.js";
3
+ export { createNoonUtcDate } from "./createNoonUtcDate.js";
4
4
  export { createRecorderHandler } from "./storybook.js";
5
+ export { defaultTranslationFunction } from "./defaultTranslationFunction.js";
6
+ export { getTimeZoneList } from "./getTimeZoneList.js";
5
7
  export { hasModifiers } from "./hasModifiers.js";
6
8
  export { NotificationHandler } from "./NotificationHandler.js";
7
9
  export { readFile } from "./readFile.js";
@@ -53,18 +53,24 @@ export type FileInputError = ErrorW<{
53
53
  isValidMimeType: boolean;
54
54
  isValidExtension: boolean;
55
55
  }>;
56
+ /** h 0-360+ (deg), s 0-100%, v 0-100%, a 0-1 */
56
57
  export type HsvaColor = {
57
58
  h: number;
58
59
  s: number;
59
60
  v: number;
60
61
  a?: number;
61
62
  };
63
+ /** rgb 0-255, 0-1 for alpha */
62
64
  export type RgbaColor = {
63
65
  r: number;
64
66
  g: number;
65
67
  b: number;
66
68
  a?: number;
67
69
  };
70
+ export type Point = {
71
+ x: number;
72
+ y: number;
73
+ };
68
74
  export type ScrollNearContainerEdgesOptions = {
69
75
  containerEl: Ref<HTMLElement | null>;
70
76
  /** Margin inside contianer that allows scrolling. 10 by default. */
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Notifies the user if the given value is an error. Useful for making non-critical errors don't go unnoticed.
3
+ *
4
+ * Integrates with {@link TypedError} from `@alanscodelog/utils` to display the error code as well if it has one.
5
+ *
6
+ * If the value is not an error, it is returned.
7
+ */
8
+ export declare function notifyIfError<T>(err: T, { logger, ns, force, }?: {
9
+ logger?: {
10
+ debug: (...args: any[]) => void;
11
+ };
12
+ ns?: string;
13
+ force?: boolean;
14
+ }): T;
@@ -0,0 +1,29 @@
1
+ import { TypedError } from "@alanscodelog/utils/TypedError.js";
2
+ import { useNotificationHandler } from "../composables/useNotificationHandler.js";
3
+ export function notifyIfError(err, {
4
+ logger,
5
+ ns,
6
+ force = false
7
+ } = {}) {
8
+ if (force || err instanceof Error) {
9
+ const errMessage = {
10
+ message: err instanceof Error ? err.message : `Unknown error ${err}`,
11
+ code: err instanceof TypedError ? err.code : void 0
12
+ };
13
+ if (logger) {
14
+ logger.debug({
15
+ ns,
16
+ ...errMessage,
17
+ stack: err instanceof Error ? err.stack : void 0
18
+ });
19
+ }
20
+ void useNotificationHandler().notify({
21
+ title: "Error",
22
+ ...errMessage,
23
+ options: ["Ok"],
24
+ cancellable: "Ok",
25
+ timeout: true
26
+ });
27
+ }
28
+ return err;
29
+ }