@sewadah/selaras 0.2.0 → 0.3.1

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 (176) hide show
  1. package/README.md +36 -7
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +31 -15
  4. package/dist/runtime/components/Autocomplete.vue +3 -1
  5. package/dist/runtime/components/Badge.d.vue.ts +1 -1
  6. package/dist/runtime/components/Badge.vue.d.ts +1 -1
  7. package/dist/runtime/components/Calendar.d.vue.ts +69 -0
  8. package/dist/runtime/components/Calendar.vue +163 -0
  9. package/dist/runtime/components/Calendar.vue.d.ts +69 -0
  10. package/dist/runtime/components/Callout.vue +12 -1
  11. package/dist/runtime/components/CodeGroup.vue +13 -3
  12. package/dist/runtime/components/CodeTree.d.vue.ts +2 -0
  13. package/dist/runtime/components/CodeTree.vue +52 -1
  14. package/dist/runtime/components/CodeTree.vue.d.ts +2 -0
  15. package/dist/runtime/components/ColorPicker.d.vue.ts +8 -0
  16. package/dist/runtime/components/ColorPicker.vue +66 -45
  17. package/dist/runtime/components/ColorPicker.vue.d.ts +8 -0
  18. package/dist/runtime/components/Column.js +1 -1
  19. package/dist/runtime/components/CommandPalette.d.vue.ts +3 -1
  20. package/dist/runtime/components/CommandPalette.vue +5 -0
  21. package/dist/runtime/components/CommandPalette.vue.d.ts +3 -1
  22. package/dist/runtime/components/ContentNavigation.d.vue.ts +8 -1
  23. package/dist/runtime/components/ContentNavigation.vue +9 -2
  24. package/dist/runtime/components/ContentNavigation.vue.d.ts +8 -1
  25. package/dist/runtime/components/ContentToc.vue +14 -7
  26. package/dist/runtime/components/ContextMenu.d.vue.ts +8 -1
  27. package/dist/runtime/components/ContextMenu.vue +16 -4
  28. package/dist/runtime/components/ContextMenu.vue.d.ts +8 -1
  29. package/dist/runtime/components/DatePicker.d.vue.ts +10 -3
  30. package/dist/runtime/components/DatePicker.vue +24 -16
  31. package/dist/runtime/components/DatePicker.vue.d.ts +10 -3
  32. package/dist/runtime/components/Dropdown.d.vue.ts +33 -6
  33. package/dist/runtime/components/Dropdown.vue +143 -11
  34. package/dist/runtime/components/Dropdown.vue.d.ts +33 -6
  35. package/dist/runtime/components/FileTree.d.vue.ts +2 -0
  36. package/dist/runtime/components/FileTree.vue +8 -4
  37. package/dist/runtime/components/FileTree.vue.d.ts +2 -0
  38. package/dist/runtime/components/FileUpload.vue +1 -1
  39. package/dist/runtime/components/FormField.d.vue.ts +4 -0
  40. package/dist/runtime/components/FormField.vue +19 -4
  41. package/dist/runtime/components/FormField.vue.d.ts +4 -0
  42. package/dist/runtime/components/Input.vue +3 -2
  43. package/dist/runtime/components/Kbd.vue +12 -2
  44. package/dist/runtime/components/NavigationMenu.d.vue.ts +10 -10
  45. package/dist/runtime/components/NavigationMenu.vue +13 -7
  46. package/dist/runtime/components/NavigationMenu.vue.d.ts +10 -10
  47. package/dist/runtime/components/NavigationMenuAccordionItem.vue +5 -1
  48. package/dist/runtime/components/NavigationMenuFlyoutList.vue +7 -4
  49. package/dist/runtime/components/PinInput.vue +1 -1
  50. package/dist/runtime/components/Popover.d.vue.ts +8 -2
  51. package/dist/runtime/components/Popover.vue +9 -4
  52. package/dist/runtime/components/Popover.vue.d.ts +8 -2
  53. package/dist/runtime/components/ProsePre.d.vue.ts +6 -2
  54. package/dist/runtime/components/ProsePre.vue +11 -4
  55. package/dist/runtime/components/ProsePre.vue.d.ts +6 -2
  56. package/dist/runtime/components/Select.vue +3 -1
  57. package/dist/runtime/components/SplitterPanel.d.vue.ts +2 -2
  58. package/dist/runtime/components/SplitterPanel.vue.d.ts +2 -2
  59. package/dist/runtime/components/Table.vue +95 -26
  60. package/dist/runtime/components/Tabs.d.vue.ts +44 -4
  61. package/dist/runtime/components/Tabs.vue +171 -18
  62. package/dist/runtime/components/Tabs.vue.d.ts +44 -4
  63. package/dist/runtime/components/Textarea.vue +3 -2
  64. package/dist/runtime/components/Timeline.d.vue.ts +19 -0
  65. package/dist/runtime/components/Timeline.vue +99 -0
  66. package/dist/runtime/components/Timeline.vue.d.ts +19 -0
  67. package/dist/runtime/components/Toast.d.vue.ts +10 -1
  68. package/dist/runtime/components/Toast.vue +94 -8
  69. package/dist/runtime/components/Toast.vue.d.ts +10 -1
  70. package/dist/runtime/components/Tooltip.d.vue.ts +8 -2
  71. package/dist/runtime/components/Tooltip.vue +9 -4
  72. package/dist/runtime/components/Tooltip.vue.d.ts +8 -2
  73. package/dist/runtime/composables/use-form-field.d.ts +1 -0
  74. package/dist/runtime/composables/use-table.d.ts +3 -0
  75. package/dist/runtime/composables/use-table.js +10 -4
  76. package/dist/runtime/composables/use-toast.js +5 -1
  77. package/dist/runtime/default-colors.css +1 -1
  78. package/dist/runtime/directives/hotkey.d.ts +13 -0
  79. package/dist/runtime/directives/hotkey.js +107 -0
  80. package/dist/runtime/directives/index.d.ts +2 -0
  81. package/dist/runtime/directives/index.js +2 -0
  82. package/dist/runtime/directives/mask.d.ts +30 -0
  83. package/dist/runtime/directives/mask.js +165 -0
  84. package/dist/runtime/internal/ComboboxSelectBase.d.vue.ts +29 -25
  85. package/dist/runtime/internal/ComboboxSelectBase.vue +104 -20
  86. package/dist/runtime/internal/ComboboxSelectBase.vue.d.ts +29 -25
  87. package/dist/runtime/internal/ComboboxSelectBody.d.vue.ts +16 -11
  88. package/dist/runtime/internal/ComboboxSelectBody.vue +49 -10
  89. package/dist/runtime/internal/ComboboxSelectBody.vue.d.ts +16 -11
  90. package/dist/runtime/internal/DatePickerCalendarBody.vue +1 -1
  91. package/dist/runtime/internal/DatePickerRangeCalendarBody.vue +1 -1
  92. package/dist/runtime/internal/ShortcutHint.d.vue.ts +6 -0
  93. package/dist/runtime/internal/ShortcutHint.vue +13 -0
  94. package/dist/runtime/internal/ShortcutHint.vue.d.ts +6 -0
  95. package/dist/runtime/internal/ToastItemRenderer.d.vue.ts +9 -1
  96. package/dist/runtime/internal/ToastItemRenderer.vue +55 -8
  97. package/dist/runtime/internal/ToastItemRenderer.vue.d.ts +9 -1
  98. package/dist/runtime/internal/combobox-select.js +2 -1
  99. package/dist/runtime/internal/programmatic-services.d.ts +2 -0
  100. package/dist/runtime/internal/programmatic-services.js +8 -0
  101. package/dist/runtime/internal/toast-stack.d.ts +36 -0
  102. package/dist/runtime/internal/toast-stack.js +36 -0
  103. package/dist/runtime/structural.css +1 -1
  104. package/dist/runtime/table.d.ts +1 -1
  105. package/dist/runtime/theme/alert-dialog.js +1 -1
  106. package/dist/runtime/theme/avatar.js +7 -7
  107. package/dist/runtime/theme/badge.js +2 -2
  108. package/dist/runtime/theme/calendar.d.ts +86 -0
  109. package/dist/runtime/theme/calendar.js +19 -0
  110. package/dist/runtime/theme/callout.d.ts +0 -12
  111. package/dist/runtime/theme/callout.js +8 -8
  112. package/dist/runtime/theme/checkbox.js +1 -1
  113. package/dist/runtime/theme/code-tree.js +1 -1
  114. package/dist/runtime/theme/command-palette.js +1 -1
  115. package/dist/runtime/theme/content-navigation.d.ts +3 -0
  116. package/dist/runtime/theme/content-navigation.js +1 -0
  117. package/dist/runtime/theme/date-picker.js +1 -1
  118. package/dist/runtime/theme/drawer.js +1 -1
  119. package/dist/runtime/theme/dropdown.d.ts +21 -0
  120. package/dist/runtime/theme/dropdown.js +15 -2
  121. package/dist/runtime/theme/file-tree.js +1 -1
  122. package/dist/runtime/theme/file-upload.js +2 -2
  123. package/dist/runtime/theme/form-field.d.ts +18 -0
  124. package/dist/runtime/theme/form-field.js +6 -1
  125. package/dist/runtime/theme/icon.js +1 -1
  126. package/dist/runtime/theme/input-number.js +1 -1
  127. package/dist/runtime/theme/input.js +1 -1
  128. package/dist/runtime/theme/modal.js +1 -1
  129. package/dist/runtime/theme/pin-input.js +1 -1
  130. package/dist/runtime/theme/progress.js +2 -2
  131. package/dist/runtime/theme/prose.d.ts +6 -0
  132. package/dist/runtime/theme/prose.js +2 -0
  133. package/dist/runtime/theme/radio-group.js +4 -4
  134. package/dist/runtime/theme/rating.js +1 -1
  135. package/dist/runtime/theme/select.js +2 -2
  136. package/dist/runtime/theme/separator.js +1 -1
  137. package/dist/runtime/theme/slideover.js +1 -1
  138. package/dist/runtime/theme/slider.js +2 -2
  139. package/dist/runtime/theme/splitter-resize-handle.js +1 -1
  140. package/dist/runtime/theme/switch.js +1 -1
  141. package/dist/runtime/theme/table.d.ts +27 -0
  142. package/dist/runtime/theme/table.js +11 -2
  143. package/dist/runtime/theme/tabs.d.ts +87 -3
  144. package/dist/runtime/theme/tabs.js +46 -22
  145. package/dist/runtime/theme/textarea.js +1 -1
  146. package/dist/runtime/theme/timeline.d.ts +251 -0
  147. package/dist/runtime/theme/timeline.js +84 -0
  148. package/dist/runtime/theme/toast.d.ts +92 -0
  149. package/dist/runtime/theme/toast.js +29 -7
  150. package/dist/runtime/theme-config.d.ts +12 -1
  151. package/dist/runtime/types/index.d.ts +6 -1
  152. package/dist/runtime/utils/arrow.d.ts +21 -0
  153. package/dist/runtime/utils/arrow.js +10 -0
  154. package/dist/runtime/utils/color-registry.d.ts +3 -1
  155. package/dist/runtime/utils/color-registry.js +4 -0
  156. package/dist/runtime/utils/file-icons.d.ts +8 -0
  157. package/dist/runtime/utils/file-icons.js +133 -0
  158. package/dist/runtime/utils/functional-tokens.d.ts +1 -1
  159. package/dist/runtime/utils/functional-tokens.js +1 -1
  160. package/dist/runtime/utils/icons.d.ts +2 -0
  161. package/dist/runtime/utils/icons.js +2 -0
  162. package/dist/runtime/utils/messages.d.ts +6 -0
  163. package/dist/runtime/utils/messages.js +6 -0
  164. package/dist/runtime/utils/navigation-menu.d.ts +10 -0
  165. package/dist/runtime/utils/navigation-menu.js +3 -0
  166. package/dist/runtime/utils/overlay.d.ts +17 -0
  167. package/dist/runtime/utils/overlay.js +7 -0
  168. package/dist/runtime/utils/seed-colors.d.ts +2 -1
  169. package/dist/runtime/utils/seed-colors.js +24 -16
  170. package/dist/runtime/utils/select-contracts.d.ts +7 -1
  171. package/dist/runtime/utils/table-columns.js +1 -1
  172. package/dist/runtime/utils/table-contracts.d.ts +9 -1
  173. package/dist/runtime/utils/timeline-contracts.d.ts +42 -0
  174. package/dist/runtime/utils/timeline-contracts.js +0 -0
  175. package/dist/tokens/default-colors.tokens.json +100 -100
  176. package/package.json +21 -9
