orio-ui 1.16.2 → 1.18.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 (52) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/assets/css/variables.css +1 -1
  3. package/dist/runtime/components/Badge.d.vue.ts +2 -0
  4. package/dist/runtime/components/Badge.vue +13 -4
  5. package/dist/runtime/components/Badge.vue.d.ts +2 -0
  6. package/dist/runtime/components/Button.d.vue.ts +2 -1
  7. package/dist/runtime/components/Button.vue +11 -5
  8. package/dist/runtime/components/Button.vue.d.ts +2 -1
  9. package/dist/runtime/components/CheckBox.d.vue.ts +2 -1
  10. package/dist/runtime/components/CheckBox.vue +11 -3
  11. package/dist/runtime/components/CheckBox.vue.d.ts +2 -1
  12. package/dist/runtime/components/ControlElement.vue +21 -11
  13. package/dist/runtime/components/Icon.d.vue.ts +0 -1
  14. package/dist/runtime/components/Icon.vue +7 -14
  15. package/dist/runtime/components/Icon.vue.d.ts +0 -1
  16. package/dist/runtime/components/Input.d.vue.ts +2 -2
  17. package/dist/runtime/components/Input.vue +14 -6
  18. package/dist/runtime/components/Input.vue.d.ts +2 -2
  19. package/dist/runtime/components/NavButton.d.vue.ts +2 -1
  20. package/dist/runtime/components/NavButton.vue +11 -5
  21. package/dist/runtime/components/NavButton.vue.d.ts +2 -1
  22. package/dist/runtime/components/NumberInput/Horizontal.d.vue.ts +1 -1
  23. package/dist/runtime/components/NumberInput/Horizontal.vue +7 -1
  24. package/dist/runtime/components/NumberInput/Horizontal.vue.d.ts +1 -1
  25. package/dist/runtime/components/NumberInput/Vertical.d.vue.ts +1 -1
  26. package/dist/runtime/components/NumberInput/Vertical.vue +7 -1
  27. package/dist/runtime/components/NumberInput/Vertical.vue.d.ts +1 -1
  28. package/dist/runtime/components/NumberInput/index.d.vue.ts +3 -2
  29. package/dist/runtime/components/NumberInput/index.vue +23 -3
  30. package/dist/runtime/components/NumberInput/index.vue.d.ts +3 -2
  31. package/dist/runtime/components/Popover.d.vue.ts +22 -51
  32. package/dist/runtime/components/Popover.vue +37 -65
  33. package/dist/runtime/components/Popover.vue.d.ts +22 -51
  34. package/dist/runtime/components/RadioButton.d.vue.ts +3 -2
  35. package/dist/runtime/components/RadioButton.vue +12 -5
  36. package/dist/runtime/components/RadioButton.vue.d.ts +3 -2
  37. package/dist/runtime/components/Selector.d.vue.ts +2 -1
  38. package/dist/runtime/components/Selector.vue +68 -10
  39. package/dist/runtime/components/Selector.vue.d.ts +2 -1
  40. package/dist/runtime/components/SwitchButton.d.vue.ts +2 -1
  41. package/dist/runtime/components/SwitchButton.vue +10 -2
  42. package/dist/runtime/components/SwitchButton.vue.d.ts +2 -1
  43. package/dist/runtime/components/Textarea.d.vue.ts +2 -1
  44. package/dist/runtime/components/Textarea.vue +10 -7
  45. package/dist/runtime/components/Textarea.vue.d.ts +2 -1
  46. package/dist/runtime/components/view/Text.d.vue.ts +1 -1
  47. package/dist/runtime/components/view/Text.vue.d.ts +1 -1
  48. package/dist/runtime/composables/useListKeyboard.d.ts +19 -0
  49. package/dist/runtime/composables/useListKeyboard.js +59 -0
  50. package/dist/runtime/composables/useValidation.d.ts +3 -3
  51. package/dist/runtime/composables/useValidation.js +5 -6
  52. package/package.json +1 -1
