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
@@ -0,0 +1,125 @@
1
+ import { camelize, computed, getCurrentInstance, ref, toHandlerKey, warn, watch } from "vue";
2
+ //#region src/components/forward-root.ts
3
+ function keysOf(value) {
4
+ return Array.isArray(value) ? value : Object.keys(value ?? {});
5
+ }
6
+ function isBooleanProp(definition) {
7
+ const type = definition?.type;
8
+ return Array.isArray(type) ? type.includes(Boolean) : type === Boolean;
9
+ }
10
+ /**
11
+ * Forwarding contract for components that wrap another component (usually a Reka root):
12
+ * every prop and emit of the wrapped `root` that the wrapper declares reaches `root`.
13
+ *
14
+ * Returns the bindings to `v-bind` on `root`:
15
+ * - each prop of `root` (except `omit`) whose value on the wrapper is not `undefined`;
16
+ * - a listener for each emit of `root` (except `omit`) that re-emits it from the wrapper
17
+ * when `emit` is given (the wrapper must declare those emits).
18
+ *
19
+ * A forwarded boolean prop must be declared with a default (`withDefaults`), usually
20
+ * `undefined`: Vue casts an absent boolean without a default to `false`, which would
21
+ * override `root`'s own default (for example `modal: true`). In development a forwarded
22
+ * boolean without a default is reported with a Vue warning.
23
+ *
24
+ * Props and emits the wrapper binds itself (models, layout-controlled `asChild`) go in `omit`.
25
+ */
26
+ function useForwardToRoot(root, props, emit, omit = []) {
27
+ const skip = new Set(omit);
28
+ const options = root;
29
+ const rootProps = keysOf(options.props).filter((key) => !skip.has(key));
30
+ const ownProps = (getCurrentInstance()?.type)?.props;
31
+ for (const key of rootProps) {
32
+ const own = ownProps?.[key];
33
+ if (own && isBooleanProp(own) && !("default" in own)) warn(`vunor: boolean prop "${key}" is forwarded to a wrapped component but has no default; declare it with \`default: undefined\` so an absent prop is not forwarded as false.`);
34
+ }
35
+ const listeners = {};
36
+ if (emit) {
37
+ for (const event of keysOf(options.emits)) if (!skip.has(event)) listeners[toHandlerKey(camelize(event))] = (...args) => emit(event, ...args);
38
+ }
39
+ return computed(() => {
40
+ const bindings = { ...listeners };
41
+ for (const key of rootProps) if (props[key] !== void 0) bindings[key] = props[key];
42
+ return bindings;
43
+ });
44
+ }
45
+ /**
46
+ * The value a Reka root starts `X` with when `defaultX` is not set, as its setup computes
47
+ * it (`SliderRoot` `defaultValue` -> `[0]`, `PopoverRoot` `defaultOpen` -> `false`, a
48
+ * `multiple` Select / Combobox `defaultValue` -> `[]`).
49
+ */
50
+ function rekaDefault(root, key, props) {
51
+ const definition = root.props;
52
+ const option = definition?.[key];
53
+ if (!option || typeof option !== "object") return;
54
+ if (key === "defaultValue" && definition && "multiple" in definition) return props.multiple ? [] : void 0;
55
+ if ("default" in option) {
56
+ const types = [option.type].flat();
57
+ return typeof option.default === "function" && !types.includes(Function) ? option.default() : option.default;
58
+ }
59
+ return isBooleanProp(option) ? false : void 0;
60
+ }
61
+ /**
62
+ * The one source of truth for what a wrapper exposes about a root model `X` (slot props,
63
+ * data attributes, toggles), where it reads it outside the root. It mirrors how the Reka
64
+ * root resolves `X` (VueUse `useVModel` in its setup):
65
+ * - a bound model is the state;
66
+ * - `defaultX` is read ONCE, at setup, like Reka does (a later change has no effect);
67
+ * - a root that starts unbound (passive) holds `defaultX ?? <Reka's setup default>` until
68
+ * the model is first set, and whatever the model last was after that (a cleared model
69
+ * -> no value);
70
+ * - a root that starts bound falls back to `defaultX ?? <Reka's setup default>` when the
71
+ * model is cleared.
72
+ * The root itself stays bound to the raw model. Writing sets the model (one write per
73
+ * toggle); the wrapper never writes because the parent changed the model.
74
+ */
75
+ function useModelState(model, root, defaultKey, props) {
76
+ const values = props;
77
+ const rootDefault = rekaDefault(root, defaultKey, values);
78
+ const initial = values[defaultKey] ?? rootDefault;
79
+ const cleared = typeof rootDefault === "boolean" ? rootDefault : void 0;
80
+ const passive = model.value === void 0;
81
+ const touched = ref(false);
82
+ watch(model, () => {
83
+ touched.value = true;
84
+ });
85
+ return computed({
86
+ get: () => {
87
+ if (model.value !== void 0) return model.value;
88
+ return passive && touched.value ? cleared : initial;
89
+ },
90
+ set: (value) => {
91
+ model.value = value;
92
+ }
93
+ });
94
+ }
95
+ const claimedGestures = /* @__PURE__ */ new WeakSet();
96
+ /**
97
+ * One user gesture toggles a popup ONCE. A click on an append icon also bubbles to the
98
+ * field shell around it, and both may want to toggle; every vunor toggle handler calls
99
+ * this first and only toggles when it returns true:
100
+ * - the first (innermost) handler on the event's path claims it, later ones skip it;
101
+ * - a gesture that lands inside a Reka trigger (marked `data-vu-trigger`) belongs to that
102
+ * trigger, which toggles by itself: no vunor handler toggles for it.
103
+ * Calls without an event (programmatic `openPopup()`) always toggle.
104
+ */
105
+ function claimToggle(event) {
106
+ if (!event) return true;
107
+ if (claimedGestures.has(event)) return false;
108
+ claimedGestures.add(event);
109
+ return !event.target?.closest?.("[data-vu-trigger]");
110
+ }
111
+ /**
112
+ * What to bind on the Reka root for a model: `undefined` (uncontrolled, so the root applies
113
+ * `defaultX`) until the model is first set, then always a value: the state from
114
+ * `useModelState`. A root that was handed a value and then `undefined` would pass
115
+ * `undefined` on to parts that require a value (Reka `Presence` `present`).
116
+ */
117
+ function useRootBinding(model, state) {
118
+ const everSet = ref(model.value !== void 0);
119
+ watch(model, (value) => {
120
+ if (value !== void 0) everSet.value = true;
121
+ });
122
+ return computed(() => model.value !== void 0 || everSet.value ? state.value : void 0);
123
+ }
124
+ //#endregion
125
+ export { useRootBinding as i, useForwardToRoot as n, useModelState as r, claimToggle as t };
package/dist/nuxt.mjs CHANGED
@@ -1,6 +1,41 @@
1
1
  import { addComponent, defineNuxtModule } from "@nuxt/kit";
