vunor 0.3.2 → 0.4.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 (57) hide show
  1. package/README.md +6 -1
  2. package/dist/AppLayout.mjs +17 -68
  3. package/dist/AppToasts.d.mts +3 -0
  4. package/dist/AppToasts.mjs +59 -76
  5. package/dist/Button.d.mts +26 -10
  6. package/dist/Button.mjs +95 -60
  7. package/dist/ButtonBase.mjs +26 -40
  8. package/dist/Calendar.d.mts +14 -2
  9. package/dist/Calendar.mjs +37 -85
  10. package/dist/Card.mjs +6 -25
  11. package/dist/CardHeader.mjs +3 -12
  12. package/dist/Checkbox.d.mts +7 -2
  13. package/dist/Checkbox.mjs +37 -45
  14. package/dist/Combobox.d.mts +24 -8
  15. package/dist/Combobox.mjs +274 -350
  16. package/dist/DatePicker.d.mts +18 -4
  17. package/dist/DatePicker.mjs +93 -216
  18. package/dist/DatePickerBase.d.mts +21 -8
  19. package/dist/DatePickerBase.mjs +108 -257
  20. package/dist/DatePickerPopup.d.mts +27 -2
  21. package/dist/DatePickerPopup.mjs +62 -13
  22. package/dist/DelayedSwitch.mjs +3 -14
  23. package/dist/DevTools.mjs +377 -388
  24. package/dist/Dialog.d.mts +8 -3
  25. package/dist/Dialog.mjs +77 -107
  26. package/dist/Icon.mjs +1 -4
  27. package/dist/InnerLoading.mjs +1 -4
  28. package/dist/Input.mjs +35 -126
  29. package/dist/InputBase.mjs +28 -93
  30. package/dist/LoadingIndicator.mjs +1 -5
  31. package/dist/Menu.mjs +44 -65
  32. package/dist/MenuItem.mjs +4 -16
  33. package/dist/OverflowContainer.mjs +9 -11
  34. package/dist/Pagination.mjs +11 -29
  35. package/dist/Popover.d.mts +39 -11
  36. package/dist/Popover.mjs +80 -99
  37. package/dist/ProgressBar.d.mts +9 -2
  38. package/dist/ProgressBar.mjs +11 -27
  39. package/dist/RadioGroup.d.mts +2 -1
  40. package/dist/RadioGroup.mjs +33 -47
  41. package/dist/Select.d.mts +24 -7
  42. package/dist/Select.mjs +94 -186
  43. package/dist/SelectBase.d.mts +26 -11
  44. package/dist/SelectBase.mjs +59 -83
  45. package/dist/Slider.d.mts +6 -1
  46. package/dist/Slider.mjs +34 -103
  47. package/dist/Tabs.mjs +13 -49
  48. package/dist/forward-root-DPGTBptG.js +125 -0
  49. package/dist/nuxt.mjs +36 -1
  50. package/dist/theme.d.mts +1 -1
  51. package/dist/theme.mjs +15 -4
  52. package/dist/{utils-6bTTIoaw.js → utils-BCBGEM10.js} +30 -1
  53. package/dist/utils.mjs +1 -1
  54. package/dist/vite.d.mts +13 -4
  55. package/dist/vite.mjs +5 -0
  56. package/package.json +24 -9
  57. package/dist/utils-Z0nPSAzb.js +0 -42