@@ -1,3 +1,20 @@
1
+ type PopoverPosition = "top" | "bottom" | "left" | "right" | "top-left" | "top-right" | "bottom-left" | "bottom-right" | "left-top" | "left-bottom" | "right-top" | "right-bottom";
2
+ interface PopoverProps {
3
+ /**
4
+ * Defines where the popover is placed relative to the trigger.
5
+ * Acceptable single values: 'top', 'bottom', 'left', 'right'
6
+ * Acceptable combos: 'top-left', 'top-right', 'bottom-left', 'bottom-right',
7
+ * 'left-top', 'left-bottom', 'right-top', 'right-bottom'
8
+ * If you only provide 'top', 'bottom', 'left', or 'right',
9
+ * it aligns center by default.
10
+ */
11
+ position?: PopoverPosition;
12
+ /**
13
+ * Distance (in px) between the popover and the trigger element.
14
+ */
15
+ offset?: number;
16
+ disabled?: boolean;
17
+ }
1
18
  /**
2
19
  * Toggles popover visibility.
3
20
  * @param {boolean|null} force - If `true`/`false`, force that state; if `null`, toggle.
@@ -5,67 +22,21 @@
5
22
  declare function togglePopover(force?: boolean | null): Promise<void>;
6
23
  declare var __VLS_1: {
7
24
  toggle: typeof togglePopover;
25
+ isOpen: boolean;
8
26
  }, __VLS_15: {
9
27
  toggle: typeof togglePopover;
28
+ isOpen: true;
10
29
  };
11
30
  type __VLS_Slots = {} & {
12
31
  default?: (props: typeof __VLS_1) => any;
13
32
  } & {
14
33
  content?: (props: typeof __VLS_15) => any;
15
34
  };
16
- declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
17
- /**
18
- * Defines where the popover is placed relative to the trigger.
19
- * Acceptable single values: 'top', 'bottom', 'left', 'right'
20
- * Acceptable combos: 'top-left', 'top-right', 'bottom-left', 'bottom-right',
21
- * 'left-top', 'left-bottom', 'right-top', 'right-bottom'
22
- * If you only provide 'top', 'bottom', 'left', or 'right',
23
- * it aligns center by default.
24
- */
25
- position: {
26
- type: StringConstructor;
27
- default: string;
28
- };
29
- /**
30
- * Distance (in px) between the popover and the trigger element.
31
- */
32
- offset: {
33
- type: NumberConstructor;
34
- default: number;
35
- };
36
- disabled: {
37
- type: BooleanConstructor;
38
- default: boolean;
39
- };
40
- }>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
41
- /**
42
- * Defines where the popover is placed relative to the trigger.
43
- * Acceptable single values: 'top', 'bottom', 'left', 'right'
44
- * Acceptable combos: 'top-left', 'top-right', 'bottom-left', 'bottom-right',
45
- * 'left-top', 'left-bottom', 'right-top', 'right-bottom'
46
- * If you only provide 'top', 'bottom', 'left', or 'right',
47
- * it aligns center by default.
48
- */
49
- position: {
50
- type: StringConstructor;
51
- default: string;
52
- };
53
- /**
54
- * Distance (in px) between the popover and the trigger element.
55
- */
56
- offset: {
57
- type: NumberConstructor;
58
- default: number;
59
- };
60
- disabled: {
61
- type: BooleanConstructor;
62
- default: boolean;
63
- };
64
- }>> & Readonly<{}>, {
35
+ declare const __VLS_base: import("vue").DefineComponent<PopoverProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<PopoverProps> & Readonly<{}>, {
65
36
  disabled: boolean;
66
- position: string;
37
+ position: PopoverPosition;
67
38
  offset: number;
68
- }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
39
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
69
40
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
70
41
  declare const _default: typeof __VLS_export;
71
42
  export default _default;
@@ -1,18 +1,18 @@
1
1
  <template>
2
2
  <div>
3
- <div ref="trigger">
4
- <slot :toggle="togglePopover" />
3
+ <div ref="triggerRef">
4
+ <slot :toggle="togglePopover" :is-open="showPopover" />
5
5
  </div>
6
6
 
7
7
  <Teleport to="body">
8
8
  <Transition name="animate-fade-slide" appear>
9
9
  <div
10
10
  v-if="showPopover"
11
- ref="popover"
11
+ ref="containerRef"
12
12
  class="popover"
13
13
  :style="popoverStyle"
14
14
  >
15
- <slot name="content" :toggle="togglePopover" />
15
+ <slot name="content" :toggle="togglePopover" :is-open="showPopover" />
16
16
  </div>
17
17
  </Transition>
18
18
  </Teleport>
@@ -20,43 +20,20 @@
20
20
  </template>
21
21
 
22
22
  <script setup>
23
+ import { ref, computed, nextTick, useTemplateRef, watch } from "vue";
23
24
  import {
24
- ref,
25
- computed,
26
- nextTick,
27
- onMounted,
28
- onBeforeUnmount,
29
- watch
30
- } from "vue";
31
- import { useElementBounding } from "@vueuse/core";
25
+ useElementBounding,
26
+ onClickOutside,
27
+ useEventListener
28
+ } from "@vueuse/core";
32
29
  const props = defineProps({
33
- /**
34
- * Defines where the popover is placed relative to the trigger.
35
- * Acceptable single values: 'top', 'bottom', 'left', 'right'
36
- * Acceptable combos: 'top-left', 'top-right', 'bottom-left', 'bottom-right',
37
- * 'left-top', 'left-bottom', 'right-top', 'right-bottom'
38
- * If you only provide 'top', 'bottom', 'left', or 'right',
39
- * it aligns center by default.
40
- */
41
- position: {
42
- type: String,
43
- default: "bottom-left"
44
- },
45
- /**
46
- * Distance (in px) between the popover and the trigger element.
47
- */
48
- offset: {
49
- type: Number,
50
- default: 10
51
- },
52
- disabled: {
53
- type: Boolean,
54
- default: false
55
- }
30
+ position: { type: String, required: false, default: "bottom-left" },
31
+ offset: { type: Number, required: false, default: 10 },
32
+ disabled: { type: Boolean, required: false, default: false }
56
33
  });
