nuxt-ui-tools 1.16.7 → 1.16.9

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.
package/dist/module.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "configKey": "nuxtUiTools",
6
6
  "docs": "https://ui.nuxt.com/docs/getting-started/installation/nuxt",
7
7
  "name": "nuxt-ui-tools",
8
- "version": "1.16.7",
8
+ "version": "1.16.9",
9
9
  "builder": {
10
10
  "@nuxt/module-builder": "1.0.3",
11
11
  "unbuild": "unknown"
@@ -86,6 +86,7 @@ const controlAttrs = computed(() => {
86
86
  provideFormFieldBare(bare);
87
87
  provideFormFieldControlAttrs(controlAttrs);
88
88
  const field = computed(() => createFormFieldInstance(props.field));
89
+ const headless = computed(() => !field.value.capability.has("layout"));
89
90
  const path = computed(() => fieldPath(props.parentPath, props.field));
90
91
  const childPath = computed(() => childParentPath(props.parentPath, props.field));
91
92
  const visible = computed(() => form.shouldRender(props.field, path.value));
@@ -200,7 +201,7 @@ function resolveControlLabel(controlField) {
200
201
  </script>
201
202
 
202
203
  <template>
203
- <template v-if="visible && bare">
204
+ <template v-if="visible && (bare || headless)">
204
205
  <component :is="renderer" v-if="renderer" v-bind="rendererProps" />
205
206
  <UAlert
206
207
  v-else
@@ -9,6 +9,7 @@ import { useUiToolsLocale } from "#ui-tools/i18n";
9
9
  import { useFormActions } from "../../composables/use-form-actions";
10
10
  import { useFormGridLayout } from "../../composables/use-form-layout";
11
11
  import { useFormRoot } from "../../composables/use-form-root";
12
+ import { createFormFieldInstance } from "../../utils/field-instance";
12
13
  import { getFormHeader } from "../../utils/overlay";
13
14
  import { isRecord } from "../../utils/path";
14
15
  import { isFunction, isNumber, isString } from "../../utils/predicate";
@@ -69,6 +70,11 @@ watch(
69
70
  const displayedStep = computed(
70
71
  () => isSteppedSchema(schemaRef.value) ? getSchemaSteps(schemaRef.value)[displayedStepIndex.value] : void 0
71
72
  );
73
+ const skeletonFields = computed(
74
+ () => runtime.currentFields.value.filter(
75
+ (field) => createFormFieldInstance(field).capability.has("layout")
76
+ )
77
+ );
72
78
  const displayedFields = computed(
73
79
  () => isSteppedSchema(schemaRef.value) ? displayedStep.value?.fields ?? [] : getSchemaFields(schemaRef.value)
74
80
  );
@@ -295,7 +301,7 @@ function getSchemaShowStepper(schema) {
295
301
  :style="grid.style.value"
296
302
  >
297
303
  <div
298
- v-for="field in runtime.currentFields.value"
304
+ v-for="field in skeletonFields"
299
305
  :key="field.key"
300
306
  :class="mergeFormUiClass('grid gap-2', formUi.ui.value.root?.ui?.skeletonField)"
301
307
  >
@@ -1,4 +1,4 @@
1
- import { useRegle } from "@regle/core";
1
+ import { markStatic, useRegle } from "@regle/core";
2
2
  import { withMessage } from "@regle/rules";
3
3
  import { computed, nextTick, reactive, ref, unref, watch } from "vue";
4
4
  import { calendarSeedFromValue, isDateFamilyRange } from "../fields/date-family/utils.js";
@@ -387,6 +387,12 @@ function buildFieldRules(params) {
387
387
  continue;
388
388
  }
389
389
  const path = fieldPath(params.parentPath, field);
390
+ if (fieldInstance.type.is("custom-component")) {
391
+ const value = getPathValue(params.state, path);
392
+ if (isRecord(value)) {
393
+ markStatic(value);
394
+ }
395
+ }
390
396
  const api = params.apiFactory(path, field);
391
397
  const callbackParams = {
392
398
  api,
@@ -65,8 +65,9 @@ function isOptionValue(value) {
65
65
  <ChoiceCardLabel
66
66
  :label="optionByKey.get(item.value)?.label ?? item.label"
67
67
  :icon="optionByKey.get(item.value)?.icon"
68
+ :value="optionByKey.get(item.value)?.value"
68
69
  :tile="cards.tile.value"
69
- :corner="cards.corner.value"
70
+ :mark="cards.mark.value"
70
71
  :selected="model.includes(item.value)"
71
72
  :ui="cards.ui.value"
72
73
  />
@@ -1,14 +1,19 @@
1
1
  import type { FormControlUi } from '../../types/index.js';
2
+ import type { ChoiceCardMark } from './types.js';
2
3
  /**
3
- * Content of a card's label: the option icon, inline or in a tile above the label, and the check
4
- * a selected card shows in its corner. Styled through `ui.tile`, `ui.tileIcon`, `ui.optionIcon`,
5
- * `ui.check`, and `ui.checkIcon`.
4
+ * Content of a card's label: the option icon, inline or in a tile above the label, and the
5
+ * selection mark the card draws itself: a check in the corner of a selected card, or a switch
6
+ * that follows the selection. Both marks are decorative; the group's own control keeps the
7
+ * semantics. Styled through `ui.tile`, `ui.tileIcon`, `ui.optionIcon`, `ui.check`,
8
+ * `ui.checkIcon`, `ui.switch`, and `ui.switchThumb`; the tile and the switch carry the option
9
+ * value as `data-value` for per-option styling.
6
10
  */
7
11
  type __VLS_Props = {
8
12
  label?: string;
9
13
  icon?: string;
14
+ value?: string | number | boolean;
10
15
  tile: boolean;
11
- corner: boolean;
16
+ mark: ChoiceCardMark;
12
17
  selected: boolean;
13
18
  ui?: FormControlUi;
14
19
  };
@@ -4,8 +4,9 @@ import { mergeFormUiClass } from "../../utils/ui";
4
4
  defineProps({
5
5
  label: { type: String, required: false },
6
6
  icon: { type: String, required: false },
7
+ value: { type: [String, Number, Boolean], required: false },
7
8
  tile: { type: Boolean, required: true },
8
- corner: { type: Boolean, required: true },
9
+ mark: { type: String, required: true },
9
10
  selected: { type: Boolean, required: true },
10
11
  ui: { type: Object, required: false }
11
12
  });
@@ -15,6 +16,7 @@ defineProps({
15
16
  <span
16
17
  v-if="tile && icon"
17
18
  :data-selected="selected || void 0"
19
+ :data-value="value"
18
20
  :class="
19
21
  mergeFormUiClass(
20
22
  'grid size-8 place-items-center rounded-lg bg-elevated text-muted transition-colors data-[selected]:bg-primary/10 data-[selected]:text-primary',
@@ -35,7 +37,7 @@ defineProps({
35
37
  </span>
36
38
  <span v-else class="block">{{ label }}</span>
37
39
  <span
38
- v-if="corner && selected"
40
+ v-if="mark === 'corner' && selected"
39
41
  aria-hidden="true"
40
42
  :class="
41
43
  mergeFormUiClass(
@@ -47,4 +49,26 @@ defineProps({
47
49
  >
48
50
  <UIcon name="i-lucide-check" :class="mergeFormUiClass('size-2.5', ui?.checkIcon)" />
49
51
  </span>
52
+ <span
53
+ v-if="mark === 'switch'"
54
+ aria-hidden="true"
55
+ :data-state="selected ? 'checked' : 'unchecked'"
56
+ :data-value="value"
57
+ :class="
58
+ mergeFormUiClass(
59
+ 'group/switch absolute end-3 top-3.5 inline-flex h-[18px] w-8 shrink-0 items-center rounded-full bg-accented p-0.5 transition-colors duration-150 data-[state=checked]:bg-primary',
60
+ ui?.switch
61
+ )
62
+ "
63
+ data-choice-switch
64
+ >
65
+ <span
66
+ :class="
67
+ mergeFormUiClass(
68
+ 'size-3.5 rounded-full bg-default shadow-xs transition-transform duration-150 group-data-[state=checked]/switch:translate-x-3.5 motion-reduce:transition-none rtl:group-data-[state=checked]/switch:-translate-x-3.5',
69
+ ui?.switchThumb
70
+ )
71
+ "
72
+ />
73
+ </span>
50
74
  </template>
@@ -1,14 +1,19 @@
1
1
  import type { FormControlUi } from '../../types/index.js';
2
+ import type { ChoiceCardMark } from './types.js';
2
3
  /**
3
- * Content of a card's label: the option icon, inline or in a tile above the label, and the check
4
- * a selected card shows in its corner. Styled through `ui.tile`, `ui.tileIcon`, `ui.optionIcon`,
5
- * `ui.check`, and `ui.checkIcon`.
4
+ * Content of a card's label: the option icon, inline or in a tile above the label, and the
5
+ * selection mark the card draws itself: a check in the corner of a selected card, or a switch
6
+ * that follows the selection. Both marks are decorative; the group's own control keeps the
7
+ * semantics. Styled through `ui.tile`, `ui.tileIcon`, `ui.optionIcon`, `ui.check`,
8
+ * `ui.checkIcon`, `ui.switch`, and `ui.switchThumb`; the tile and the switch carry the option
9
+ * value as `data-value` for per-option styling.
6
10
  */
7
11
  type __VLS_Props = {
8
12
  label?: string;
9
13
  icon?: string;
14
+ value?: string | number | boolean;
10
15
  tile: boolean;
11
- corner: boolean;
16
+ mark: ChoiceCardMark;
12
17
  selected: boolean;
13
18
  ui?: FormControlUi;
14
19
  };
@@ -18,14 +18,19 @@ export interface FormChoiceCardProps {
18
18
  orientation?: 'horizontal' | 'vertical';
19
19
  /**
20
20
  * Selection mark: the radio or checkbox before the content (`start`, the default), after it
21
- * (`end`), none (`hidden`), or a check in the top corner of the selected cards (`corner`).
21
+ * (`end`), none (`hidden`), a check in the top corner of the selected cards (`corner`), or a
22
+ * switch in the top corner of every card that turns on with the selection (`switch`), for cards
23
+ * that enable something.
22
24
  */
23
- indicator?: 'start' | 'end' | 'hidden' | 'corner';
25
+ indicator?: 'start' | 'end' | 'hidden' | 'corner' | 'switch';
24
26
  /**
25
27
  * Where an option's `icon` shows: before its label (`inline`, the default), or in a tile above
26
28
  * it (`tile`) that takes the selection color. Style the parts the engine renders with the
27
- * `ui` slots `tile`, `tileIcon`, `optionIcon`, `check`, and `checkIcon`, next to the Nuxt UI
28
- * slots of the group.
29
+ * `ui` slots `tile`, `tileIcon`, `optionIcon`, `check`, `checkIcon`, `switch`, and
30
+ * `switchThumb`, next to the Nuxt UI slots of the group. The tile and the switch carry the
31
+ * option value as `data-value`, so one option can take its own colors.
29
32
  */
30
33
  icon?: 'inline' | 'tile';
31
34
  }
35
+ /** The mark a card draws itself, in place of the Nuxt UI indicator. */
36
+ export type ChoiceCardMark = 'corner' | 'switch' | 'none';
@@ -1,18 +1,18 @@
1
1
  import type { FormControlUi } from '../../types/index.js';
2
- import type { FormChoiceCardProps } from './types.js';
2
+ import type { ChoiceCardMark, FormChoiceCardProps } from './types.js';
3
3
  /** Props the card fields render themselves, kept away from the Nuxt UI group. */
4
4
  export declare const CHOICE_CARD_PROPS: string[];
5
5
  /**
6
6
  * Presentation of a radio or checkbox card group: the flowing row or fixed grid of cards, the
7
7
  * selection mark, and where option icons go. Returns the props and `ui` slots for the Nuxt UI
8
- * group, and what the card content renders (`tile`, `corner`).
8
+ * group, and what the card content renders (`tile`, `mark`).
9
9
  */
10
10
  export declare function useChoiceCard(params: {
11
11
  props: () => FormChoiceCardProps;
12
12
  ui: () => FormControlUi | undefined;
13
13
  }): {
14
- corner: import("vue").ComputedRef<boolean>;
15
14
  indicator: import("vue").ComputedRef<"end" | "start" | "hidden" | undefined>;
15
+ mark: import("vue").ComputedRef<ChoiceCardMark>;
16
16
  orientation: import("vue").ComputedRef<"horizontal" | "vertical" | undefined>;
17
17
  style: import("vue").ComputedRef<{
18
18
  '--nut-choice-columns': string;
@@ -9,11 +9,14 @@ export function useChoiceCard(params) {
9
9
  const count = Math.trunc(Number(columnsValue.value));
10
10
  return count > 0 ? count : void 0;
11
11
  });
12
- const corner = computed(() => params.props().indicator === "corner");
12
+ const mark = computed(() => {
13
+ const value = params.props().indicator;
14
+ return value === "corner" || value === "switch" ? value : "none";
15
+ });
13
16
  const tile = computed(() => params.props().icon === "tile");
14
17
  const indicator = computed(() => {
15
18
  const value = params.props().indicator;
16
- return value === "corner" ? "hidden" : value;
19
+ return value === "corner" || value === "switch" ? "hidden" : value;
17
20
  });
18
21
  const orientation = computed(() => params.props().orientation);
19
22
  const style = computed(
@@ -29,7 +32,7 @@ export function useChoiceCard(params) {
29
32
  ),
30
33
  item: mergeFormUiClass(
31
34
  `border-default ${CARD_SELECTED_RING}`,
32
- corner.value ? "relative" : void 0,
35
+ mark.value === "none" ? void 0 : "relative",
33
36
  current?.item
34
37
  ),
35
38
  // The card label renders the option icon itself (inline or as a tile): Nuxt UI's own copy
@@ -45,11 +48,12 @@ export function useChoiceCard(params) {
45
48
  tile.value ? "flex flex-col items-start gap-1.5" : void 0,
46
49
  [
47
50
  indicator.value === "hidden" ? "ms-0 text-start" : "",
48
- corner.value && !tile.value ? "pe-7" : ""
51
+ mark.value === "corner" && !tile.value ? "pe-7" : "",
52
+ mark.value === "switch" && !tile.value ? "pe-12" : ""
49
53
  ].join(" "),
50
54
  current?.wrapper
51
55
  )
52
56
  };
53
57
  });
54
- return { corner, indicator, orientation, style, tile, ui };
58
+ return { indicator, mark, orientation, style, tile, ui };
55
59
  }
@@ -49,8 +49,9 @@ const items = computed(() => [...options.items.value]);
49
49
  <ChoiceCardLabel
50
50
  :label="item.label"
51
51
  :icon="item.icon"
52
+ :value="item.value"
52
53
  :tile="cards.tile.value"
53
- :corner="cards.corner.value"
54
+ :mark="cards.mark.value"
54
55
  :selected="model === item.value"
55
56
  :ui="cards.ui.value"
56
57
  />
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuxt-ui-tools",
3
- "version": "1.16.7",
3
+ "version": "1.16.9",
4
4
  "description": "Nuxt module and runtime toolkit for UI-heavy Nuxt applications",
5
5
  "homepage": "https://github.com/ChronicStone/nuxt-ui-tools#readme",
6
6
  "bugs": "https://github.com/ChronicStone/nuxt-ui-tools/issues",