@@ -1,6 +1,8 @@
1
1
  import type { VariantProps } from 'tailwind-variants';
2
2
  import type { ColorPickerThemeSlots } from '../theme/color-picker.js';
3
+ import type { RoundedArrowConfig } from '../utils/arrow.js';
3
4
  import type { ColorRole } from '../utils/color-registry.js';
5
+ import type { OverlayPortal, OverlayPositioning } from '../utils/overlay.js';
4
6
  import type { UiProp } from '../utils/ui.js';
5
7
  import { colorPickerTheme } from '../theme/color-picker.js';
6
8
  type ColorPickerVariants = VariantProps<typeof colorPickerTheme>;
@@ -21,6 +23,12 @@ export interface ColorPickerProps {
21
23
  placeholder?: string;
22
24
  /** Opts into the picker's accessible small-screen modal presentation. */
23
25
  adaptive?: boolean;
26
+ /** Shows a pointer on the anchored picker; the adaptive modal has no arrow. */
27
+ arrow?: boolean | RoundedArrowConfig;
28
+ /** Positioning of the anchored picker (the adaptive modal uses its own layout). */
29
+ positioning?: OverlayPositioning;
30
+ /** Teleport target for the anchored picker, or `false` to render it inline. */
31
+ portal?: OverlayPortal;
24
32
  size?: ColorPickerVariants['size'];
25
33
  color?: ColorRole;
26
34
  ui?: UiProp<ColorPickerThemeSlots>;
@@ -1,14 +1,17 @@
1
1
  <script setup>
2
- import { ColorSwatch } from "reka-ui";
2
+ import { ColorSwatch, PopoverArrow, PopoverContent, PopoverPortal, PopoverRoot, PopoverTrigger } from "reka-ui";
3
3
  import { computed, getCurrentInstance, onMounted, onUnmounted, ref, watch } from "vue";
4
+ import { useFormField } from "../composables/use-form-field";
4
5
  import { useIsMobile } from "../composables/use-media-query";
5
6
  import { useMessages } from "../composables/use-messages";
6
7
  import ColorPickerBody from "../internal/ColorPickerBody.vue";
7
8
  import { colorPickerTheme } from "../theme/color-picker";
9
+ import { popoverTheme } from "../theme/popover";
10
+ import { arrowContentProps, arrowElementProps } from "../utils/arrow";
11
+ import { overlayPortalProps } from "../utils/overlay";
8
12
  import { resolveRegisteredColorRole } from "../utils/registered-colors";
9
13
  import { resolveSlot, useComponentTheme, useThemeBindings } from "../utils/ui";
10
14
  import Modal from "./Modal.vue";
11
- import Popover from "./Popover.vue";
12
15
  const props = defineProps({
13
16
  name: { type: String, required: false },
14
17
  form: { type: String, required: false },
@@ -21,12 +24,17 @@ const props = defineProps({
21
24
  swatches: { type: Array, required: false },
22
25
  placeholder: { type: String, required: false },
23
26
  adaptive: { type: Boolean, required: false },
27
+ arrow: { type: [Boolean, Object], required: false },
28
+ positioning: { type: Object, required: false },
29
+ portal: { type: [Boolean, String], required: false, skipCheck: true, default: void 0 },
24
30
  size: { type: null, required: false },
25
31
  color: { type: null, required: false },
26
32
  ui: { type: Object, required: false }
27
33
  });
28
34
  const emit = defineEmits(["update:open", "update:modelValue"]);
29
35
  const messages = useMessages();
36
+ const field = useFormField();
37
+ const formName = computed(() => props.name ?? field?.name);
30
38
  const instance = getCurrentInstance();
31
39
  const formAnchor = ref();
32
40
  const initialColor = props.defaultValue ?? "#000000";
@@ -104,55 +112,68 @@ const bodyProps = computed(() => ({
104
112
  swatchIndicatorProps: swatchIndicatorProps.value,
105
113
  colorRoleMarker: colorRoleMarker.value
106
114
  }));
107
- const popoverUi = computed(() => ({ content: resolveSlot(ui.value.content, props.ui?.content) }));
115
+ const popoverThemeFn = useComponentTheme("popover", popoverTheme);
116
+ const popoverUi = computed(() => popoverThemeFn.value());
117
+ const popoverContentProps = computed(() => ({
118
+ ...resolveSlot(popoverUi.value.content, resolveSlot(ui.value.content, props.ui?.content)),
119
+ ...arrowContentProps(props.arrow),
120
+ ...props.positioning
121
+ }));
122
+ const popoverArrowProps = computed(() => ({ ...resolveSlot(popoverUi.value.arrow, void 0), ...arrowElementProps(props.arrow) }));
123
+ const popoverPortalProps = computed(() => overlayPortalProps(props.portal));
108
124
  const adaptiveUi = computed(() => ({ content: "rounded-[var(--selaras-resolved-radius-md)]" }));
109
125
  const mobileContentProps = computed(() => resolveSlot(ui.value.mobileContent, props.ui?.mobileContent));
110
126
  </script>
111
127
 
112
128
  <template>
113
- <Popover v-if="!showMobileModal" :open="open" :ui="popoverUi" @update:open="onUpdateOpen">
114
- <button
115
- type="button"
116
- :disabled="disabled"
117
- :aria-label="messages.colorPicker"
118
- :data-selaras-color="colorRoleMarker"
119
- :data-selaras-theme="themeBindings['data-selaras-theme']"
120
- :data-selaras-mode="themeBindings['data-selaras-mode']"
121
- :style="themeBindings.style"
122
- v-bind="triggerProps"
123
- >
124
- <ColorSwatch :color="internalColor" v-bind="triggerSwatchProps" />
125
- <span v-bind="triggerValueProps">{{ internalColor }}</span>
126
- </button>
129
+ <!--
130
+ The root and trigger stay mounted for both presentations. Only the
131
+ content switches: the anchored panel is removed outright while the
132
+ adaptive Modal is showing, and the Modal returns focus to this same
133
+ trigger on close.
134
+ -->
135
+ <PopoverRoot :open="open" @update:open="onUpdateOpen">
136
+ <PopoverTrigger as-child>
137
+ <button
138
+ type="button"
139
+ :disabled="disabled"
140
+ :aria-label="messages.colorPicker"
141
+ :data-selaras-color="colorRoleMarker"
142
+ :data-selaras-theme="themeBindings['data-selaras-theme']"
143
+ :data-selaras-mode="themeBindings['data-selaras-mode']"
144
+ :style="themeBindings.style"
145
+ v-bind="triggerProps"
146
+ >
147
+ <ColorSwatch :color="internalColor" v-bind="triggerSwatchProps" />
148
+ <span v-bind="triggerValueProps">{{ internalColor }}</span>
149
+ </button>
150
+ </PopoverTrigger>
127
151
 
128
- <template #content>
129
- <ColorPickerBody v-bind="bodyProps" @update:model-value="onUpdateColor" />
130
- </template>
131
- </Popover>
152
+ <PopoverPortal v-if="!showMobileModal" v-bind="popoverPortalProps">
153
+ <PopoverContent
154
+ :data-selaras-theme="themeBindings['data-selaras-theme']"
155
+ :data-selaras-mode="themeBindings['data-selaras-mode']"
156
+ :style="themeBindings.style"
157
+ side="bottom"
158
+ align="start"
159
+ :side-offset="8"
160
+ v-bind="popoverContentProps"
161
+ >
162
+ <ColorPickerBody v-bind="bodyProps" @update:model-value="onUpdateColor" />
163
+ <PopoverArrow v-if="arrow" v-bind="popoverArrowProps" />
164
+ </PopoverContent>
165
+ </PopoverPortal>
132
166
 
133
- <Modal
134
- v-else :open="open" :title="messages.colorPicker" :description="messages.colorPickerDescription"
135
- :ui="adaptiveUi" @update:open="onUpdateOpen"
136
- >
137
- <button
138
- type="button"
139
- :disabled="disabled"
140
- :aria-label="messages.colorPicker"
141
- :data-selaras-color="colorRoleMarker"
142
- :data-selaras-theme="themeBindings['data-selaras-theme']"
143
- :data-selaras-mode="themeBindings['data-selaras-mode']"
144
- :style="themeBindings.style"
145
- v-bind="triggerProps"
167
+ <Modal
168
+ v-else :open="open" :title="messages.colorPicker" :description="messages.colorPickerDescription"
169
+ :ui="adaptiveUi" @update:open="onUpdateOpen"
146
170
  >
147
- <ColorSwatch :color="internalColor" v-bind="triggerSwatchProps" />
148
- <span v-bind="triggerValueProps">{{ internalColor }}</span>
149
- </button>
150
-
151
- <template #content>
152
- <div v-bind="mobileContentProps">
153
- <ColorPickerBody v-bind="bodyProps" @update:model-value="onUpdateColor" />
154
- </div>
155
- </template>
156
- </Modal>
157
- <input ref="formAnchor" type="hidden" :name="name" :form="form" :value="internalColor">
171
+ <template #content>
172
+ <div v-bind="mobileContentProps">
173
+ <ColorPickerBody v-bind="bodyProps" @update:model-value="onUpdateColor" />
174
+ </div>
175
+ </template>
176
+ </Modal>
177
+ </PopoverRoot>
178
+ <input ref="formAnchor" type="hidden" :name="formName" :form="form" :value="internalColor" :disabled="disabled">
158
179
  </template>
@@ -1,6 +1,8 @@
1
1
  import type { VariantProps } from 'tailwind-variants';
2
2
  import type { ColorPickerThemeSlots } from '../theme/color-picker.js';
3
+ import type { RoundedArrowConfig } from '../utils/arrow.js';
3
4
  import type { ColorRole } from '../utils/color-registry.js';
5
+ import type { OverlayPortal, OverlayPositioning } from '../utils/overlay.js';
4
6
  import type { UiProp } from '../utils/ui.js';
5
7
  import { colorPickerTheme } from '../theme/color-picker.js';
6
8
  type ColorPickerVariants = VariantProps<typeof colorPickerTheme>;
@@ -21,6 +23,12 @@ export interface ColorPickerProps {
21
23
  placeholder?: string;
22
24
  /** Opts into the picker's accessible small-screen modal presentation. */
23
25
  adaptive?: boolean;
26
+ /** Shows a pointer on the anchored picker; the adaptive modal has no arrow. */
27
+ arrow?: boolean | RoundedArrowConfig;
28
+ /** Positioning of the anchored picker (the adaptive modal uses its own layout). */
29
+ positioning?: OverlayPositioning;
30
+ /** Teleport target for the anchored picker, or `false` to render it inline. */
31
+ portal?: OverlayPortal;
24
32
  size?: ColorPickerVariants['size'];
25
33
  color?: ColorRole;
26
34
  ui?: UiProp<ColorPickerThemeSlots>;
@@ -16,7 +16,7 @@ export default defineComponent({
16
16
  },
17
17
  sortable: {
18
18
  type: Boolean,
19
- default: true
19
+ default: false
20
20
  },
21
21
  filterable: {
22
22
  type: Boolean,
@@ -3,8 +3,10 @@ import type { UiProp } from '../utils/ui.js';
3
3
  export interface CommandPaletteItem {
4
4
  label: string;
5
5
  icon?: string;
6
- /** Rendered via Kbd - a display hint only, this component doesn't bind the key itself. */
6
+ /** Displayed via Kbd. */
7
7
  shortcut?: string;
8
+ /** Bind `shortcut` while this palette is open; activation uses this item's normal selection behavior. */
9
+ hotkey?: boolean;
8
10
  disabled?: boolean;
9
11
  onSelect?: () => void;
10
12
  }
@@ -4,6 +4,7 @@ import { computed, onMounted, onUnmounted, ref, useId, watch } from "vue";
4
4
  import { useCommandPalette } from "../composables/use-command-palette";
5
5
  import { useIcons } from "../composables/use-icons";
6
6
  import { useMessages } from "../composables/use-messages";
7
+ import { vHotkey } from "../directives/hotkey";
7
8
  import { commandPaletteTheme } from "../theme/command-palette";
8
9
  import { fuzzyScore } from "../utils/fuzzy-match";
9
10
  import { applyClassPrefix, resolveSlot, useComponentTheme, useRootProps, useThemeBindings } from "../utils/ui";
@@ -40,6 +41,9 @@ const query = ref("");
40
41
  const highlightedIndex = ref(0);
41
42
  const inputRef = ref();
42
43
  const listboxId = `selaras-command-palette-list-${useId()}`;
44
+ function hotkeyFor(item) {
45
+ return item.hotkey && item.shortcut && !item.disabled ? { keys: item.shortcut, when: () => isOpen.value, allowInEditable: true } : void 0;
46
+ }
43
47
  watch(isOpen, (open) => {
44
48
  if (!open) {
45
49
  query.value = "";
@@ -178,6 +182,7 @@ function itemId(index) {
178
182
  v-for="item in group.items"
179
183
  :id="itemId(allItems.indexOf(item))"
180
184
  :key="item.label"
185
+ v-hotkey="hotkeyFor(item)"
181
186
  role="option"
182
187
  :aria-selected="isHighlighted(item)"
183
188
  :aria-disabled="item.disabled"
@@ -3,8 +3,10 @@ import type { UiProp } from '../utils/ui.js';
3
3
  export interface CommandPaletteItem {
4
4
  label: string;
5
5
  icon?: string;
6
- /** Rendered via Kbd - a display hint only, this component doesn't bind the key itself. */
6
+ /** Displayed via Kbd. */
7
7
  shortcut?: string;
8
+ /** Bind `shortcut` while this palette is open; activation uses this item's normal selection behavior. */
9
+ hotkey?: boolean;
8
10
  disabled?: boolean;
9
11
  onSelect?: () => void;
10
12
  }
@@ -4,7 +4,12 @@ import type { UiProp } from '../utils/ui.js';
4
4
  export interface ContentNavigationLink {
5
5
  title: string;
6
6
  path: string;
7
- icon?: string;
7
+ icon?: string | false;
8
+ order?: number;
9
+ collapse?: boolean;
10
+ navBadges?: Array<string | {
11
+ text: string;
12
+ }>;
8
13
  children?: ContentNavigationLink[];
9
14
  }
10
15
  type __VLS_Slots = ContentNavigationSlots;
@@ -12,6 +17,8 @@ export interface ContentNavigationProps {
12
17
  navigation: ContentNavigationLink[];
13
18
  /** The active-link accent. @default 'primary' */
14
19
  color?: ColorRole;
20
+ /** Collapse every group initially unless the item's own `collapse` metadata overrides it. @default false */
21
+ collapsed?: boolean;
15
22
  /** Set only by this component's own recursive self-call, one level down for each nested group - gates the tree-connector rail (see `content-navigation.ts`'s own `isNested` variant), since a root-level entry has no parent trunk to its left to branch off of. */
16
23
  isNested?: boolean;
17
24
  ui?: UiProp<ContentNavigationThemeSlots>;
@@ -10,6 +10,7 @@ defineOptions({ inheritAttrs: false });
10
10
  const props = defineProps({
11
11
  navigation: { type: Array, required: true },
12
12
  color: { type: null, required: false },
13
+ collapsed: { type: Boolean, required: false },
13
14
  isNested: { type: Boolean, required: false },
14
15
  ui: { type: Object, required: false }
15
16
  });
@@ -37,19 +38,22 @@ const groupUi = computed(() => ({
37
38
  <Accordion
38
39
  v-if="link.children?.length"
39
40
  :items="[{ value: link.path, label: link.title }]"
40
- :default-value="[link.path]"
41
+ :default-value="link.collapse ?? collapsed ? [] : [link.path]"
41
42
  :ui="groupUi"
42
43
  >
43
44
  <template #label>
44
45
  <slot name="link" :link="link" :active="false">
45
46
  <Icon v-if="link.icon" :name="link.icon" v-bind="resolveSlot(ui.icon, props.ui?.icon)" />
46
47
  {{ link.title }}
48
+ <span v-for="(badge, index) in link.navBadges" :key="`${link.path}:badge:${index}`" v-bind="resolveSlot(ui.badge, props.ui?.badge)">
49
+ {{ typeof badge === "string" ? badge : badge.text }}
50
+ </span>
47
51
  </slot>
48
52
  </template>
49
53
  <template #[link.path]>
50
54
  <!-- Vue's SFC self-recursion resolves by this file's own bare name -
51
55
  keep it unprefixed even though the public component is SContentNavigation. -->
52
- <ContentNavigation :navigation="link.children" is-nested :color="color" :ui="props.ui">
56
+ <ContentNavigation :navigation="link.children" is-nested :color="color" :collapsed="collapsed" :ui="props.ui">
53
57
  <template #link="scope">
54
58
  <slot name="link" v-bind="scope" />
55
59
  </template>
@@ -60,6 +64,9 @@ const groupUi = computed(() => ({
60
64
  <slot name="link" :link="link" :active="isActive(link)">
61
65
  <Icon v-if="link.icon" :name="link.icon" v-bind="resolveSlot(ui.icon, props.ui?.icon)" />
62
66
  {{ link.title }}
67
+ <span v-for="(badge, index) in link.navBadges" :key="`${link.path}:badge:${index}`" v-bind="resolveSlot(ui.badge, props.ui?.badge)">
68
+ {{ typeof badge === "string" ? badge : badge.text }}
69
+ </span>
63
70
  </slot>
64
71
  </NuxtLink>
65
72
  </li>
@@ -4,7 +4,12 @@ import type { UiProp } from '../utils/ui.js';
4
4
  export interface ContentNavigationLink {
5
5
  title: string;
6
6
  path: string;
7
- icon?: string;
7
+ icon?: string | false;
8
+ order?: number;
9
+ collapse?: boolean;
10
+ navBadges?: Array<string | {
11
+ text: string;
12
+ }>;
8
13
  children?: ContentNavigationLink[];
9
14
  }
10
15
  type __VLS_Slots = ContentNavigationSlots;
@@ -12,6 +17,8 @@ export interface ContentNavigationProps {
12
17
  navigation: ContentNavigationLink[];
13
18
  /** The active-link accent. @default 'primary' */
14
19
  color?: ColorRole;
20
+ /** Collapse every group initially unless the item's own `collapse` metadata overrides it. @default false */
21
+ collapsed?: boolean;
15
22
  /** Set only by this component's own recursive self-call, one level down for each nested group - gates the tree-connector rail (see `content-navigation.ts`'s own `isNested` variant), since a root-level entry has no parent trunk to its left to branch off of. */
16
23
  isNested?: boolean;
17
24
  ui?: UiProp<ContentNavigationThemeSlots>;
@@ -119,27 +119,34 @@ function buildRailPath(links, height) {
119
119
  const x2 = RAIL_X_OUTER + index * indentStep;
120
120
  return Math.round(clamp(x2, RAIL_X_OUTER, RAIL_X_INNER_MAX));
121
121
  };
122
+ const curveHalf = RAIL_CURVE_SPAN / 2;
122
123
  let x = xForDepth(items[0]?.depth ?? baseDepth);
124
+ let yPath = 0;
123
125
  let yPrev = 0;
124
126
  let d = `M ${x} 0`;
125
127
  for (const item of items) {
126
128
  const y = clamp(item.y, 0, height);
127
129
  const xNext = xForDepth(item.depth);
128
130
  if (xNext === x) {
129
- if (y > yPrev)
131
+ if (y > yPath) {
130
132
  d += ` L ${x} ${y}`;
131
- yPrev = Math.max(yPrev, y);
133
+ yPath = y;
134
+ }
135
+ yPrev = y;
132
136
  continue;
133
137
  }
134
- const y0 = clamp(y - RAIL_CURVE_SPAN, yPrev, height);
135
- const midY = (y0 + y) / 2;
136
- if (y0 > yPrev)
138
+ const midY = (yPrev + y) / 2;
139
+ const y0 = clamp(midY - curveHalf, yPath, y);
140
+ const y1 = clamp(midY + curveHalf, y0, y);
141
+ const controlY = (y0 + y1) / 2;
142
+ if (y0 > yPath)
137
143
  d += ` L ${x} ${y0}`;
138
- d += ` C ${x} ${midY} ${xNext} ${midY} ${xNext} ${y}`;
144
+ d += ` C ${x} ${controlY} ${xNext} ${controlY} ${xNext} ${y1}`;
139
145
  x = xNext;
146
+ yPath = y1;
140
147
  yPrev = y;
141
148
  }
142
- if (height > yPrev)
149
+ if (height > yPath)
143
150
  d += ` L ${x} ${height}`;
144
151
  return d;
145
152
  }
@@ -1,4 +1,5 @@
1
1
  import type { ContextMenuThemeSlots } from '../theme/context-menu.js';
2
+ import type { ContextMenuPositioning, OverlayPortal } from '../utils/overlay.js';
2
3
  import type { UiProp } from '../utils/ui.js';
3
4
  export interface ContextMenuItemDef {
4
5
  label: string;
@@ -7,12 +8,18 @@ export interface ContextMenuItemDef {
7
8
  /** Styles this item for a delete/remove-style action (danger text, danger-tinted hover) - matches Dropdown's own `destructive` flag. */
8
9
  destructive?: boolean;
9
10
  onSelect?: () => void;
10
- /** Not read by ContextMenu's own default item rendering - carried purely so a custom #item slot override can display one. */
11
+ /** Displays a keyboard shortcut hint. */
11
12
  shortcut?: string;
13
+ /** Bind `shortcut` while this menu is open; activation follows the normal menu item click path. */
14
+ hotkey?: boolean;
12
15
  }
13
16
  type __VLS_Slots = ContextMenuSlots;
14
17
  export interface ContextMenuProps {
15
18
  items: ContextMenuItemDef[][];
19
+ /** Collision and offset controls for the menu anchored to the right-click point. */
20
+ positioning?: ContextMenuPositioning;
21
+ /** Teleport target, or `false` to render inline. Defaults to `body`. */
22
+ portal?: OverlayPortal;
16
23
  ui?: UiProp<ContextMenuThemeSlots>;
17
24
  }
18
25
  export interface ContextMenuSlots {
@@ -1,18 +1,25 @@
1
1
  <script setup>
2
2
  import { ContextMenuContent, ContextMenuItem, ContextMenuPortal, ContextMenuRoot, ContextMenuSeparator, ContextMenuTrigger } from "reka-ui";
3
- import { computed } from "vue";
3
+ import { computed, ref } from "vue";
4
+ import { vHotkey } from "../directives/hotkey";
5
+ import ShortcutHint from "../internal/ShortcutHint.vue";
4
6
  import { contextMenuTheme } from "../theme/context-menu";
7
+ import { overlayPortalProps } from "../utils/overlay";
5
8
  import { resolveSlot, useComponentTheme, useThemeBindings } from "../utils/ui";
6
9
  import Icon from "./Icon.vue";
7
10
  const props = defineProps({
8
11
  items: { type: Array, required: true },
12
+ positioning: { type: Object, required: false },
13
+ portal: { type: [Boolean, String], required: false, skipCheck: true, default: void 0 },
9
14
  ui: { type: Object, required: false }
10
15
  });
11
16
  defineSlots();
12
17
  const theme = useComponentTheme("contextMenu", contextMenuTheme);
18
+ const open = ref(false);
13
19
  const themeBindings = useThemeBindings();
14
20
  const ui = computed(() => theme.value());
15
- const contentProps = computed(() => resolveSlot(ui.value.content, props.ui?.content));
21
+ const contentProps = computed(() => ({ ...resolveSlot(ui.value.content, props.ui?.content), ...props.positioning }));
22
+ const portalProps = computed(() => overlayPortalProps(props.portal));
16
23
  function itemPropsFor(item) {
17
24
  return resolveSlot(theme.value({ destructive: item.destructive }).item, props.ui?.item);
18
25
  }
@@ -20,20 +27,24 @@ function iconPropsFor(item) {
20
27
  return resolveSlot(theme.value({ destructive: item.destructive }).icon, props.ui?.icon);
21
28
  }
22
29
  const separatorProps = computed(() => resolveSlot(ui.value.separator, props.ui?.separator));
30
+ function hotkeyFor(item) {
31
+ return item.hotkey && item.shortcut && !item.disabled ? { keys: item.shortcut, when: () => open.value } : void 0;
32
+ }
23
33
  </script>
24
34
 
25
35
  <template>
26
- <ContextMenuRoot>
36
+ <ContextMenuRoot :open="open" @update:open="open = $event">
27
37
  <ContextMenuTrigger as-child>
28
38
  <slot />
29
39
  </ContextMenuTrigger>
30
- <ContextMenuPortal>
40
+ <ContextMenuPortal v-bind="portalProps">
31
41
  <ContextMenuContent :data-selaras-theme="themeBindings['data-selaras-theme']" :data-selaras-mode="themeBindings['data-selaras-mode']" :style="themeBindings.style" v-bind="contentProps">
32
42
  <template v-for="(group, groupIndex) in items" :key="groupIndex">
33
43
  <ContextMenuSeparator v-if="groupIndex > 0" v-bind="separatorProps" />
34
44
  <ContextMenuItem
35
45
  v-for="(item, itemIndex) in group"
36
46
  :key="itemIndex"
47
+ v-hotkey="hotkeyFor(item)"
37
48
  :disabled="item.disabled"
38
49
  v-bind="itemPropsFor(item)"
39
50
  @select="item.onSelect?.()"
@@ -41,6 +52,7 @@ const separatorProps = computed(() => resolveSlot(ui.value.separator, props.ui?.
41
52
  <Icon v-if="item.icon" :name="item.icon" v-bind="iconPropsFor(item)" />
42
53
  <slot name="item" :item="item">
43
54
  {{ item.label }}
55
+ <ShortcutHint :shortcut="item.shortcut" />
44
56
  </slot>
45
57
  </ContextMenuItem>
46
58
  </template>
@@ -1,4 +1,5 @@
1
1
  import type { ContextMenuThemeSlots } from '../theme/context-menu.js';
2
+ import type { ContextMenuPositioning, OverlayPortal } from '../utils/overlay.js';
2
3
  import type { UiProp } from '../utils/ui.js';
3
4
  export interface ContextMenuItemDef {
4
5
  label: string;
@@ -7,12 +8,18 @@ export interface ContextMenuItemDef {
7
8
  /** Styles this item for a delete/remove-style action (danger text, danger-tinted hover) - matches Dropdown's own `destructive` flag. */
8
9
  destructive?: boolean;
9
10
  onSelect?: () => void;
10
- /** Not read by ContextMenu's own default item rendering - carried purely so a custom #item slot override can display one. */
11
+ /** Displays a keyboard shortcut hint. */
11
12
  shortcut?: string;
13
+ /** Bind `shortcut` while this menu is open; activation follows the normal menu item click path. */
14
+ hotkey?: boolean;
12
15
  }
13
16
  type __VLS_Slots = ContextMenuSlots;
14
17
  export interface ContextMenuProps {
15
18
  items: ContextMenuItemDef[][];
19
+ /** Collision and offset controls for the menu anchored to the right-click point. */
20
+ positioning?: ContextMenuPositioning;
21
+ /** Teleport target, or `false` to render inline. Defaults to `body`. */
22
+ portal?: OverlayPortal;
16
23
  ui?: UiProp<ContextMenuThemeSlots>;
17
24
  }
18
25
  export interface ContextMenuSlots {
@@ -2,7 +2,9 @@ import type { DateValue } from '@internationalized/date';
2
2
  import type { DateRange } from 'reka-ui';
3
3
  import type { VariantProps } from 'tailwind-variants';
4
4
  import type { DatePickerThemeSlots } from '../theme/date-picker.js';
5
+ import type { RoundedArrowConfig } from '../utils/arrow.js';
5
6
  import type { ColorRole } from '../utils/color-registry.js';
7
+ import type { OverlayPortal, OverlayPositioning } from '../utils/overlay.js';
6
8
  import type { UiProp } from '../utils/ui.js';
7
9
  import { Time } from '@internationalized/date';
8
10
  import { datePickerTheme } from '../theme/date-picker.js';
@@ -73,7 +75,12 @@ export interface DatePickerProps {
73
75
  activeColor?: ColorRole;
74
76
  size?: DatePickerVariants['size'];
75
77
  /** Shows a small pointer triangle connecting the panel to its trigger. */
76
- arrow?: boolean;
78
+ /** Shows the pointer; an object configures its size, rounding, and edge clearance. */
79
+ arrow?: boolean | RoundedArrowConfig;
80
+ /** Positioning of the anchored calendar (the adaptive modal uses its own layout). */
81
+ positioning?: OverlayPositioning;
82
+ /** Teleport target for the anchored calendar, or `false` to render it inline. */
83
+ portal?: OverlayPortal;
77
84
  /** Opts into the calendar's accessible small-screen modal presentation. It is selected when the calendar opens and held until close. */
78
85
  adaptive?: boolean;
79
86
  ui?: UiProp<DatePickerThemeSlots>;
@@ -158,11 +165,11 @@ type __VLS_Slots = {} & {
158
165
  declare const __VLS_base: import("vue").DefineComponent<DatePickerProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
159
166
  "update:modelValue": (value: DateValue | DateRange | Time | undefined) => any;
160
167
  "update:open": (value: boolean) => any;
161
- "update:view": (view: "month" | "year" | "date") => any;
168
+ "update:view": (view: "date" | "month" | "year") => any;
162
169
  }, string, import("vue").PublicProps, Readonly<DatePickerProps> & Readonly<{
163
170
  "onUpdate:modelValue"?: ((value: DateValue | DateRange | Time | undefined) => any) | undefined;
164
171
  "onUpdate:open"?: ((value: boolean) => any) | undefined;
165
- "onUpdate:view"?: ((view: "month" | "year" | "date") => any) | undefined;
172
+ "onUpdate:view"?: ((view: "date" | "month" | "year") => any) | undefined;
166
173
  }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
167
174
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
168
175
  declare const _default: typeof __VLS_export;