57
- const trigger = ref(null);
58
- const popover = ref(null);
59
- const { width: popoverWidth, height: popoverHeight } = useElementBounding(popover);
34
+ const triggerRef = useTemplateRef("triggerRef");
35
+ const containerRef = useTemplateRef("containerRef");
36
+ const { width: popoverWidth, height: popoverHeight } = useElementBounding(containerRef);
60
37
  const showPopover = ref(false);
61
38
  const triggerRect = ref(null);
62
39
  const popoverRect = ref(null);
@@ -96,7 +73,7 @@ const popoverStyle = computed(() => {
96
73
  });
97
74
  const currentPosition = ref(props.position);
98
75
  const getFallbackPositions = (pos) => {
99
- const [main, sub = "center"] = pos.split("-");
76
+ const [main = "", sub = "center"] = pos.split("-");
100
77
  const opposites = {
101
78
  top: "bottom",
102
79
  bottom: "top",
@@ -107,15 +84,19 @@ const getFallbackPositions = (pos) => {
107
84
  `${main}-${sub}`,
108
85
  `${opposites[main]}-${sub}`,
109
86
  `${main}-center`,
110
- `${opposites[main]}-center`,
111
- `${sub}-${main}`,
112
- // e.g. left-top
113
- `${sub}-${opposites[main]}`
87
+ `${opposites[main]}-center`
114
88
  ];
89
+ if (sub !== "center") {
90
+ allPositions.push(
91
+ `${sub}-${main}`,
92
+ // e.g. left-top
93
+ `${sub}-${opposites[main]}`
94
+ );
95
+ }
115
96
  return [...new Set(allPositions)];
116
97
  };
117
98
  function checkIfFits(position, tRect, pRect, offset) {
118
- const [main, sub = "center"] = position.split("-");
99
+ const [main] = position.split("-");
119
100
  const space = {
120
101
  top: tRect.top,
121
102
  bottom: window.innerHeight - tRect.bottom,
@@ -130,15 +111,14 @@ function checkIfFits(position, tRect, pRect, offset) {
130
111
  }
131
112
  async function updateRects() {
132
113
  await nextTick();
133
- const triggerEl = trigger.value;
134
- const popoverEl = popover.value?.firstElementChild || popover.value;
114
+ const triggerEl = triggerRef.value;
115
+ const popoverEl = containerRef.value;
135
116
  if (!triggerEl || !popoverEl) return;
136
117
  const tRect = triggerEl.getBoundingClientRect();
137
118
  triggerRect.value = tRect;
138
119
  const fallbacks = getFallbackPositions(props.position);
120
+ popoverEl.style.visibility = "hidden";
139
121
  for (const pos of fallbacks) {
140
- popoverEl.style.visibility = "hidden";
141
- popoverEl.style.display = "block";
142
122
  const pRect = popoverEl.getBoundingClientRect();
143
123
  const fits = checkIfFits(pos, tRect, pRect, props.offset);
144
124
  if (fits) {
@@ -150,6 +130,7 @@ async function updateRects() {
150
130
  }
151
131
  popoverRect.value = popoverEl.getBoundingClientRect();
152
132
  currentPosition.value = props.position;
133
+ popoverEl.style.visibility = "";
153
134
  }
154
135
  async function togglePopover(force = null) {
155
136
  if (props.disabled) return;
@@ -158,29 +139,20 @@ async function togglePopover(force = null) {
158
139
  await nextTick();
159
140
  updateRects();
160
141
  }
161
- function handleDocumentClick(e) {
162
- if (!showPopover.value) return;
163
- const clickedInsideTrigger = trigger.value && trigger.value.contains(e.target);
164
- const clickedInsidePopover = popover.value && popover.value.contains(e.target);
165
- if (!clickedInsideTrigger && !clickedInsidePopover) {
142
+ onClickOutside(
143
+ containerRef,
144
+ () => {
166
145
  showPopover.value = false;
167
- }
168
- }
146
+ },
147
+ { ignore: [triggerRef] }
148
+ );
169
149
  function redrawPopover() {
170
150
  if (!showPopover.value) return;
171
151
  updateRects();
172
152
  }
173
153
  watch([popoverWidth, popoverHeight], redrawPopover);
174
- onMounted(() => {
175
- document.addEventListener("click", handleDocumentClick);
176
- window.addEventListener("scroll", redrawPopover, true);
177
- window.addEventListener("resize", redrawPopover, true);
178
- });
179
- onBeforeUnmount(() => {
180
- document.removeEventListener("click", handleDocumentClick);
181
- window.removeEventListener("scroll", redrawPopover, true);
182
- window.removeEventListener("resize", redrawPopover, true);
183
- });
154
+ useEventListener(window, "scroll", redrawPopover, { capture: true });
155
+ useEventListener(window, "resize", redrawPopover, { capture: true });
184
156
  </script>
185
157
 
186
158
  <style scoped>
@@ -1,3 +1,20 @@
1
+ type PopoverPosition = "top" | "bottom" | "left" | "right" | "top-left" | "top-right" | "bottom-left" | "bottom-right" | "left-top" | "left-bottom" | "right-top" | "right-bottom";
2
+ interface PopoverProps {
3
+ /**
4
+ * Defines where the popover is placed relative to the trigger.
5
+ * Acceptable single values: 'top', 'bottom', 'left', 'right'
6
+ * Acceptable combos: 'top-left', 'top-right', 'bottom-left', 'bottom-right',
7
+ * 'left-top', 'left-bottom', 'right-top', 'right-bottom'
8
+ * If you only provide 'top', 'bottom', 'left', or 'right',
9
+ * it aligns center by default.
10
+ */
11
+ position?: PopoverPosition;
12
+ /**
13
+ * Distance (in px) between the popover and the trigger element.
14
+ */
15
+ offset?: number;
16
+ disabled?: boolean;
17
+ }
1
18
  /**
2
19
  * Toggles popover visibility.
3
20
  * @param {boolean|null} force - If `true`/`false`, force that state; if `null`, toggle.
@@ -5,67 +22,21 @@
5
22
  declare function togglePopover(force?: boolean | null): Promise<void>;
6
23
  declare var __VLS_1: {
7
24
  toggle: typeof togglePopover;
25
+ isOpen: boolean;
8
26
  }, __VLS_15: {
9
27
  toggle: typeof togglePopover;
28
+ isOpen: true;
10
29
  };
11
30
  type __VLS_Slots = {} & {
12
31
  default?: (props: typeof __VLS_1) => any;
13
32
  } & {
14
33
  content?: (props: typeof __VLS_15) => any;
15
34
  };
16
- declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
17
- /**
18
- * Defines where the popover is placed relative to the trigger.
19
- * Acceptable single values: 'top', 'bottom', 'left', 'right'
20
- * Acceptable combos: 'top-left', 'top-right', 'bottom-left', 'bottom-right',
21
- * 'left-top', 'left-bottom', 'right-top', 'right-bottom'
22
- * If you only provide 'top', 'bottom', 'left', or 'right',
23
- * it aligns center by default.
24
- */
25
- position: {
26
- type: StringConstructor;
27
- default: string;
28
- };
29
- /**
30
- * Distance (in px) between the popover and the trigger element.
31
- */
32
- offset: {
33
- type: NumberConstructor;
34
- default: number;
35
- };
36
- disabled: {
37
- type: BooleanConstructor;
38
- default: boolean;
39
- };
40
- }>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
41
- /**
42
- * Defines where the popover is placed relative to the trigger.
43
- * Acceptable single values: 'top', 'bottom', 'left', 'right'
44
- * Acceptable combos: 'top-left', 'top-right', 'bottom-left', 'bottom-right',
45
- * 'left-top', 'left-bottom', 'right-top', 'right-bottom'
46
- * If you only provide 'top', 'bottom', 'left', or 'right',
47
- * it aligns center by default.
48
- */
49
- position: {
50
- type: StringConstructor;
51
- default: string;
52
- };
53
- /**
54
- * Distance (in px) between the popover and the trigger element.
55
- */
56
- offset: {
57
- type: NumberConstructor;
58
- default: number;
59
- };
60
- disabled: {
61
- type: BooleanConstructor;
62
- default: boolean;
63
- };
64
- }>> & Readonly<{}>, {
35
+ declare const __VLS_base: import("vue").DefineComponent<PopoverProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<PopoverProps> & Readonly<{}>, {
65
36
  disabled: boolean;
66
- position: string;
37
+ position: PopoverPosition;
67
38
  offset: number;
68
- }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
39
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
69
40
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
70
41
  declare const _default: typeof __VLS_export;
71
42
  export default _default;
@@ -1,10 +1,11 @@
1
- export interface RadioButtonProps {
1
+ import type { ControlProps } from "./ControlElement.vue.js";
2
+ export interface RadioButtonProps extends ControlProps {
2
3
  /** The value this radio represents; compared to v-model to determine checked state */
3
4
  value?: unknown;
4
5
  /** HTML name attribute — groups radios together so only one is selected at a time */
5
6
  name?: string;
6
7
  /** Inline label text (alternative to default slot) */
7
- label?: string;
8
+ text?: string;
8
9
  /** Visually hides the label while keeping it accessible to SR (screen readers) */
9
10
  hideLabel?: boolean;
10
11
  }
@@ -1,15 +1,22 @@
1
1
  <script setup>
2
2
  const modelValue = defineModel({ type: null });
3
- defineProps({
3
+ const props = defineProps({
4
4
  value: { type: null, required: false },
5
5
  name: { type: String, required: false },
6
+ text: { type: String, required: false },
7
+ hideLabel: { type: Boolean, required: false },
8
+ appearance: { type: String, required: false },
9
+ error: { type: [String, null], required: false },
10
+ group: { type: Boolean, required: false },
11
+ id: { type: String, required: false },
6
12
  label: { type: String, required: false },
7
- hideLabel: { type: Boolean, required: false }
13
+ layout: { type: String, required: false },
14
+ size: { type: String, required: false }
8
15
  });
9
16
  </script>
10
17
 
11
18
  <template>
12
- <orio-control-element class="radio">
19
+ <orio-control-element v-bind="props" class="radio">
13
20
  <label class="radio-label">
14
21
  <input
15
22
  v-model="modelValue"
@@ -21,11 +28,11 @@ defineProps({
21
28
  />
22
29
  <span class="radio-box" />
23
30
  <span
24
- v-if="label || $slots.default"
31
+ v-if="text || $slots.default"
25
32
  class="radio-text"
26
33
  :class="{ 'sr-only': hideLabel }"
27
34
  >
28
- <slot>{{ label }}</slot>
35
+ <slot>{{ text }}</slot>
29
36
  </span>
30
37
  </label>
31
38
  </orio-control-element>
@@ -1,10 +1,11 @@
1
- export interface RadioButtonProps {
1
+ import type { ControlProps } from "./ControlElement.vue.js";
2
+ export interface RadioButtonProps extends ControlProps {
2
3
  /** The value this radio represents; compared to v-model to determine checked state */
3
4
  value?: unknown;
4
5
  /** HTML name attribute — groups radios together so only one is selected at a time */
5
6
  name?: string;
6
7
  /** Inline label text (alternative to default slot) */
7
- label?: string;
8
+ text?: string;
8
9
  /** Visually hides the label while keeping it accessible to SR (screen readers) */
9
10
  hideLabel?: boolean;
10
11
  }
@@ -1,5 +1,6 @@
1
+ import type { ControlProps } from "./ControlElement.vue.js";
1
2
  export type SelectableOption<T extends object = object> = string | T;
2
- export interface SelectProps<T extends object = object> {
3
+ export interface SelectProps<T extends object = object> extends ControlProps {
3
4
  options: SelectableOption[];
4
5
  multiple?: boolean;
5
6
  field?: string;
@@ -1,11 +1,19 @@
1
1
  <script setup>
2
- import { computed, toRefs } from "vue";
2
+ import { computed, ref, toRefs } from "vue";
3
+ import { useListKeyboard } from "../composables/useListKeyboard";
3
4
  const props = defineProps({
4
5
  options: { type: Array, required: true },
5
6
  multiple: { type: Boolean, required: false },
6
7
  field: { type: String, required: false, default: "id" },
7
8
  optionName: { type: String, required: false },
8
- placeholder: { type: String, required: false, default: "Select an option" }
9
+ placeholder: { type: String, required: false, default: "Select an option" },
10
+ appearance: { type: String, required: false },
11
+ error: { type: [String, null], required: false },
12
+ group: { type: Boolean, required: false },
13
+ id: { type: String, required: false },
14
+ label: { type: String, required: false },
15
+ layout: { type: String, required: false },
16
+ size: { type: String, required: false }
9
17
  });
10
18
  const { field, optionName, placeholder } = toRefs(props);
11
19
  const modelValue = defineModel({ type: null, ...{
@@ -59,15 +67,53 @@ function getOptionKey(option) {
59
67
  if (typeof option === "string") return option;
60
68
  return String(option[key.value]);
61
69
  }
70
+ const controlProps = computed(() => {
71
+ const {
72
+ options: _options,
73
+ multiple: _multiple,
74
+ field: _field,
75
+ optionName: _optionName,
76
+ placeholder: _placeholder,
77
+ ...rest
78
+ } = props;
79
+ return rest;
80
+ });
62
81
  const selectorAttrs = computed(() => ({ getOptionKey, getOptionLabel }));
82
+ const popoverToggleRef = ref(() => {
83
+ });
84
+ const {
85
+ highlightedIndex,
86
+ listRef,
87
+ onKeydown,
88
+ reset: resetHighlight
89
+ } = useListKeyboard({
90
+ count: () => props.options.length,
91
+ onSelect: (index) => toggleOption(props.options[index], () => popoverToggleRef.value(false)),
92
+ onOpen: () => {
93
+ popoverToggleRef.value(true);
94
+ resetHighlight();
95
+ },
96
+ onClose: () => popoverToggleRef.value(false),
97
+ initialIndex: () => props.options.findIndex(isOptionSelected)
98
+ });
63
99
  </script>
64
100
 
65
101
  <template>
66
- <orio-control-element>
102
+ <orio-control-element v-bind="controlProps">
67
103
  <orio-popover position="bottom-right" :offset="5">
68
- <template #default="{ toggle }">
104
+ <template #default="{ toggle, isOpen }">
69
105
  <slot name="trigger" :toggle>
70
- <div class="selector-trigger" @click="toggle()">
106
+ <button
107
+ :id="props.id"
108
+ type="button"
109
+ class="selector-trigger"
110
+ aria-haspopup="listbox"
111
+ :aria-expanded="isOpen"
112
+ @click="toggle();
113
+ !isOpen && resetHighlight()"
114
+ @keydown="popoverToggleRef = toggle;
115
+ onKeydown($event, isOpen)"
116
+ >
71
117
  <slot
72
118
  name="trigger-content"
73
119
  :toggle
@@ -91,18 +137,29 @@ const selectorAttrs = computed(() => ({ getOptionKey, getOptionLabel }));
91
137
  </div>
92
138
  <orio-icon name="chevron-down" />
93
139
  </slot>
94
- </div>
140
+ </button>
95
141
  </slot>
96
142
  </template>
97
143
 
98
144
  <template #content="{ toggle }">
99
145
  <div class="selector-content">
100
- <ul v-if="options.length">
146
+ <ul
147
+ v-if="options.length"
148
+ ref="listRef"
149
+ role="listbox"
150
+ :aria-multiselectable="multiple || void 0"
151
+ >
101
152
  <li
102
- v-for="option in options"
153
+ v-for="(option, index) in options"
103
154
  :key="getOptionKey(option)"
104
- :class="{ selected: isOptionSelected(option) }"
155
+ role="option"
156
+ :aria-selected="isOptionSelected(option)"
157
+ :class="{
158
+ selected: isOptionSelected(option),
159
+ highlighted: index === highlightedIndex
160
+ }"
105
161
  @click="toggleOption(option, toggle)"
162
+ @mouseenter="highlightedIndex = index"
106
163
  >
107
164
  <slot
108
165
  name="option"
@@ -127,6 +184,7 @@ const selectorAttrs = computed(() => ({ getOptionKey, getOptionLabel }));
127
184
 
128
185
  <style scoped>
129
186
  .selector-trigger {
187
+ width: 100%;
130
188
  z-index: 1;
131
189
  min-height: 1.5rem;
132
190
  user-select: none;
@@ -178,7 +236,7 @@ const selectorAttrs = computed(() => ({ getOptionKey, getOptionLabel }));
178
236
  transition: background-color 0.15s ease, color 0.15s ease;
179
237
  color: var(--color-text);
180
238
  }
181
- .selector-content ul li:hover {
239
+ .selector-content ul li:hover, .selector-content ul li.highlighted {
182
240
  background-color: var(--color-surface); /* neutral lift */
183
241
  }
184
242
  .selector-content ul li.selected {
@@ -1,5 +1,6 @@
1
+ import type { ControlProps } from "./ControlElement.vue.js";
1
2
  export type SelectableOption<T extends object = object> = string | T;
2
- export interface SelectProps<T extends object = object> {
3
+ export interface SelectProps<T extends object = object> extends ControlProps {
3
4
  options: SelectableOption[];
4
5
  multiple?: boolean;
5
6
  field?: string;
@@ -1,4 +1,5 @@
1
- interface Props {
1
+ import type { ControlProps } from "./ControlElement.vue.js";
2
+ interface Props extends ControlProps {
2
3
  disabled?: boolean;
3
4
  }
4
5
  type __VLS_Props = Props;
@@ -1,6 +1,13 @@
1
1
  <script setup>
2
2
  const props = defineProps({
3
- disabled: { type: Boolean, required: false, default: false }
3
+ disabled: { type: Boolean, required: false, default: false },
4
+ appearance: { type: String, required: false },
5
+ error: { type: [String, null], required: false },
6
+ group: { type: Boolean, required: false },
7
+ id: { type: String, required: false },
8
+ label: { type: String, required: false },
9
+ layout: { type: String, required: false },
10
+ size: { type: String, required: false }
4
11
  });
5
12
  const modelValue = defineModel({ type: Boolean, ...{ required: false } });
6
13
  function toggle() {
@@ -10,9 +17,10 @@ function toggle() {
10
17
  </script>
11
18
 
12
19
  <template>
13
- <orio-control-element v-slot="{ id }">
20
+ <orio-control-element v-slot="{ id }" v-bind="props">
14
21
  <button
15
22
  :id
23
+ v-bind="$attrs"
16
24
  class="switch-button"
17
25
  :class="{ active: modelValue, disabled }"
18
26
  :disabled="disabled"
@@ -1,4 +1,5 @@
1
- interface Props {
1
+ import type { ControlProps } from "./ControlElement.vue.js";
2
+ interface Props extends ControlProps {
2
3
  disabled?: boolean;
3
4
  }
4
5
  type __VLS_Props = Props;
@@ -1,5 +1,6 @@
1
+ import type { ControlProps } from "./ControlElement.vue.js";
1
2
  import type { InputLayout } from "./Input.vue.js";
2
- interface Props {
3
+ interface Props extends Omit<ControlProps, "layout"> {
3
4
  layout?: InputLayout;
4
5
  }
5
6
  type __VLS_Props = Props;
@@ -1,20 +1,24 @@
1
1
  <script setup>
2
- import { useAttrs } from "vue";
3
- const attrs = useAttrs();
4
2
  const modelValue = defineModel({ type: String, ...{ default: "" } });
5
- defineProps({
6
- layout: { type: String, required: false, default: "vertical" }
3
+ const props = defineProps({
4
+ layout: { type: String, required: false, default: "vertical" },
5
+ appearance: { type: String, required: false },
6
+ error: { type: [String, null], required: false },
7
+ group: { type: Boolean, required: false },
8
+ id: { type: String, required: false },
9
+ label: { type: String, required: false },
10
+ size: { type: String, required: false }
7
11
  });
8
12
  </script>
9
13
 
10
14
  <template>
11
15
  <orio-control-element
12
16
  v-slot="{ id }"
13
- v-bind="attrs"
17
+ v-bind="props"
14
18
  :layout="layout === 'inner' ? 'vertical' : layout"
15
19
  :class="{ inner: layout === 'inner' }"
16
20
  >
17
- <textarea v-bind="attrs" :id v-model="modelValue" rows="4" />
21
+ <textarea :id v-model="modelValue" rows="4" v-bind="$attrs" />
18
22
  </orio-control-element>
19
23
  </template>
20
24
 
@@ -67,7 +71,6 @@ textarea {
67
71
  .inner :deep(.control-label) {
68
72
  position: absolute;
69
73
  z-index: 1;
70
- pointer-events: none;
71
74
  left: var(--control-px);
72
75
  top: var(--control-label-block-start);
73
76
  color: var(--color-muted);