package/README.md CHANGED
@@ -379,9 +379,14 @@ export default defineConfig({
379
379
  ### Vue 3 + Vite
380
380
 
381
381
  ```bash
382
- pnpm add vunor
382
+ pnpm add vunor vue unocss reka-ui
383
383
  ```
384
384
 
385
+ `vue`, `unocss` and `reka-ui` are peer dependencies: your app provides them, so it has a
386
+ single copy of each (two copies of Vue or Reka break reactivity and SSR). npm and pnpm
387
+ install missing peers automatically, but listing them pins the versions you use.
388
+ `vue-router` is optional: `<VuButton :to>` uses the `RouterLink` your app registers.
389
+
385
390
  **vite.config.ts**
386
391
 
387
392
  ```ts
@@ -1,5 +1,4 @@
1
1
  import { computed, createCommentVNode, createElementBlock, createElementVNode, defineComponent, normalizeClass, normalizeStyle, onBeforeUnmount, onMounted, openBlock, ref, renderSlot } from "vue";
2
- import "\0stub:/Users/mavrik/code/vunor/packages/vunor/src/components/AppLayout/AppLayout.vue?vue&type=style&index=0&lang.css";
3
2
  //#region src/components/AppLayout/AppLayout.vue?vue&type=script&setup=true&lang.ts
4
3
  const _hoisted_1 = { class: "mx-auto max-w-[var(--app-max-w)] flex items-center justify-between w-full" };
5
4
  const _hoisted_2 = { class: "max-w-[var(--app-max-w)] w-full mx-auto flex" };
@@ -16,71 +15,21 @@ const _hoisted_4 = {
16
15
  var AppLayout_default = /* @__PURE__ */ defineComponent({
17
16
  __name: "AppLayout",
18
17
  props: {
19
- maxW: {
20
- type: String,
21
- required: false,
22
- default: "90rem"
23
- },
24
- headerH: {
25
- type: String,
26
- required: false,
27
- default: "3rem"
28
- },
29
- footerH: {
30
- type: String,
31
- required: false,
32
- default: "3rem"
33
- },
34
- leftW: {
35
- type: String,
36
- required: false,
37
- default: "14rem"
38
- },
39
- rightW: {
40
- type: String,
41
- required: false,
42
- default: "14rem"
43
- },
44
- footer: {
45
- type: Boolean,
46
- required: false
47
- },
48
- header: {
49
- type: Boolean,
50
- required: false
51
- },
52
- left: {
53
- type: Boolean,
54
- required: false
55
- },
56
- right: {
57
- type: Boolean,
58
- required: false
59
- },
60
- headerClass: {
61
- type: [String, Object],
62
- required: false
63
- },
64
- footerClass: {
65
- type: [String, Object],
66
- required: false
67
- },
68
- leftClass: {
69
- type: [String, Object],
70
- required: false
71
- },
72
- rightClass: {
73
- type: [String, Object],
74
- required: false
75
- },
76
- mainClass: {
77
- type: [String, Object],
78
- required: false
79
- },
80
- scrollTopOnChangeView: {
81
- type: Boolean,
82
- required: false
83
- }
18
+ maxW: { default: "90rem" },
19
+ headerH: { default: "3rem" },
20
+ footerH: { default: "3rem" },
21
+ leftW: { default: "14rem" },
22
+ rightW: { default: "14rem" },
23
+ footer: { type: Boolean },
24
+ header: { type: Boolean },
25
+ left: { type: Boolean },
26
+ right: { type: Boolean },
27
+ headerClass: {},
28
+ footerClass: {},
29
+ leftClass: {},
30
+ rightClass: {},
31
+ mainClass: {},
32
+ scrollTopOnChangeView: { type: Boolean }
84
33
  },
85
34
  setup(__props) {
86
35
  const props = __props;
@@ -123,7 +72,7 @@ var AppLayout_default = /* @__PURE__ */ defineComponent({
123
72
  }, [__props.header ? (openBlock(), createElementBlock("header", {
124
73
  key: 0,
125
74
  class: normalizeClass(["sticky top-0 flex-shrink-0 h-[var(--app-header-h)] w-full flex", __props.headerClass])
126
- }, [createElementVNode("div", _hoisted_1, [renderSlot(_ctx.$slots, "header")])], 2)) : createCommentVNode("v-if", true), createElementVNode("div", _hoisted_2, [
75
+ }, [createElementVNode("div", _hoisted_1, [renderSlot(_ctx.$slots, "header")])], 2)) : createCommentVNode("", true), createElementVNode("div", _hoisted_2, [
127
76
  createElementVNode("aside", _hoisted_3, [createElementVNode("div", { class: normalizeClass(["min-h-full", __props.leftClass]) }, [renderSlot(_ctx.$slots, "left")], 2)]),
128
77
  createElementVNode("main", {
129
78
  class: normalizeClass(["flex-1 overflow-auto lg:pl-[var(--app-left-w)] lg:pr-[var(--app-right-w)]", __props.mainClass]),
@@ -134,7 +83,7 @@ var AppLayout_default = /* @__PURE__ */ defineComponent({
134
83
  class: normalizeClass(["hidden xl:block w-[var(--app-right-w)] top-[var(--app-header-h)] overflow-auto fixed", __props.rightClass]),
135
84
  style: "height: calc(100vh - var(--app-header-h) - var(--app-footer-h)); left: calc(50% - var(--app-right-w) + min(var(--app-max-w), 100vw) / 2);"
136
85
  }, [renderSlot(_ctx.$slots, "right")], 2)
137
- ])], 512), __props.footer ? (openBlock(), createElementBlock("footer", _hoisted_4, [..._cache[0] || (_cache[0] = [createElementVNode("div", { class: "mx-auto max-w-[var(--app-max-w)] flex items-center justify-between w-full" }, " footer ", -1)])])) : createCommentVNode("v-if", true)], 4);
86
+ ])], 512), __props.footer ? (openBlock(), createElementBlock("footer", _hoisted_4, [..._cache[0] || (_cache[0] = [createElementVNode("div", { class: "mx-auto max-w-[var(--app-max-w)] flex items-center justify-between w-full" }, " footer ", -1)])])) : createCommentVNode("", true)], 4);
138
87
  };
139
88
  }
140
89
  });
@@ -2,8 +2,11 @@ import type { ToastProviderProps } from 'reka-ui';
2
2
  import type { TVueCssClass } from 'vunor/utils';
3
3
  type __VLS_Props = ToastProviderProps & {
4
4
  class?: TVueCssClass;
5
+ /** Keys that focus the toast viewport (Reka `ToastViewport`), default F8 */
6
+ hotkey?: string[];
5
7
  };
6
8
  declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
9
+ disableSwipe: boolean;
7
10
  swipeDirection: "left" | "right" | "up" | "down";
8
11
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
9
12
  declare const _default: typeof __VLS_export;
@@ -1,4 +1,5 @@
1
- import { Fragment, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, mergeProps, normalizeClass, openBlock, renderList, toDisplayString, unref, withCtx } from "vue";
1
+ import { n as useForwardToRoot } from "./forward-root-DPGTBptG.js";
2
+ import { Fragment, createBlock, createElementBlock, createElementVNode, createVNode, defineComponent, guardReactiveProps, mergeProps, normalizeClass, normalizeProps, openBlock, renderList, toDisplayString, unref, withCtx } from "vue";
2
3
  import { ToastAction, ToastDescription, ToastProvider, ToastRoot, ToastTitle, ToastViewport } from "reka-ui";
3
4
  import { useAppToasts } from "vunor";
4
5
  import VuButton from "vunor/Button";
@@ -7,41 +8,24 @@ const _hoisted_1 = { class: "text-body-s" };
7
8
  //#endregion
8
9
  //#region src/components/AppToasts/AppToasts.vue
9
10
  var AppToasts_default = /* @__PURE__ */ defineComponent({
11
+ inheritAttrs: false,
10
12
  __name: "AppToasts",
11
13
  props: {
12
- label: {
13
- type: String,
14
- required: false
15
- },
16
- duration: {
17
- type: Number,
18
- required: false
19
- },
14
+ label: {},
15
+ duration: {},
20
16
  disableSwipe: {
21
17
  type: Boolean,
22
- required: false
23
- },
24
- swipeDirection: {
25
- type: String,
26
- required: false,
27
- default: "left"
18
+ default: void 0
28
19
  },
29
- swipeThreshold: {
30
- type: Number,
31
- required: false
32
- },
33
- class: {
34
- type: [
35
- String,
36
- Array,
37
- Object
38
- ],
39
- required: false
40
- }
20
+ swipeDirection: { default: "left" },
21
+ swipeThreshold: {},
22
+ class: {},
23
+ hotkey: {}
41
24
  },
42
25
  setup(__props) {
43
26
  const { toasts, close } = useAppToasts();
44
27
  const props = __props;
28
+ const providerProps = useForwardToRoot(ToastProvider, props);
45
29
  const defaultActions = (toast) => [{
46
30
  text: "Dismiss",
47
31
  action: () => {
@@ -49,56 +33,55 @@ var AppToasts_default = /* @__PURE__ */ defineComponent({
49
33
  }
50
34
  }];
51
35
  return (_ctx, _cache) => {
52
- return openBlock(), createBlock(unref(ToastProvider), mergeProps(props, { class: void 0 }), {
53
- default: withCtx(() => [
54
- createCommentVNode(" prettier-ignore-attribute class "),
55
- (openBlock(true), createElementBlock(Fragment, null, renderList(unref(toasts), (toast) => {
56
- return openBlock(), createBlock(unref(ToastRoot), {
57
- key: toast.id,
58
- duration: toast.duration,
59
- open: toast.open,
60
- "onUpdate:open": [($event) => toast.open = $event, (open) => {
61
- if (!open) unref(close)(toast.id);
62
- }],
63
- "data-swipe": __props.swipeDirection,
64
- class: normalizeClass([toast.class, "toast-root"])
65
- }, {
66
- default: withCtx(() => [
67
- createVNode(unref(ToastTitle), { class: "[grid-area:_title] mb-$s fw-$bold" }, {
68
- default: withCtx(() => [createElementVNode("span", null, toDisplayString(toast.title), 1)]),
69
- _: 2
70
- }, 1024),
71
- createVNode(unref(ToastDescription), { "as-child": "" }, {
72
- default: withCtx(() => [createElementVNode("span", _hoisted_1, toDisplayString(toast.message), 1)]),
36
+ return openBlock(), createBlock(unref(ToastProvider), normalizeProps(guardReactiveProps(unref(providerProps))), {
37
+ default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(unref(toasts), (toast) => {
38
+ return openBlock(), createBlock(unref(ToastRoot), {
39
+ key: toast.id,
40
+ duration: toast.duration,
41
+ open: toast.open,
42
+ "onUpdate:open": [($event) => toast.open = $event, (open) => {
43
+ if (!open) unref(close)(toast.id);
44
+ }],
45
+ "data-swipe": __props.swipeDirection,
46
+ class: normalizeClass([toast.class, "toast-root"])
47
+ }, {
48
+ default: withCtx(() => [
49
+ createVNode(unref(ToastTitle), { class: "[grid-area:_title] mb-$s fw-$bold" }, {
50
+ default: withCtx(() => [createElementVNode("span", null, toDisplayString(toast.title), 1)]),
51
+ _: 2
52
+ }, 1024),
53
+ createVNode(unref(ToastDescription), { "as-child": "" }, {
54
+ default: withCtx(() => [createElementVNode("span", _hoisted_1, toDisplayString(toast.message), 1)]),
55
+ _: 2
56
+ }, 1024),
57
+ (openBlock(true), createElementBlock(Fragment, null, renderList(toast.actions || defaultActions(toast), (action) => {
58
+ return openBlock(), createBlock(unref(ToastAction), {
59
+ class: "[grid-area:_action]",
60
+ "as-child": "",
61
+ "alt-text": action.text,
62
+ key: action.text
63
+ }, {
64
+ default: withCtx(() => [createVNode(VuButton, {
65
+ onClick: ($event) => action.action(),
66
+ class: "c8-flat text-body-s",
67
+ label: action.text
68
+ }, null, 8, ["onClick", "label"])]),
73
69
  _: 2
74
- }, 1024),
75
- (openBlock(true), createElementBlock(Fragment, null, renderList(toast.actions || defaultActions(toast), (action) => {
76
- return openBlock(), createBlock(unref(ToastAction), {
77
- class: "[grid-area:_action]",
78
- "as-child": "",
79
- "alt-text": action.text,
80
- key: action.text
81
- }, {
82
- default: withCtx(() => [createVNode(VuButton, {
83
- onClick: ($event) => action.action(),
84
- class: "c8-flat text-body-s",
85
- label: action.text
86
- }, null, 8, ["onClick", "label"])]),
87
- _: 2
88
- }, 1032, ["alt-text"]);
89
- }), 128))
90
- ]),
91
- _: 2
92
- }, 1032, [
93
- "duration",
94
- "open",
95
- "onUpdate:open",
96
- "data-swipe",
97
- "class"
98
- ]);
99
- }), 128)),
100
- createVNode(unref(ToastViewport), { class: normalizeClass([props.class, "toasts-viewport"]) }, null, 8, ["class"])
101
- ]),
70
+ }, 1032, ["alt-text"]);
71
+ }), 128))
72
+ ]),
73
+ _: 2
74
+ }, 1032, [
75
+ "duration",
76
+ "open",
77
+ "onUpdate:open",
78
+ "data-swipe",
79
+ "class"
80
+ ]);
81
+ }), 128)), createVNode(unref(ToastViewport), mergeProps(_ctx.$attrs, {
82
+ hotkey: props.hotkey,
83
+ class: [props.class, "toasts-viewport"]
84
+ }), null, 16, ["hotkey", "class"])]),
102
85
  _: 1
103
86
  }, 16);
104
87
  };
package/dist/Button.d.mts CHANGED
@@ -1,4 +1,3 @@
1
- import { type RouteLocationRaw } from 'vue-router';
2
1
  type __VLS_Props = {
3
2
  label?: string;
4
3
  icon?: string;
@@ -8,26 +7,43 @@ type __VLS_Props = {
8
7
  active?: boolean;
9
8
  pressed?: boolean;
10
9
  selected?: boolean;
11
- to?: RouteLocationRaw;
10
+ /**
11
+ * Router location: renders a router link through the app's RouterLink. Typed
12
+ * structurally (compatible with vue-router's `RouteLocationRaw`), so vunor needs no
13
+ * vue-router types. Without a router, a string (or `{ path }`) renders `<a href>`.
14
+ */
15
+ to?: string | {
16
+ path?: string;
17
+ name?: string | symbol;
18
+ params?: Record<string, unknown>;
19
+ query?: Record<string, unknown>;
20
+ hash?: string;
21
+ replace?: boolean;
22
+ force?: boolean;
23
+ state?: unknown;
24
+ };
12
25
  class?: string | Record<string, boolean>;
13
26
  style?: string | Record<string, string>;
14
27
  asLink?: boolean;
15
28
  };
16
- declare var __VLS_15: {}, __VLS_17: {}, __VLS_20: {}, __VLS_30: {}, __VLS_32: {}, __VLS_35: {};
29
+ declare var __VLS_17: {}, __VLS_19: {}, __VLS_22: {}, __VLS_32: {}, __VLS_34: {}, __VLS_37: {};
17
30
  type __VLS_Slots = {} & {
18
- 'icon-left'?: (props: typeof __VLS_15) => any;
31
+ 'icon-left'?: (props: typeof __VLS_17) => any;
19
32
  } & {
20
- default?: (props: typeof __VLS_17) => any;
33
+ default?: (props: typeof __VLS_19) => any;
21
34
  } & {
22
- 'icon-right'?: (props: typeof __VLS_20) => any;
35
+ 'icon-right'?: (props: typeof __VLS_22) => any;
23
36
  } & {
24
- 'icon-left'?: (props: typeof __VLS_30) => any;
37
+ 'icon-left'?: (props: typeof __VLS_32) => any;
25
38
  } & {
26
- default?: (props: typeof __VLS_32) => any;
39
+ default?: (props: typeof __VLS_34) => any;
27
40
  } & {
28
- 'icon-right'?: (props: typeof __VLS_35) => any;
41
+ 'icon-right'?: (props: typeof __VLS_37) => any;
29
42
  };
30
- declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
43
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {
44
+ pressed: boolean;
45
+ selected: boolean;
46
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
31
47
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
32
48
  declare const _default: typeof __VLS_export;
33
49
  export default _default;
package/dist/Button.mjs CHANGED
@@ -1,73 +1,107 @@
1
- import { createBlock, createSlots, createVNode, defineComponent, mergeProps, openBlock, renderSlot, unref, withCtx } from "vue";
2
- import { RouterLink } from "vue-router";
1
+ import { computed, createBlock, createSlots, createVNode, defineComponent, getCurrentInstance, mergeProps, openBlock, renderSlot, resolveDynamicComponent, unref, useAttrs, warn, withCtx } from "vue";
3
2
  import VuButtonBase from "vunor/ButtonBase";
4
3
  //#endregion
5
4
  //#region src/components/Button/Button.vue
6
5
  var Button_default = /* @__PURE__ */ defineComponent({
6
+ inheritAttrs: false,
7
7
  __name: "Button",
8
8
  props: {
9
- label: {
10
- type: String,
11
- required: false
12
- },
13
- icon: {
14
- type: String,
15
- required: false
16
- },
17
- iconSide: {
18
- type: String,
19
- required: false
20
- },
21
- loading: {
22
- type: Boolean,
23
- required: false
24
- },
25
- disabled: {
26
- type: Boolean,
27
- required: false
28
- },
29
- active: {
30
- type: Boolean,
31
- required: false
32
- },
9
+ label: {},
10
+ icon: {},
11
+ iconSide: {},
12
+ loading: { type: Boolean },
13
+ disabled: { type: Boolean },
14
+ active: { type: Boolean },
33
15
  pressed: {
34
16
  type: Boolean,
35
- required: false
17
+ default: void 0
36
18
  },
37
19
  selected: {
38
20
  type: Boolean,
39
- required: false
21
+ default: void 0
40
22
  },
41
- to: {
42
- type: null,
43
- required: false
44
- },
45
- class: {
46
- type: [String, Object],
47
- required: false
48
- },
49
- style: {
50
- type: [String, Object],
51
- required: false
52
- },
53
- asLink: {
54
- type: Boolean,
55
- required: false
56
- }
23
+ to: {},
24
+ class: {},
25
+ style: {},
26
+ asLink: { type: Boolean }
57
27
  },
58
28
  setup(__props) {
29
+ const SELECTABLE_ROLES = new Set([
30
+ "option",
31
+ "tab",
32
+ "gridcell",
33
+ "row",
34
+ "columnheader",
35
+ "rowheader",
36
+ "treeitem"
37
+ ]);
38
+ /** the defined entries only: an `undefined` binding would still override or render */
39
+ function defined(values) {
40
+ const result = {};
41
+ for (const key in values) if (values[key] !== void 0) result[key] = values[key];
42
+ return result;
43
+ }
44
+ /** `@click` with any modifiers Vue keeps in the key (`.once`, `.capture`, `.passive`) */
45
+ const CLICK_LISTENER = /^onClick(?:Once|Passive|Capture)*$/;
59
46
  const props = __props;
47
+ const RouterLink = getCurrentInstance()?.appContext.components.RouterLink;
48
+ const baseProps = computed(() => defined({
49
+ label: props.label,
50
+ icon: props.icon,
51
+ iconSide: props.iconSide,
52
+ loading: props.loading,
53
+ disabled: props.disabled,
54
+ class: props.class,
55
+ style: props.style
56
+ }));
57
+ const attrs = useAttrs();
58
+ const ariaSelected = computed(() => props.selected !== void 0 && SELECTABLE_ROLES.has(String(attrs.role)) ? props.selected : void 0);
59
+ const stateAttrs = computed(() => defined({
60
+ "data-active": props.active ? "" : void 0,
61
+ "aria-pressed": props.pressed,
62
+ "data-selected": props.selected === void 0 ? void 0 : String(props.selected),
63
+ "aria-selected": ariaSelected.value
64
+ }));
65
+ const inactive = computed(() => !!(props.disabled || props.loading));
66
+ function linkAttrs() {
67
+ const rest = {};
68
+ for (const key in attrs) {
69
+ if (key === "onClick" || inactive.value && CLICK_LISTENER.test(key)) continue;
70
+ rest[key] = attrs[key];
71
+ }
72
+ return rest;
73
+ }
74
+ function onLinkClick(event, navigate) {
75
+ if (inactive.value) {
76
+ event.preventDefault();
77
+ return;
78
+ }
79
+ const handlers = attrs.onClick;
80
+ for (const handler of [handlers ?? []].flat()) handler(event);
81
+ navigate?.(event);
82
+ }
83
+ const fallbackHref = computed(() => {
84
+ const to = props.to;
85
+ return typeof to === "string" ? to : to?.path;
86
+ });
87
+ const isLink = computed(() => props.asLink || fallbackHref.value !== void 0);
88
+ if (props.to !== void 0 && !RouterLink) warn("VuButton: `to` is set but the app has no router (app.use(router)); rendering a plain link without client-side navigation.");
60
89
  return (_ctx, _cache) => {
61
- return !!__props.to ? (openBlock(), createBlock(unref(RouterLink), {
90
+ return !!__props.to && unref(RouterLink) ? (openBlock(), createBlock(resolveDynamicComponent(unref(RouterLink)), {
62
91
  key: 0,
63
92
  custom: "",
64
93
  to: __props.to
65
94
  }, {
66
- default: withCtx(({ isActive, href }) => [createVNode(VuButtonBase, mergeProps({
95
+ default: withCtx(({ isActive, isExactActive, href, navigate }) => [createVNode(VuButtonBase, mergeProps({
96
+ ...isExactActive ? { "aria-current": "page" } : {},
97
+ ...isActive ? { "data-selected": "true" } : {},
98
+ ...linkAttrs(),
99
+ ...baseProps.value
100
+ }, {
67
101
  as: "a",
68
102
  href,
69
- "aria-selected": isActive
70
- }, props), createSlots({
103
+ onClick: ($event) => onLinkClick($event, navigate)
104
+ }), createSlots({
71
105
  default: withCtx(() => [renderSlot(_ctx.$slots, "default")]),
72
106
  _: 2
73
107
  }, [_ctx.$slots["icon-left"] ? {
@@ -78,15 +112,21 @@ var Button_default = /* @__PURE__ */ defineComponent({
78
112
  name: "icon-right",
79
113
  fn: withCtx(() => [renderSlot(_ctx.$slots, "icon-right")]),
80
114
  key: "1"
81
- } : void 0]), 1040, ["href", "aria-selected"])]),
115
+ } : void 0]), 1040, ["href", "onClick"])]),
82
116
  _: 3
83
117
  }, 8, ["to"])) : (openBlock(), createBlock(VuButtonBase, mergeProps({
84
118
  key: 1,
85
- as: __props.asLink ? "a" : "button"
86
- }, props, {
87
- "data-active": __props.active ? "" : void 0,
88
- "aria-pressed": typeof __props.pressed === "boolean" ? __props.pressed : void 0,
89
- "aria-selected": typeof __props.selected === "boolean" ? __props.selected : void 0
119
+ as: isLink.value ? "a" : "button",
120
+ href: fallbackHref.value
121
+ }, isLink.value ? {
122
+ ...stateAttrs.value,
123
+ ...linkAttrs(),
124
+ ...baseProps.value,
125
+ onClick: onLinkClick
126
+ } : {
127
+ ...stateAttrs.value,
128
+ ..._ctx.$attrs,
129
+ ...baseProps.value
90
130
  }), createSlots({
91
131
  default: withCtx(() => [renderSlot(_ctx.$slots, "default")]),
92
132
  _: 2
@@ -98,12 +138,7 @@ var Button_default = /* @__PURE__ */ defineComponent({
98
138
  name: "icon-right",
99
139
  fn: withCtx(() => [renderSlot(_ctx.$slots, "icon-right")]),
100
140
  key: "1"
101
- } : void 0]), 1040, [
102
- "as",
103
- "data-active",
104
- "aria-pressed",
105
- "aria-selected"
106
- ]));
141
+ } : void 0]), 1040, ["as", "href"]));
107
142
  };
108
143
  }
109
144
  });
@@ -1,4 +1,4 @@
1
- import { Fragment, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, openBlock, renderSlot, toDisplayString, unref, withCtx } from "vue";
1
+ import { computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, mergeProps, openBlock, renderSlot, toDisplayString, unref, withCtx } from "vue";
2
2
  import { Primitive } from "reka-ui";
3
3
  import VuIcon from "vunor/Icon";
4
4
  import VuLoadingIndicator from "vunor/LoadingIndicator";
@@ -13,71 +13,57 @@ const _hoisted_2 = { class: "btn-label" };
13
13
  var ButtonBase_default = /* @__PURE__ */ defineComponent({
14
14
  __name: "ButtonBase",
15
15
  props: {
16
- label: {
17
- type: String,
18
- required: false
19
- },
20
- icon: {
21
- type: String,
22
- required: false
23
- },
24
- iconSide: {
25
- type: String,
26
- required: false,
27
- default: "left"
28
- },
29
- loading: {
30
- type: Boolean,
31
- required: false
32
- },
33
- disabled: {
34
- type: Boolean,
35
- required: false
36
- },
37
- as: {
38
- type: String,
39
- required: false,
40
- default: "button"
41
- },
42
- href: {
43
- type: String,
44
- required: false
45
- }
16
+ label: {},
17
+ icon: {},
18
+ iconSide: { default: "left" },
19
+ loading: { type: Boolean },
20
+ disabled: { type: Boolean },
21
+ as: { default: "button" },
22
+ href: {}
46
23
  },
47
24
  setup(__props) {
25
+ const INACTIVE_LINK_ATTRS = Object.freeze({
26
+ "role": "link",
27
+ "aria-disabled": "true",
28
+ "tabindex": 0
29
+ });
30
+ const NO_ATTRS = Object.freeze({});
31
+ const props = __props;
32
+ const inactiveLink = computed(() => props.as === "a" && !!(props.loading || props.disabled));
48
33
  return (_ctx, _cache) => {
49
- return openBlock(), createElementBlock(Fragment, null, [createCommentVNode("\n `group/btn` is no longer read by any vunor rule: since 0.3.0 the label and\n icon take their cue from inherited custom properties instead of\n `group-[.btn-square]/btn:` descendant rules. It stays because it is public\n surface that consumer-authored `group-*/btn:` rules may still key on.\n "), createVNode(unref(Primitive), {
34
+ return openBlock(), createBlock(unref(Primitive), mergeProps({
50
35
  class: "btn group/btn",
51
36
  as: __props.as,
52
- href: __props.href,
37
+ href: inactiveLink.value ? void 0 : __props.href
38
+ }, inactiveLink.value ? unref(INACTIVE_LINK_ATTRS) : unref(NO_ATTRS), {
53
39
  "data-has-label": !!__props.label || !!_ctx.$slots.default,
54
40
  "data-has-icon": !!__props.icon,
55
41
  "data-loading": __props.loading ? "" : void 0,
56
- disabled: __props.loading || __props.disabled || void 0
57
- }, {
42
+ disabled: __props.as === "button" ? __props.loading || __props.disabled || void 0 : void 0
43
+ }), {
58
44
  default: withCtx(() => [
59
- __props.loading ? (openBlock(), createElementBlock("div", _hoisted_1, [createVNode(VuLoadingIndicator)])) : createCommentVNode("v-if", true),
45
+ __props.loading ? (openBlock(), createElementBlock("div", _hoisted_1, [createVNode(VuLoadingIndicator)])) : createCommentVNode("", true),
60
46
  renderSlot(_ctx.$slots, "icon-left", {}, () => [__props.icon && __props.iconSide === "left" ? (openBlock(), createBlock(VuIcon, {
61
47
  key: 0,
62
48
  name: __props.icon,
63
49
  class: "btn-icon btn-icon-left"
64
- }, null, 8, ["name"])) : createCommentVNode("v-if", true)]),
50
+ }, null, 8, ["name"])) : createCommentVNode("", true)]),
65
51
  renderSlot(_ctx.$slots, "default", {}, () => [createElementVNode("span", _hoisted_2, toDisplayString(__props.label), 1)]),
66
52
  renderSlot(_ctx.$slots, "icon-right", {}, () => [__props.icon && __props.iconSide === "right" ? (openBlock(), createBlock(VuIcon, {
67
53
  key: 0,
68
54
  name: __props.icon,
69
55
  class: "btn-icon btn-icon-right"
70
- }, null, 8, ["name"])) : createCommentVNode("v-if", true)])
56
+ }, null, 8, ["name"])) : createCommentVNode("", true)])
71
57
  ]),
72
58
  _: 3
73
- }, 8, [
59
+ }, 16, [
74
60
  "as",
75
61
  "href",
76
62
  "data-has-label",
77
63
  "data-has-icon",
78
64
  "data-loading",
79
65
  "disabled"
80
- ])], 2112);
66
+ ]);
81
67
  };
82
68
  }
83
69
  });