orio-ui 1.24.0 → 1.28.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 (102) hide show
  1. package/README.md +78 -3
  2. package/bin/orio-ui.mjs +72 -0
  3. package/dist/agents/ROUTING.md +140 -0
  4. package/dist/agents/component-finder.md +142 -0
  5. package/dist/agents/component-worker.md +152 -0
  6. package/dist/agents/snippet.md +6 -0
  7. package/dist/module.json +1 -1
  8. package/dist/runtime/components/AnimatedContainer.USAGE.md +79 -0
  9. package/dist/runtime/components/Badge.USAGE.md +75 -0
  10. package/dist/runtime/components/Banner.USAGE.md +52 -0
  11. package/dist/runtime/components/Button.USAGE.md +78 -0
  12. package/dist/runtime/components/Button.d.vue.ts +3 -2
  13. package/dist/runtime/components/Button.vue +19 -11
  14. package/dist/runtime/components/Button.vue.d.ts +3 -2
  15. package/dist/runtime/components/Calendar.USAGE.md +59 -0
  16. package/dist/runtime/components/Calendar.vue +254 -87
  17. package/dist/runtime/components/Canvas/USAGE.md +73 -0
  18. package/dist/runtime/components/CheckBox.USAGE.md +63 -0
  19. package/dist/runtime/components/CheckBox.vue +9 -3
  20. package/dist/runtime/components/CheckboxGroup.USAGE.md +95 -0
  21. package/dist/runtime/components/CheckboxGroup.vue +7 -1
  22. package/dist/runtime/components/ControlElement.USAGE.md +77 -0
  23. package/dist/runtime/components/ControlElement.d.vue.ts +42 -27
  24. package/dist/runtime/components/ControlElement.vue +28 -9
  25. package/dist/runtime/components/ControlElement.vue.d.ts +42 -27
  26. package/dist/runtime/components/DashedContainer.USAGE.md +65 -0
  27. package/dist/runtime/components/EmptyState.USAGE.md +65 -0
  28. package/dist/runtime/components/Form.USAGE.md +102 -0
  29. package/dist/runtime/components/Icon.USAGE.md +61 -0
  30. package/dist/runtime/components/Input.USAGE.md +57 -0
  31. package/dist/runtime/components/Input.vue +13 -3
  32. package/dist/runtime/components/ListItem.USAGE.md +84 -0
  33. package/dist/runtime/components/LoadingSpinner.USAGE.md +50 -0
  34. package/dist/runtime/components/LocaleSwitcher.USAGE.md +73 -0
  35. package/dist/runtime/components/Modal.USAGE.md +72 -0
  36. package/dist/runtime/components/NavButton.USAGE.md +80 -0
  37. package/dist/runtime/components/NavButton.d.vue.ts +0 -1
  38. package/dist/runtime/components/NavButton.vue +9 -5
  39. package/dist/runtime/components/NavButton.vue.d.ts +0 -1
  40. package/dist/runtime/components/NumberInput/Horizontal.USAGE.md +61 -0
  41. package/dist/runtime/components/NumberInput/Horizontal.vue +7 -2
  42. package/dist/runtime/components/NumberInput/USAGE.md +74 -0
  43. package/dist/runtime/components/NumberInput/Vertical.USAGE.md +55 -0
  44. package/dist/runtime/components/NumberInput/Vertical.vue +7 -2
  45. package/dist/runtime/components/NumberInput/index.d.vue.ts +0 -2
  46. package/dist/runtime/components/NumberInput/index.vue +9 -7
  47. package/dist/runtime/components/NumberInput/index.vue.d.ts +0 -2
  48. package/dist/runtime/components/Popover.USAGE.md +103 -0
  49. package/dist/runtime/components/RadioButton.USAGE.md +72 -0
  50. package/dist/runtime/components/RadioButton.d.vue.ts +0 -2
  51. package/dist/runtime/components/RadioButton.vue +9 -4
  52. package/dist/runtime/components/RadioButton.vue.d.ts +0 -2
  53. package/dist/runtime/components/Selector.USAGE.md +131 -0
  54. package/dist/runtime/components/Selector.d.vue.ts +1 -0
  55. package/dist/runtime/components/Selector.vue +10 -4
  56. package/dist/runtime/components/Selector.vue.d.ts +1 -0
  57. package/dist/runtime/components/SwitchButton.USAGE.md +62 -0
  58. package/dist/runtime/components/SwitchButton.d.vue.ts +1 -4
  59. package/dist/runtime/components/SwitchButton.vue +10 -7
  60. package/dist/runtime/components/SwitchButton.vue.d.ts +1 -4
  61. package/dist/runtime/components/Tag.USAGE.md +51 -0
  62. package/dist/runtime/components/TaggableSelector.USAGE.md +73 -0
  63. package/dist/runtime/components/TaggableSelector.vue +7 -1
  64. package/dist/runtime/components/Textarea.USAGE.md +72 -0
  65. package/dist/runtime/components/Textarea.vue +13 -3
  66. package/dist/runtime/components/Tooltip.USAGE.md +84 -0
  67. package/dist/runtime/components/ZoomableContainer.USAGE.md +108 -0
  68. package/dist/runtime/components/date/Picker.USAGE.md +52 -0
  69. package/dist/runtime/components/date/Picker.vue +7 -1
  70. package/dist/runtime/components/date/PickerTrigger.USAGE.md +65 -0
  71. package/dist/runtime/components/date/PickerTrigger.vue +9 -3
  72. package/dist/runtime/components/date/RangePicker.USAGE.md +97 -0
  73. package/dist/runtime/components/date/RangePicker.vue +7 -1
  74. package/dist/runtime/components/gallery/Carousel.USAGE.md +98 -0
  75. package/dist/runtime/components/gallery/CarouselPreview.USAGE.md +51 -0
  76. package/dist/runtime/components/upload/USAGE.md +91 -0
  77. package/dist/runtime/components/view/Dates.USAGE.md +67 -0
  78. package/dist/runtime/components/view/KeyBinds.USAGE.md +58 -0
  79. package/dist/runtime/components/view/Separator.USAGE.md +57 -0
  80. package/dist/runtime/components/view/Text.USAGE.md +68 -0
  81. package/dist/runtime/composables/useApi.USAGE.md +64 -0
  82. package/dist/runtime/composables/useControlSize.USAGE.md +73 -0
  83. package/dist/runtime/composables/useDecimalFormatter.USAGE.md +72 -0
  84. package/dist/runtime/composables/useFilter.USAGE.md +120 -0
  85. package/dist/runtime/composables/useFilter.d.ts +91 -0
  86. package/dist/runtime/composables/useFilter.js +111 -0
  87. package/dist/runtime/composables/useFuzzySearch.USAGE.md +68 -0
  88. package/dist/runtime/composables/useInertia.USAGE.md +80 -0
  89. package/dist/runtime/composables/useListKeyboard.USAGE.md +97 -0
  90. package/dist/runtime/composables/useModal.USAGE.md +82 -0
  91. package/dist/runtime/composables/usePinchZoom.USAGE.md +95 -0
  92. package/dist/runtime/composables/usePressAndHold.USAGE.md +70 -0
  93. package/dist/runtime/composables/useRovingGrid.USAGE.md +106 -0
  94. package/dist/runtime/composables/useRovingGrid.d.ts +35 -0
  95. package/dist/runtime/composables/useRovingGrid.js +115 -0
  96. package/dist/runtime/composables/useSound.USAGE.md +74 -0
  97. package/dist/runtime/composables/useTheme.USAGE.md +76 -0
  98. package/dist/runtime/composables/useUrlSync.USAGE.md +91 -0
  99. package/dist/runtime/composables/useValidation.USAGE.md +100 -0
  100. package/dist/runtime/i18n/en.json +4 -1
  101. package/dist/runtime/i18n/uk.json +4 -1
  102. package/package.json +12 -2