2
2
  //#region src/nuxt.ts
3
- const components = __vue_files__;
3
+ const components = [
4
+ "AppLayout",
5
+ "AppToasts",
6
+ "Button",
7
+ "ButtonBase",
8
+ "Calendar",
9
+ "Card",
10
+ "CardHeader",
11
+ "CardInner",
12
+ "Checkbox",
13
+ "Combobox",
14
+ "DatePicker",
15
+ "DatePickerBase",
16
+ "DatePickerInner",
17
+ "DatePickerPopup",
18
+ "DevTools",
19
+ "DelayedSwitch",
20
+ "Dialog",
21
+ "Icon",
22
+ "Input",
23
+ "InputBase",
24
+ "Label",
25
+ "InnerLoading",
26
+ "LoadingIndicator",
27
+ "Menu",
28
+ "MenuItem",
29
+ "OverflowContainer",
30
+ "Pagination",
31
+ "Popover",
32
+ "ProgressBar",
33
+ "RadioGroup",
34
+ "Select",
35
+ "SelectBase",
36
+ "Slider",
37
+ "Tabs"
38
+ ];
4
39
  const index = defineNuxtModule({
5
40
  meta: {
6
41
  name: "vunor/nuxt",
package/dist/theme.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { PresetFactory } from "unocss";
2
- import { Theme } from "@unocss/preset-mini";
2
+ import { Theme } from "unocss/preset-mini";
3
3
 
4
4
  //#region src/theme/utils/define-sc.d.ts
5
5
  interface TVunorShortcut {
package/dist/theme.mjs CHANGED
@@ -602,6 +602,7 @@ const componentClasses = {
602
602
  "loading-indicator-ring",
603
603
  "mb-$s",
604
604
  "surface",
605
+ "tab",
605
606
  "text-body-s",
606
607
  "toast-root",
607
608
  "toasts-viewport"
@@ -616,7 +617,8 @@ const componentClasses = {
616
617
  "icon-size",
617
618
  "loading-indicator",
618
619
  "loading-indicator-ring",
619
- "surface"
620
+ "surface",
621
+ "tab"
620
622
  ],
621
623
  ButtonBase: [
622
624
  "btn",
@@ -628,7 +630,8 @@ const componentClasses = {
628
630
  "icon-size",
629
631
  "loading-indicator",
630
632
  "loading-indicator-ring",
631
- "surface"
633
+ "surface",
634
+ "tab"
632
635
  ],
633
636
  Calendar: [
634
637
  "btn",
@@ -661,6 +664,7 @@ const componentClasses = {
661
664
  "rounded",
662
665
  "select-none",
663
666
  "surface",
667
+ "tab",
664
668
  "text-center"
665
669
  ],
666
670
  Card: [
@@ -853,6 +857,7 @@ const componentClasses = {
853
857
  "segmented",
854
858
  "select-none",
855
859
  "surface",
860
+ "tab",
856
861
  "text-body",
857
862
  "text-center",
858
863
  "text-ellipsis",
@@ -920,6 +925,7 @@ const componentClasses = {
920
925
  "segmented",
921
926
  "select-none",
922
927
  "surface",
928
+ "tab",
923
929
  "text-center",
924
930
  "top-0"
925
931
  ],
@@ -960,6 +966,7 @@ const componentClasses = {
960
966
  "rounded",
961
967
  "select-none",
962
968
  "surface",
969
+ "tab",
963
970
  "text-center"
964
971
  ],
965
972
  DelayedSwitch: ["visible"],
@@ -1116,6 +1123,7 @@ const componentClasses = {
1116
1123
  "py-$m",
1117
1124
  "rounded",
1118
1125
  "surface",
1126
+ "tab",
1119
1127
  "text-card-header",
1120
1128
  "text-mt-0"
1121
1129
  ],
@@ -1276,6 +1284,7 @@ const componentClasses = {
1276
1284
  "scope-grey",
1277
1285
  "segmented",
1278
1286
  "surface",
1287
+ "tab",
1279
1288
  "text-body",
1280
1289
  "text-caption",
1281
1290
  "text-center",
@@ -1299,7 +1308,8 @@ const componentClasses = {
1299
1308
  "loading-indicator-ring",
1300
1309
  "menu-item",
1301
1310
  "scope-grey",
1302
- "surface"
1311
+ "surface",
1312
+ "tab"
1303
1313
  ],
1304
1314
  OverflowContainer: ["flex", "whitespace-nowrap"],
1305
1315
  Pagination: [
@@ -1329,7 +1339,8 @@ const componentClasses = {
1329
1339
  "icon-size",
1330
1340
  "loading-indicator",
1331
1341
  "loading-indicator-ring",
1332
- "surface"
1342
+ "surface",
1343
+ "tab"
1333
1344
  ],
1334
1345
  ProgressBar: [
1335
1346
  "progress-bar",
@@ -36,5 +36,34 @@ function useInputProps() {
36
36
  }));
37
37
  }
38
38
  }
39
+ function useInputBaseProps() {
40
+ const instance = getCurrentInstance();
41
+ if (instance) {
42
+ const props = instance.props;
43
+ return computed(() => ({
44
+ label: props.stackLabel ? void 0 : props.label,
45
+ placeholder: props.placeholder,
46
+ readonly: props.readonly,
47
+ design: props.design,
48
+ disabled: props.disabled,
49
+ loading: props.loading,
50
+ iconPrepend: props.iconPrepend,
51
+ iconAppend: props.iconAppend,
52
+ groupItem: props.groupItem,
53
+ type: props.type,
54
+ rows: props.type === "textarea" ? props.rows : void 0,
55
+ autoGrow: props.autoGrow,
56
+ maxlength: props.maxlength,
57
+ required: props.required,
58
+ active: props.active,
59
+ noUnderline: props.noUnderline,
60
+ autocomplete: props.autocomplete,
61
+ onAppendClick: props.onAppendClick,
62
+ onPrependClick: props.onPrependClick,
63
+ onBlur: props.onBlur,
64
+ onFocus: props.onFocus
65
+ }));
66
+ }
67
+ }
39
68
  //#endregion
40
- export { useInputProps as t };
69
+ export { useInputProps as n, useInputBaseProps as t };
package/dist/utils.mjs CHANGED
@@ -27,7 +27,7 @@ const useProvideInject = (key, init) => {
27
27
  return tr;
28
28
  },
29
29
  inject: (...args) => {
30
- const _inject = inject(key);
30
+ const _inject = inject(key, null);
31
31
  if (_inject) return _inject(...args);
32
32
  }
33
33
  };
package/dist/vite.d.mts CHANGED
@@ -1,6 +1,15 @@
1
- import { ComponentResolver } from "unplugin-vue-components";
2
-
3
1
  //#region src/vite.d.ts
4
- declare const VunorVueResolver: ComponentResolver;
2
+ /** What a resolver returns: the default export of `vunor/<Name>`, imported as `Vu<Name>`. */
3
+ interface TVunorResolvedComponent {
4
+ name: 'default';
5
+ as: string;
6
+ from: string;
7
+ }
8
+ /**
9
+ * Component resolver for `unplugin-vue-components` (`Components({ resolvers: [VunorVueResolver] })`):
10
+ * resolves `Vu<Name>` to `vunor/<Name>`. Typed structurally (it satisfies the plugin's
11
+ * `ComponentResolver`), so vunor's types do not depend on unplugin-vue-components.
12
+ */
13
+ declare const VunorVueResolver: (componentName: string) => TVunorResolvedComponent | undefined;
5
14
  //#endregion
6
- export { VunorVueResolver };
15
+ export { TVunorResolvedComponent, VunorVueResolver };
package/dist/vite.mjs CHANGED
@@ -1,4 +1,9 @@
1
1
  //#region src/vite.ts
2
+ /**
3
+ * Component resolver for `unplugin-vue-components` (`Components({ resolvers: [VunorVueResolver] })`):
4
+ * resolves `Vu<Name>` to `vunor/<Name>`. Typed structurally (it satisfies the plugin's
5
+ * `ComponentResolver`), so vunor's types do not depend on unplugin-vue-components.
6
+ */
2
7
  const VunorVueResolver = (componentName) => {
3
8
  if (componentName.startsWith("Vu")) return {
4
9
  name: "default",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vunor",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "files": [
5
5
  "dist"
6
6
  ],
@@ -174,20 +174,18 @@
174
174
  "gen-exports": "node scripts/gen-exports.js",
175
175
  "extract-classes": "tsx scripts/extract-component-classes.ts",
176
176
  "emit-vue-dts": "node scripts/emit-vue-dts.js",
177
- "build": "pnpm gen-exports && pnpm extract-classes && rolldown -c && pnpm emit-vue-dts",
177
+ "clean": "node -e \"require('fs').rmSync('dist', { recursive: true, force: true })\"",
178
+ "build": "pnpm clean && pnpm gen-exports && pnpm extract-classes && rolldown -c && pnpm emit-vue-dts && pnpm test:dist",
178
179
  "pub": "npm publish --access public",
179
180
  "type-check": "vue-tsc --build --force",
180
181
  "test": "vitest run && playwright test",
182
+ "test:dist": "vitest run --config vitest.dist.config.ts",
181
183
  "test:e2e": "playwright test"
182
184
  },
183
185
  "dependencies": {
184
186
  "@internationalized/date": "^3.12.1",
185
187
  "@prostojs/palitra": "^0.0.4",
186
- "@unocss/preset-mini": "^66.6.8",
187
- "defu": "^6.1.7",
188
- "reka-ui": "^2.9.6",
189
- "unocss": "^66.6.8",
190
- "vue": "^3.5.32"
188
+ "defu": "^6.1.7"
191
189
  },
192
190
  "devDependencies": {
193
191
  "@iconify/utils": "^3.1.0",
@@ -196,23 +194,40 @@
196
194
  "@playwright/test": "^1.59.1",
197
195
  "@types/node": "^25.6.0",
198
196
  "@unocss/preset-icons": "^66.6.8",
199
- "@unocss/preset-mini": "^66.6.0",
200
197
  "@unocss/preset-wind": "^66.6.8",
201
198
  "@unocss/reset": "^66.6.8",
202
199
  "@vitejs/plugin-vue": "^6.0.6",
203
200
  "@vue/tsconfig": "^0.9.1",
204
201
  "@vueuse/core": "^14.2.1",
202
+ "happy-dom": "^21.0.4",
203
+ "reka-ui": "^2.9.6",
205
204
  "rolldown": "1.0.0-rc.16",
206
205
  "rolldown-plugin-dts": "^0.23.2",
207
206
  "tsx": "^4.21.0",
208
207
  "typescript": "^6.0.3",
209
- "unocss": "^66.6.0",
208
+ "unocss": "^66.6.8",
210
209
  "unocss-preset-scrollbar": "^4.0.0",
211
210
  "unplugin-auto-import": "^21.0.0",
212
211
  "unplugin-vue-components": "^32.0.0",
213
212
  "vite": "^8.0.8",
214
213
  "vitest": "^4.1.4",
214
+ "vue": "^3.5.32",
215
215
  "vue-router": "^5.0.4",
216
216
  "vue-tsc": "^3.2.6"
217
+ },
218
+ "peerDependencies": {
219
+ "@nuxt/kit": ">=3.0.0",
220
+ "@nuxt/schema": ">=3.0.0",
221
+ "reka-ui": "^2.8.0",
222
+ "unocss": "^66.2.1",
223
+ "vue": "^3.5.0"
224
+ },
225
+ "peerDependenciesMeta": {
226
+ "@nuxt/kit": {
227
+ "optional": true
228
+ },
229
+ "@nuxt/schema": {
230
+ "optional": true
231
+ }
217
232
  }
218
233
  }
@@ -1,42 +0,0 @@
1
- import { computed, getCurrentInstance } from "vue";
2
-
3
- //#region src/components/Input/utils.ts
4
- function useInputProps() {
5
- const instance = getCurrentInstance();
6
- if (instance) {
7
- const props = instance.props;
8
- return computed(() => ({
9
- label: props.label,
10
- stackLabel: props.stackLabel,
11
- placeholder: props.placeholder,
12
- design: props.design,
13
- readonly: props.readonly,
14
- disabled: props.disabled,
15
- iconPrepend: props.iconPrepend,
16
- iconAppend: props.iconAppend,
17
- groupItem: props.groupItem,
18
- type: props.type,
19
- rows: props.type === "textarea" ? props.rows : void 0,
20
- autoGrow: props.autoGrow,
21
- maxlength: props.maxlength,
22
- required: props.required,
23
- active: props.active,
24
- loading: props.loading,
25
- onAppendClick: props.onAppendClick,
26
- onPrependClick: props.onPrependClick,
27
- onBlur: props.onBlur,
28
- onFocus: props.onFocus,
29
- iconBefore: props.iconBefore,
30
- iconAfter: props.iconAfter,
31
- error: props.error,
32
- hint: props.hint,
33
- autocomplete: props.autocomplete,
34
- onBeforeClick: props.onBeforeClick,
35
- onAfterClick: props.onAfterClick,
36
- onClick: props.onClick
37
- }));
38
- }
39
- }
40
-
41
- //#endregion
42
- export { useInputProps as t };