@@ -0,0 +1,79 @@
1
+ ---
2
+ kind: component
3
+ category: Layout & containers
4
+ purpose: animation wrapper, fade/slide a slot, animated list, mount-stagger layout
5
+ short: flex container that fade-slides its direct children up on mount and exposes a sound `play` callback
6
+ invariants: true
7
+ ---
8
+
9
+ # AnimatedContainer — agent-only invariants
10
+
11
+ `<orio-animated-container>` is a flex layout that runs a fade-in-up CSS
12
+ animation on every direct child when it mounts. It is **not** a Vue
13
+ `<Transition>` — there is no leave animation and reactive state changes do
14
+ not retrigger it.
15
+
16
+ ## Invariants
17
+
18
+ - **Animation fires on mount only.** The `containerFadeInUp` keyframe is a
19
+ plain CSS animation on `.animated-container > *`. To replay, change
20
+ `:key` on the container (or on the child). Re-rendering reactive content
21
+ inside the slot does **not** restart the animation.
22
+ - **Only direct children animate.** The selector is `> *`. Wrapping children
23
+ in an extra `<div>` moves the animation up to the wrapper. Nested deep
24
+ trees do not animate per-node.
25
+ - **Layout is opinionated.** The container is `display: flex; flex-wrap:
26
+ wrap; justify-content: center; gap: 1rem; padding-inline: 1rem;`. Treat it
27
+ as a layout primitive, not a transparent wrapper.
28
+ - **`direction` toggles flex-direction _and_ justification.** `"row"`
29
+ (default) → wrap + centered. `"column"` → `flex-direction: column` +
30
+ `justify-content: flex-start`.
31
+ - **`play` slot prop is `useSound().play`.** The default slot exposes
32
+ `{ play }`. Wire it to `@mouseenter` / `@click` on children for hover or
33
+ tap feedback. There is no prop to disable or swap the sound — it is
34
+ global `useSound`.
35
+
36
+ ## Gotchas
37
+
38
+ - **No leave animation.** Items removed from the slot disappear instantly.
39
+ If you need exit animation, wrap each item in `<Transition>` or use a
40
+ different primitive.
41
+ - **Newly inserted children animate independently.** When the slot's child
42
+ list grows (v-for over a reactive array), each new node animates on its
43
+ own mount — there is no list-coordinated stagger.
44
+ - **Built-in horizontal padding bleeds.** `padding-inline: 1rem` is on the
45
+ container. If the consumer wraps it in a tight column, the inner content
46
+ starts 1rem in. Override with `:deep(.animated-container)` or pad the
47
+ parent.
48
+ - **`direction="column"` does not stretch children.** They still wrap by
49
+ default (`flex-wrap: wrap` is unchanged). Children narrower than the
50
+ container will not fill the row.
51
+
52
+ ## Quick reference
53
+
54
+ ```vue
55
+ <script setup lang="ts">
56
+ import { ref } from "vue";
57
+
58
+ const animationKey = ref(0);
59
+ function replay() {
60
+ animationKey.value += 1;
61
+ }
62
+ </script>
63
+
64
+ <template>
65
+ <orio-button @click="replay">Replay</orio-button>
66
+
67
+ <orio-animated-container :key="animationKey" direction="column" v-slot="{ play }">
68
+ <div v-for="item in items" :key="item.id" @mouseenter="play">
69
+ {{ item.label }}
70
+ </div>
71
+ </orio-animated-container>
72
+ </template>
73
+ ```
74
+
75
+ ## Related
76
+
77
+ - `useSound` composable — the source of the `play` callback. See
78
+ `docs/composables/use-sound.md`.
79
+ - Public API reference: `docs/components/animated-container.md`.
@@ -0,0 +1,75 @@
1
+ ---
2
+ kind: component
3
+ category: Buttons & indicators
4
+ purpose: badge, small status pill, notification dot, count indicator, corner badge
5
+ short: small status pill or dot indicator; optionally positioned in the top-right corner of a wrapped element
6
+ invariants: true
7
+ ---
8
+
9
+ # Badge — agent-only invariants
10
+
11
+ `<orio-badge>` renders one of two shapes:
12
+ - A standalone inline status pill / dot.
13
+ - A **positioned** badge anchored to the top-right corner of an element
14
+ passed via the `#wrapping` slot (typical notification-on-icon pattern).
15
+
16
+ ## Invariants
17
+
18
+ - **`#wrapping` slot toggles positioning mode.** When provided, the badge
19
+ is rendered absolutely at `top: 15%; right: 15%; transform: translate(50%, -50%)`
20
+ on top of the wrapped element. Without `#wrapping`, the badge is plain
21
+ inline.
22
+ - **Default slot determines dot vs text mode.** Empty default slot → dot
23
+ badge (0.5rem circle, no padding). Any content → text/number badge.
24
+ - **`variant`**: `"primary"` (default), `"danger"`, `"alert"`, `"grey"`.
25
+ Maps to accent / danger / alert / surface color tokens.
26
+ - **`pill` prop**: switches the border-radius to pill shape; ignored when
27
+ in dot mode.
28
+ - **`hidden` prop**: gates the badge render via `v-if`. The wrapped slot
29
+ still renders when in wrapping mode — only the indicator hides.
30
+ - **No `count` prop.** Use the default slot for the number: `<orio-badge>3</orio-badge>`.
31
+
32
+ ## Gotchas
33
+
34
+ - **No automatic "99+" cap.** Long content (e.g. `1234`) renders fully,
35
+ pushing the corner badge wider. Cap in the consumer.
36
+ - **Positioned badge offsets are percentages of the wrapping element.**
37
+ Tiny wrapped icons may have the badge clip outside the wrapper. The
38
+ wrapper is `position: relative; display: inline-flex` — `overflow: visible`
39
+ is implicit, but parent overflow may clip it.
40
+ - **No interactive behavior.** Click does not bubble specially; it's a
41
+ `<span>`. For removable chips, use `<orio-tag>` instead.
42
+
43
+ ## Quick reference — corner badge on an icon
44
+
45
+ ```vue
46
+ <template>
47
+ <orio-badge variant="danger">
48
+ <template #wrapping>
49
+ <orio-button icon="bell" variant="subdued" />
50
+ </template>
51
+ {{ unreadCount }}
52
+ </orio-badge>
53
+
54
+ <orio-badge variant="alert" :hidden="!hasUpdates">
55
+ <template #wrapping>
56
+ <orio-icon name="refresh" />
57
+ </template>
58
+ </orio-badge>
59
+ </template>
60
+ ```
61
+
62
+ ## Quick reference — inline status pill
63
+
64
+ ```vue
65
+ <template>
66
+ <orio-badge variant="grey" pill>{{ $t("status.draft") }}</orio-badge>
67
+ <orio-badge variant="primary">{{ $t("status.live") }}</orio-badge>
68
+ </template>
69
+ ```
70
+
71
+ ## Related
72
+
73
+ - `<orio-tag>` — chip with text and optional `id` / variant for filters
74
+ and selections.
75
+ - Public API reference: `docs/components/badge.md`.
@@ -0,0 +1,52 @@
1
+ ---
2
+ kind: component
3
+ category: Buttons & indicators
4
+ purpose: banner, page-level notice, alert strip, inline notification, info bar
5
+ short: page-level notice strip with danger/alert/success/info variants; default slot for content
6
+ invariants: true
7
+ ---
8
+
9
+ # Banner — agent-only invariants
10
+
11
+ `<orio-banner>` is a plain styled `<div>` for page-level notices. No icon,
12
+ no close button, no auto-dismiss — bring your own.
13
+
14
+ ## Invariants
15
+
16
+ - **`variant`**: `"info"` (default), `"success"`, `"alert"`, `"danger"`.
17
+ Each maps to a soft-background + matching border color from the
18
+ project's color tokens.
19
+ - **Default slot is the entire body.** Text, links, action buttons —
20
+ whatever fits the layout.
21
+ - **Padding (`0.75rem 1rem`) and border-radius (`--border-radius-sm`)
22
+ are fixed.** No size variants.
23
+
24
+ ## Gotchas
25
+
26
+ - **No semantic role / ARIA live region.** The `<div>` has no
27
+ `role="alert"` or `role="status"`. For screen-reader-announced
28
+ notices, add `role="alert"` (urgent) or `role="status"` (polite) via
29
+ `$attrs`.
30
+ - **No close button.** Pair with `v-if` on the consumer side for
31
+ dismissable banners.
32
+ - **No icon prop.** Render `<orio-icon>` inside the slot if needed —
33
+ the banner does not auto-prepend a variant-matching glyph.
34
+
35
+ ## Quick reference
36
+
37
+ ```vue
38
+ <template>
39
+ <orio-banner variant="alert" role="alert" v-if="paymentFailed">
40
+ <strong>{{ $t("billing.failed.title") }}</strong>
41
+ <orio-button variant="subdued" @click="retry">
42
+ {{ $t("billing.failed.retry") }}
43
+ </orio-button>
44
+ </orio-banner>
45
+ </template>
46
+ ```
47
+
48
+ ## Related
49
+
50
+ - `<orio-empty-state>` — for empty-list placeholders.
51
+ - `<orio-tooltip>` — for transient hover hints.
52
+ - Public API reference: `docs/components/banner.md`.
@@ -0,0 +1,78 @@
1
+ ---
2
+ kind: component
3
+ category: Buttons & indicators
4
+ purpose: button, primary action, CTA, icon button, action button
5
+ short: primary action button with variants, loading, icon slots, and auto icon-only sizing
6
+ invariants: true
7
+ ---
8
+
9
+ # Button — agent-only invariants
10
+
11
+ `<orio-button>` is the primary action button. Wraps `<orio-control-element>`
12
+ so it can carry a label/error like other form controls, but it is most
13
+ often used standalone.
14
+
15
+ ## Invariants
16
+
17
+ - **`variant`**: `"primary"` (default), `"secondary"`, `"subdued"`. Primary
18
+ is filled; secondary is outline; subdued is bare text. All use the
19
+ global `gradient-hover` class for the hover treatment.
20
+ - **`icon` prop OR `#icon` slot**: pass an icon name (registered in
21
+ `utils/iconRegistry`) OR slot in arbitrary content before the label.
22
+ Slot wins.
23
+ - **`#icon-right` slot**: trailing icon, rendered after the default slot.
24
+ - **`loading` prop**: renders `<orio-loading-spinner>` in place of all
25
+ slot content (icon, label, icon-right are all hidden). Clicks and
26
+ mousedown are blocked while loading.
27
+ - **Icon-only mode is auto-detected.** When an icon is present and the
28
+ default slot is empty, `.icon-only` is applied → `aspect-ratio: 1`,
29
+ `line-height: 0`. No need to pass a prop.
30
+ - **`disabled` blocks `click` and `mousedown` emits.** `mouseup` and
31
+ `mouseleave` always fire (so press-and-hold callers can release
32
+ state).
33
+ - **Emits**: `click`, `mousedown`, `mouseup`, `mouseleave`. Only `click`
34
+ and `mousedown` honor the loading/disabled gates.
35
+ - **Wraps ControlElement** — supports `label`, `error`, `size`, `layout`,
36
+ etc. The control bag is spread on the inner `<button>` along with
37
+ `$attrs`.
38
+
39
+ ## Gotchas
40
+
41
+ - **`$attrs` are duplicated.** Because ControlElement does **not** set
42
+ `inheritAttrs: false` and Button also spreads `$attrs` on the inner
43
+ `<button>`, an attr like `data-test="x"` may appear on both the
44
+ wrapper `<div>` (from ControlElement's root) and the inner `<button>`.
45
+ For attrs that must be unique (e.g. `data-key` for list keying,
46
+ `tabindex` override), bind a plain native `<button>` instead of
47
+ `<orio-button>`.
48
+ - **`type` defaults to `submit`** (native default). Inside an
49
+ `<orio-form>`, every `<orio-button>` will submit unless you pass
50
+ `type="button"`.
51
+ - **Loading hides the icon and the icon-right slot.** No way to keep
52
+ the trailing icon while showing a spinner — render the spinner
53
+ yourself in `#icon-right` if you need that.
54
+ - **No `aria-busy` on loading.** Set it via `$attrs` if you need
55
+ screen-reader signal.
56
+
57
+ ## Quick reference
58
+
59
+ ```vue
60
+ <template>
61
+ <orio-button @click="onSave" :loading="saving">
62
+ {{ $t("common.save") }}
63
+ </orio-button>
64
+
65
+ <orio-button variant="secondary" icon="trash" @click="onDelete">
66
+ {{ $t("common.delete") }}
67
+ </orio-button>
68
+
69
+ <orio-button variant="subdued" icon="close" aria-label="Close" />
70
+ </template>
71
+ ```
72
+
73
+ ## Related
74
+
75
+ - `<orio-nav-button>` — link-styled button with `active` state.
76
+ - `<orio-loading-spinner>` — used internally when `loading` is true.
77
+ - `<orio-icon>` — used internally for the `icon` prop and slots.
78
+ - Public API reference: `docs/components/button.md`.
@@ -3,13 +3,14 @@ interface Props extends ControlProps {
3
3
  variant?: "primary" | "secondary" | "subdued";
4
4
  icon?: string;
5
5
  loading?: boolean;
6
- disabled?: boolean;
7
6
  }
8
- declare var __VLS_13: {}, __VLS_20: {};
7
+ declare var __VLS_13: {}, __VLS_20: {}, __VLS_22: {};
9
8
  type __VLS_Slots = {} & {
10
9
  icon?: (props: typeof __VLS_13) => any;
11
10
  } & {
12
11
  default?: (props: typeof __VLS_20) => any;
12
+ } & {
13
+ 'icon-right'?: (props: typeof __VLS_22) => any;
13
14
  };
14
15
  declare const __VLS_base: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
15
16
  click: (event: PointerEvent) => any;
@@ -4,7 +4,6 @@ const props = defineProps({
4
4
  variant: { type: String, required: false, default: "primary" },
5
5
  icon: { type: String, required: false },
6
6
  loading: { type: Boolean, required: false },
7
- disabled: { type: Boolean, required: false },
8
7
  appearance: { type: String, required: false },
9
8
  error: { type: [String, null], required: false },
10
9
  group: { type: Boolean, required: false },
@@ -12,7 +11,13 @@ const props = defineProps({
12
11
  label: { type: String, required: false },
13
12
  layout: { type: String, required: false },
14
13
  size: { type: String, required: false },
15
- fill: { type: Boolean, required: false }
14
+ fill: { type: Boolean, required: false },
15
+ tabindex: { type: [Number, String], required: false },
16
+ focusKey: { type: String, required: false },
17
+ disabled: { type: Boolean, required: false },
18
+ required: { type: Boolean, required: false },
19
+ name: { type: String, required: false },
20
+ ariaLabel: { type: String, required: false }
16
21
  });
17
22
  const { loading, disabled } = toRefs(props);
18
23
  const slots = useSlots();
@@ -39,21 +44,25 @@ function onMouseleave(event) {
39
44
  </script>
40
45
 
41
46
  <template>
42
- <orio-control-element v-bind="props">
47
+ <orio-control-element v-slot="{ control }" v-bind="props">
43
48
  <button
44
- v-bind="$attrs"
49
+ v-bind="{ ...$attrs, ...control }"
45
50
  :class="[variant, 'gradient-hover', { 'icon-only': isIconOnly }]"
46
- :disabled
47
51
  @click="click"
48
52
  @mousedown="onMousedown"
49
53
  @mouseup="onMouseup"
50
54
  @mouseleave="onMouseleave"
51
55
  >
52
56
  <orio-loading-spinner v-if="loading" />
53
- <slot v-else name="icon">
54
- <orio-icon v-if="icon" :name="icon" />
55
- </slot>
56
- <slot />
57
+ <template v-else>
58
+ <slot name="icon">
59
+ <orio-icon v-if="icon" :name="icon" />
60
+ </slot>
61
+
62
+ <slot />
63
+
64
+ <slot name="icon-right" />
65
+ </template>
57
66
  </button>
58
67
  </orio-control-element>
59
68
  </template>
@@ -73,9 +82,8 @@ button {
73
82
  user-select: none;
74
83
  }
75
84
  button.icon-only {
76
- padding: 0;
77
- border-radius: 50%;
78
85
  line-height: 0;
86
+ aspect-ratio: 1;
79
87
  }
80
88
  button:disabled, button:disabled:hover {
81
89
  background-color: var(--color-accent-soft-base);
@@ -3,13 +3,14 @@ interface Props extends ControlProps {
3
3
  variant?: "primary" | "secondary" | "subdued";
4
4
  icon?: string;
5
5
  loading?: boolean;
6
- disabled?: boolean;
7
6
  }
8
- declare var __VLS_13: {}, __VLS_20: {};
7
+ declare var __VLS_13: {}, __VLS_20: {}, __VLS_22: {};
9
8
  type __VLS_Slots = {} & {
10
9
  icon?: (props: typeof __VLS_13) => any;
11
10
  } & {
12
11
  default?: (props: typeof __VLS_20) => any;
12
+ } & {
13
+ 'icon-right'?: (props: typeof __VLS_22) => any;
13
14
  };
14
15
  declare const __VLS_base: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
15
16
  click: (event: PointerEvent) => any;
@@ -0,0 +1,59 @@
1
+ ---
2
+ kind: component
3
+ category: Date
4
+ purpose: month calendar, date grid, day picker UI
5
+ short: month grid with roving-focus keyboard a11y
6
+ invariants: true
7
+ ---
8
+
9
+ # Calendar — agent-only invariants
10
+
11
+ `<orio-calendar>` is the month-grid primitive. `date/Picker.vue` and
12
+ `date/RangePicker.vue` compose it inside a popover.
13
+
14
+ ## Invariants
15
+
16
+ - **All dates are ISO `YYYY-MM-DD` strings** at the API boundary
17
+ (`selected`, `markers.start`/`end`, `getMarker`, `isDisabled`, `@select`,
18
+ `@dayEnter`). Never pass `Date` objects.
19
+ - **Two reactive states**:
20
+ - `selected` (prop) — the picked day. Calendar does not own it; emit
21
+ `@select` and let the parent update its v-model.
22
+ - `anchor` (v-model:anchor) — the visible month. Calendar owns this when
23
+ uncontrolled, derived from `selected` if not provided. Pass it as
24
+ `v-model:anchor` if you need cross-component sync (RangePicker uses
25
+ this to keep two months in lockstep).
26
+ - **Keyboard a11y via `useRovingGrid`.** Arrow keys, Home/End, PageUp/Down
27
+ navigate. Only one day cell is in the tab order at a time. Do not add
28
+ manual `tabindex` to day cells from outside.
29
+ - **42-cell grid (6 rows × 7 cols).** Leading/trailing days from neighbour
30
+ months are rendered with `inMonth: false`. Selection and disabled checks
31
+ apply to them too — `isDisabled` is called for every cell.
32
+ - **Markers are matched in reverse order** — the last marker in the array
33
+ wins on overlap. `getMarker` takes precedence over `markers[]` when both
34
+ are provided.
35
+ - **`weekStartsOn`** is `0` (Sunday) or `1` (Monday, default). Weekday
36
+ header labels are derived via `Intl.DateTimeFormat` in the active i18n
37
+ locale.
38
+
39
+ ## Gotchas
40
+
41
+ - Disabled-day logic should be reactive — `isDisabled` is called inside a
42
+ computed. Wrap any external state it reads in `computed`/`ref` or it will
43
+ not re-render on change.
44
+ - The Calendar emits `@dayEnter` on hover/keyboard-focus, **not** on
45
+ selection. Use for range-picking previews; selection is only `@select`.
46
+ - i18n keys used: `calendar.previousYear`, `calendar.previousMonth`,
47
+ `calendar.nextMonth`, `calendar.nextYear`. Override these if you ship a
48
+ locale not bundled in `runtime/i18n/`.
49
+
50
+ ## Quick reference
51
+
52
+ ```vue
53
+ <orio-calendar
54
+ :selected="iso"
55
+ :markers="[{ variant: 'accent', start: '2026-06-01', end: '2026-06-07' }]"
56
+ :is-disabled="(iso) => iso < todayIso"
57
+ @select="iso = $event"
58
+ />
59
+ ```