@xy-planning-network/trees 0.15.2 → 0.15.4

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 (53) hide show
  1. package/dist/trees.es.js +17051 -12088
  2. package/dist/trees.umd.js +81 -13
  3. package/package.json +3 -2
  4. package/src/index.css +10 -0
  5. package/src/lib-components/forms/Combobox.vue +240 -0
  6. package/src/lib-components/forms/FileUpload.vue +596 -0
  7. package/src/lib-components/forms/FormCell.vue +52 -0
  8. package/src/lib-components/forms/FormDisplay.vue +96 -0
  9. package/src/lib-components/forms/FormGrid.vue +23 -0
  10. package/src/lib-components/forms/FormSection.vue +57 -0
  11. package/src/lib-components/forms/InputDisplay.vue +36 -0
  12. package/src/lib-components/forms/InputHelp.vue +9 -2
  13. package/src/lib-components/forms/MultiSelect.vue +404 -0
  14. package/src/lib-components/forms/RadioCards.vue +14 -1
  15. package/src/lib-components/forms/RepeaterDisplay.vue +214 -0
  16. package/src/lib-components/indicators/DismissableBadge.vue +31 -0
  17. package/types/composables/badge.d.ts +8 -0
  18. package/types/composables/forms.d.ts +22 -0
  19. package/types/composables/http.d.ts +8 -0
  20. package/types/composables/index.d.ts +4 -0
  21. package/types/composables/useFieldsSchema.d.ts +115 -0
  22. package/types/composables/useInlineMarkdown.d.ts +20 -0
  23. package/types/composables/useRepeater.d.ts +116 -0
  24. package/types/entry.d.ts +2 -1
  25. package/types/helpers/Bytes.d.ts +14 -0
  26. package/types/helpers/GetProperty.d.ts +21 -0
  27. package/types/helpers/SetProperty.d.ts +18 -0
  28. package/types/lib-components/forms/BaseInput.vue.d.ts +1 -1
  29. package/types/lib-components/forms/Checkbox.vue.d.ts +1 -1
  30. package/types/lib-components/forms/Combobox.vue.d.ts +16 -0
  31. package/types/lib-components/forms/DateRangePicker.vue.d.ts +3 -3
  32. package/types/lib-components/forms/DateTimeInput.vue.d.ts +1 -1
  33. package/types/lib-components/forms/FileUpload.vue.d.ts +19 -0
  34. package/types/lib-components/forms/FormCell.vue.d.ts +15 -0
  35. package/types/lib-components/forms/FormDisplay.vue.d.ts +30 -0
  36. package/types/lib-components/forms/FormGrid.vue.d.ts +19 -0
  37. package/types/lib-components/forms/FormSection.vue.d.ts +16 -0
  38. package/types/lib-components/forms/InputDisplay.vue.d.ts +14 -0
  39. package/types/lib-components/forms/MultiCheckboxes.vue.d.ts +1 -1
  40. package/types/lib-components/forms/MultiSelect.vue.d.ts +22 -0
  41. package/types/lib-components/forms/NumberInput.vue.d.ts +3 -3
  42. package/types/lib-components/forms/Radio.vue.d.ts +1 -1
  43. package/types/lib-components/forms/RadioCards.vue.d.ts +3 -0
  44. package/types/lib-components/forms/RangeCalendar.vue.d.ts +1 -1
  45. package/types/lib-components/forms/RepeaterDisplay.vue.d.ts +29 -0
  46. package/types/lib-components/forms/Select.vue.d.ts +1 -1
  47. package/types/lib-components/forms/TextArea.vue.d.ts +1 -1
  48. package/types/lib-components/forms/YesOrNoRadio.vue.d.ts +1 -1
  49. package/types/lib-components/index.d.ts +54 -89
  50. package/types/lib-components/indicators/DismissableBadge.vue.d.ts +13 -0
  51. package/types/lib-components/overlays/ContentModal.vue.d.ts +1 -1
  52. package/types/lib-components/overlays/Modal.vue.d.ts +1 -1
  53. package/types/lib-components/overlays/Slideover.vue.d.ts +1 -1
@@ -0,0 +1,214 @@
1
+ <script setup lang="ts">
2
+ import { computed, ref, useId, useTemplateRef, watch } from "vue"
3
+ import { PlusCircleIcon, TrashIcon } from "@heroicons/vue/outline"
4
+ import type { RepeaterRow } from "@/composables/useRepeater"
5
+ import FormCell from "@/lib-components/forms/FormCell.vue"
6
+ import InputError from "@/lib-components/forms/InputError.vue"
7
+ import InputHelp from "@/lib-components/forms/InputHelp.vue"
8
+ import InputLabel from "@/lib-components/forms/InputLabel.vue"
9
+
10
+ const props = withDefaults(
11
+ defineProps<{
12
+ type: RepeaterRow["type"]
13
+ title?: string
14
+ help?: string
15
+ count: number
16
+ min?: number
17
+ max?: number
18
+ rows: RepeaterRow[]
19
+ addText?: string
20
+ addDisabled?: boolean
21
+ removeDisabled?: boolean
22
+ }>(),
23
+ {
24
+ title: "",
25
+ help: "",
26
+ min: undefined,
27
+ max: undefined,
28
+ addText: "Add",
29
+ addDisabled: false,
30
+ removeDisabled: false,
31
+ }
32
+ )
33
+
34
+ const emit = defineEmits<{
35
+ add: []
36
+ remove: [index: number]
37
+ }>()
38
+
39
+ const validationID = useId()
40
+ const errorState = ref("")
41
+ const errorInputRef = useTemplateRef<HTMLInputElement>("errorInput")
42
+
43
+ const countError = computed(() => {
44
+ const belowMin = props.min !== undefined && props.count < props.min
45
+ const aboveMax = props.max !== undefined && props.count > props.max
46
+
47
+ if (props.min !== undefined && props.max !== undefined) {
48
+ return belowMin || aboveMax
49
+ ? `Please add between ${props.min} and ${props.max} items.`
50
+ : ""
51
+ }
52
+
53
+ if (belowMin) {
54
+ return `Please add at least ${props.min} items.`
55
+ }
56
+
57
+ if (aboveMax) {
58
+ return `Please limit this list to ${props.max} items.`
59
+ }
60
+
61
+ return ""
62
+ })
63
+
64
+ const setValidationError = () => {
65
+ errorState.value = countError.value
66
+ errorInputRef.value?.setCustomValidity(countError.value)
67
+ }
68
+
69
+ const isFullWidthField = (row: RepeaterRow) => {
70
+ const span = row.fields[0]?.input.span
71
+ return row.type === "field" && (!span || span === "full")
72
+ }
73
+
74
+ const isRemoveDisabled = (index: number) => {
75
+ const isModelRow = index < props.count
76
+ return props.removeDisabled || (isModelRow && props.count <= (props.min ?? 0))
77
+ }
78
+
79
+ watch(countError, (error) => {
80
+ if (!error) {
81
+ errorInputRef.value?.setCustomValidity("")
82
+ errorState.value = ""
83
+ return
84
+ }
85
+
86
+ if (errorState.value) {
87
+ errorState.value = error
88
+ errorInputRef.value?.setCustomValidity(error)
89
+ }
90
+ })
91
+ </script>
92
+
93
+ <template>
94
+ <div
95
+ class="relative space-y-5"
96
+ role="group"
97
+ :aria-labelledby="title ? `${validationID}-label` : undefined"
98
+ :aria-describedby="help ? `${validationID}-help` : undefined"
99
+ :aria-errormessage="errorState ? `${validationID}-error` : undefined"
100
+ >
101
+ <div v-if="title || help || errorState">
102
+ <InputLabel :id="`${validationID}-label`" tag="div" :label="title" />
103
+ <InputHelp
104
+ :id="`${validationID}-help`"
105
+ :class="{ 'mt-1': title }"
106
+ :text="help"
107
+ />
108
+ <InputError
109
+ :id="`${validationID}-error`"
110
+ :class="{ 'mt-1': title || help }"
111
+ :text="errorState"
112
+ />
113
+ </div>
114
+
115
+ <div
116
+ v-for="(row, rowIndex) in rows"
117
+ :key="row.key"
118
+ class="relative"
119
+ :class="{
120
+ 'rounded-xy border border-neutral-200 p-6': row.type === 'collection',
121
+ }"
122
+ >
123
+ <div
124
+ v-if="row.type === 'collection'"
125
+ class="mb-6 flex items-center justify-between gap-x-4"
126
+ >
127
+ <InputLabel v-if="row.title" tag="div" :label="row.title" />
128
+
129
+ <button
130
+ type="button"
131
+ class="xy-btn-neutral-sm ml-auto shrink-0 gap-x-1.5"
132
+ :disabled="isRemoveDisabled(rowIndex)"
133
+ :aria-label="`Remove item ${rowIndex + 1}`"
134
+ @click="emit('remove', rowIndex)"
135
+ >
136
+ <TrashIcon class="h-4 w-4 text-red-700" aria-hidden="true" />
137
+ <span>Remove</span>
138
+ </button>
139
+ </div>
140
+
141
+ <div
142
+ v-if="row.type === 'field'"
143
+ :class="
144
+ isFullWidthField(row)
145
+ ? 'flex items-center gap-x-3'
146
+ : 'grid grid-cols-[minmax(0,1fr)_auto] items-center gap-x-3 sm:grid-cols-12'
147
+ "
148
+ >
149
+ <template v-for="field in row.fields" :key="field.key">
150
+ <FormCell
151
+ v-if="field.input.show"
152
+ :class="{ 'min-w-0 flex-1': isFullWidthField(row) }"
153
+ :span="field.input.span || 'full'"
154
+ :start="field.input.start"
155
+ >
156
+ <component
157
+ :is="field.input.$component"
158
+ v-bind="field.input.$props"
159
+ />
160
+ </FormCell>
161
+ </template>
162
+
163
+ <button
164
+ type="button"
165
+ class="xy-btn-neutral-sm shrink-0 justify-self-start"
166
+ :disabled="isRemoveDisabled(rowIndex)"
167
+ :aria-label="`Remove item ${rowIndex + 1}`"
168
+ @click="emit('remove', rowIndex)"
169
+ >
170
+ <TrashIcon class="h-5 w-5 text-red-700" aria-hidden="true" />
171
+ </button>
172
+ </div>
173
+
174
+ <div v-else>
175
+ <div class="grid grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-12">
176
+ <template v-for="field in row.fields" :key="field.key">
177
+ <FormCell
178
+ v-if="field.input.show"
179
+ :span="field.input.span || 'full'"
180
+ :start="field.input.start"
181
+ >
182
+ <component
183
+ :is="field.input.$component"
184
+ v-bind="field.input.$props"
185
+ />
186
+ </FormCell>
187
+ </template>
188
+ </div>
189
+ </div>
190
+ </div>
191
+
192
+ <button
193
+ type="button"
194
+ class="xy-btn-neutral-sm"
195
+ :disabled="addDisabled"
196
+ @click="emit('add')"
197
+ >
198
+ <PlusCircleIcon class="mr-1 h-5 w-5 text-xy-blue" aria-hidden="true" />
199
+ {{ addText }}
200
+ </button>
201
+
202
+ <!-- Participates in native form validation when the repeater is outside its limits. -->
203
+ <input
204
+ v-if="countError"
205
+ ref="errorInput"
206
+ required
207
+ class="sr-only top-1 left-1"
208
+ aria-hidden
209
+ :tabindex="errorState ? undefined : -1"
210
+ type="checkbox"
211
+ @invalid="setValidationError"
212
+ />
213
+ </div>
214
+ </template>
@@ -0,0 +1,31 @@
1
+ <script setup lang="ts">
2
+ import { BadgeKind } from "@/composables/badge"
3
+ import { XMarkIcon } from "@heroicons/vue/solid"
4
+
5
+ withDefaults(defineProps<{ label: string; kind?: BadgeKind }>(), {
6
+ kind: BadgeKind.Neutral,
7
+ })
8
+ defineEmits<{
9
+ dismiss: [label: string]
10
+ }>()
11
+ </script>
12
+
13
+ <template>
14
+ <span
15
+ :class="[kind, 'flex items-center gap-x-1 group cursor-pointer']"
16
+ @click="$emit('dismiss', label)"
17
+ >
18
+ <span class="flex-1 truncate">{{ label }}</span>
19
+ <button
20
+ type="button"
21
+ class="group relative -mr-1.5 h-3.5 w-3.5 rounded-sm group-hover:bg-gray-500/10 inline-flex items-center justify-center"
22
+ >
23
+ <span class="sr-only">Remove</span>
24
+ <XMarkIcon
25
+ class="h-3 w-3 stroke-gray-500/20 group-hover:stroke-gray-500/75"
26
+ />
27
+ <!--makes the clickable target larger-->
28
+ <span class="absolute -inset-2" />
29
+ </button>
30
+ </span>
31
+ </template>
@@ -0,0 +1,8 @@
1
+ export declare enum BadgeKind {
2
+ Neutral = "xy-badge",
3
+ Blue = "xy-badge-blue",
4
+ Green = "xy-badge-green",
5
+ Yellow = "xy-badge-yellow",
6
+ Red = "xy-badge-red"
7
+ }
8
+ export declare const isBadgeKind: (kind: string) => kind is BadgeKind;
@@ -41,6 +41,28 @@ export interface DateRangeInput extends Input {
41
41
  export interface DateTimeInput extends Input {
42
42
  modelValue?: string | null;
43
43
  }
44
+ /**
45
+ * UploadedFile is the durable value stored in a form model after a file is
46
+ * uploaded.
47
+ *
48
+ * NOTE(spk): Add display metadata like file size, content type, or timestamps
49
+ * here as upload APIs need it. The ID is the authoritative value.
50
+ */
51
+ export interface UploadedFile {
52
+ id: number;
53
+ filename: string;
54
+ url?: string;
55
+ }
56
+ export type FileUploadValue = UploadedFile | UploadedFile[] | null;
57
+ export interface FileUploadInput extends Input {
58
+ action: string;
59
+ accept?: string[];
60
+ multiple?: boolean;
61
+ fileField?: string;
62
+ maxFileBytes?: number;
63
+ maxFiles?: number;
64
+ modelValue?: FileUploadValue;
65
+ }
44
66
  export interface TextLikeInput extends Input {
45
67
  modelValue?: string | number | null;
46
68
  type: TextInputType;
@@ -0,0 +1,8 @@
1
+ export declare const HTTP_BAD_REQUEST = 400;
2
+ export declare const HTTP_CONFLICT = 409;
3
+ export declare const HTTP_NOT_FOUND = 404;
4
+ export declare const HTTP_NOT_IMPLEMENTED = 501;
5
+ export declare const HTTP_UNPROCESSABLE_ENTITY = 422;
6
+ export declare const HTTP_TOO_MANY_REQUESTS = 429;
7
+ export declare const HTTP_INTERNAL_SERVER_ERROR = 500;
8
+ export declare const statusMessages: Record<number, string>;
@@ -16,3 +16,7 @@ import { isActionItemButton, isActionItemLink, useUrlSearchParams, useTabHistory
16
16
  export { isActionItemButton, isActionItemLink, useUrlSearchParams, useTabHistory, };
17
17
  import { useSpinnerDisplay, useAppSpinner } from "./useSpinner";
18
18
  export { useSpinnerDisplay, useAppSpinner };
19
+ export * from "./badge";
20
+ export * from "./useFieldsSchema";
21
+ export * from "./useRepeater";
22
+ export * from "./useInlineMarkdown";
@@ -0,0 +1,115 @@
1
+ import { Component, ComputedRef, MaybeRefOrGetter, Ref } from "vue";
2
+ import { type BooleanInput, type DateRangeInput, type FileUploadInput, type Input, type MultiChoiceInput, type OptionsInput, type TextInputType, type TextLikeInput, type TextareaInput, type DateTimeInput, type NumericInput, type NumericInputType } from "../composables/forms";
3
+ import { type DisplayRepeater, type Repeater } from "../composables/useRepeater";
4
+ /**
5
+ * FieldsSchema supports an Array of FieldSection(s)
6
+ * or simply an Array of SchemaField(s)
7
+ */
8
+ export type FieldsSchema = Array<FieldSection> | Array<SchemaField>;
9
+ /**
10
+ * FieldsSchemaInput type declares the supported input components in a FieldsSchema
11
+ */
12
+ export type FieldsSchemaInput = InputField<BooleanInput, "checkbox" | "toggle" | "yes-no-radio"> | InputField<MultiChoiceInput, "multi-checkbox"> | InputField<MultiChoiceInput & {
13
+ customValues?: boolean;
14
+ }, "multi-select"> | InputField<DateRangeInput, "date-range"> | InputField<DateTimeInput, "datetime"> | InputField<FileUploadInput, "file-upload"> | InputField<NumericInput, NumericInputType> | InputField<OptionsInput, "combobox" | "radio" | "radio-cards" | "select"> | InputField<TextareaInput, "textarea"> | InputField<TextLikeInput, Exclude<TextInputType, "number">>;
15
+ /** An input with the component properties needed for display. */
16
+ export type DisplayInput = FieldsSchemaInput & {
17
+ $component: Component;
18
+ $props: Record<string, any>;
19
+ show: boolean;
20
+ };
21
+ export type SchemaField = FieldsSchemaInput | Repeater;
22
+ /**
23
+ * isTextInputType is a user defined type guard for
24
+ * narrowing if a FieldsSchemaInput in a FieldsSchema is a text
25
+ * like type, ex: input type="text | date | url..."
26
+ *
27
+ * @param type string
28
+ * @returns boolean
29
+ */
30
+ export declare const isTextInputType: (type: string) => type is "number" | "url" | "search" | "time" | "text" | "date" | "email" | "month" | "password" | "tel" | "week";
31
+ /**
32
+ * isNumericInputType is a user defined type guard for
33
+ * narrowing if a FieldsSchemaInput in a FieldsSchema is a numeric
34
+ * like type, ex: input type="money | number..."
35
+ *
36
+ * @param type string
37
+ * @returns boolean
38
+ */
39
+ export declare const isNumericInputType: (type: string) => type is "number" | "money" | "raw-number";
40
+ /**
41
+ * isInputsOnlySchema is a user defined type guard for narrowing that
42
+ * a FieldsSchema is only made up of FieldsSchemaInputs
43
+ *
44
+ * @param s FieldsSchema
45
+ * @returns boolean
46
+ */
47
+ export declare const isInputsOnlySchema: (s: FieldsSchema) => s is FieldsSchemaInput[];
48
+ /**
49
+ * A FieldSection maps a grouping of inputs in a FieldsSchema
50
+ * to a FormSection in the FormGrid component.
51
+ */
52
+ export interface FieldSection {
53
+ title?: string;
54
+ description?: string;
55
+ fields: Array<SchemaField>;
56
+ }
57
+ export type DisplayField = DisplayInput | DisplayRepeater;
58
+ export type DisplaySection = Omit<FieldSection, "fields"> & {
59
+ fields: DisplayField[];
60
+ };
61
+ export type InputFieldType = (typeof inputFieldTypes)[number];
62
+ export declare const inputFieldTypes: readonly ["date", "email", "month", "number", "password", "search", "tel", "text", "time", "url", "week", "money", "number", "raw-number", "checkbox", "combobox", "date-range", "datetime", "file-upload", "multi-checkbox", "multi-select", "radio", "radio-cards", "select", "textarea", "toggle", "yes-no-radio"];
63
+ /**
64
+ * InputField is the type used to declare an option of FieldsSchemaInput
65
+ * The Input interface is enforced against the InputFieldType to avoid applying unsupported
66
+ * modelValues on the input as well as ensure required props are available for the InputFieldType.
67
+ */
68
+ export type InputField<I extends Input, T extends InputFieldType> = I & {
69
+ type: T;
70
+ name: string;
71
+ columns?: 2 | 3;
72
+ span?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "full";
73
+ start?: boolean;
74
+ show?: boolean;
75
+ disabled?: boolean;
76
+ autocomplete?: string;
77
+ min?: number | string;
78
+ max?: number | string;
79
+ minlength?: number;
80
+ maxlength?: number;
81
+ pattern?: string;
82
+ required?: boolean;
83
+ };
84
+ /**
85
+ * Narrows an unsectioned schema containing ordinary inputs and/or repeaters.
86
+ */
87
+ export declare const isFieldsOnlySchema: (schema: FieldsSchema) => schema is SchemaField[];
88
+ /**
89
+ * disableSchemaFields is a utility function for disabling all input fields
90
+ * in a FieldSchema. Useful for rendering forms as read-only.
91
+ *
92
+ * @param schema FieldsSchema
93
+ * @returns FieldsSchema
94
+ */
95
+ export declare const disableSchemaFields: (schema: FieldsSchema) => FieldsSchema;
96
+ /**
97
+ * extractInputs is a utility function for extracting all directly bound
98
+ * FieldsSchemaInput entries out of a FieldSchema. Repeater templates are
99
+ * skipped because their names are incomplete or relative until rendered.
100
+ *
101
+ * @param schema FieldsSchema
102
+ * @returns Array<FieldsSchemaInput>
103
+ */
104
+ export declare const extractInputs: (schema: FieldsSchema) => Array<FieldsSchemaInput>;
105
+ /**
106
+ * useFieldsSchema computes the FieldSections of a FieldSchema and tracks
107
+ * input values on the model passed as the first argument. This encapsulates
108
+ * the necessary logic for maintaining input reactivity for use in form
109
+ * @param model Ref<Record<string, any>
110
+ * @param schema MaybeRefOrGetter<FieldsSchema>
111
+ * @returns { fieldSections: ComputedRef<FieldSection[]> }
112
+ */
113
+ export declare const useFieldsSchema: (model: Ref<Record<string, any>>, schema: MaybeRefOrGetter<FieldsSchema>) => {
114
+ fieldSections: ComputedRef<DisplaySection[]>;
115
+ };
@@ -0,0 +1,20 @@
1
+ import { type VNode, type VNodeArrayChildren, type MaybeRefOrGetter, type ComputedRef } from "vue";
2
+ export type MarkdownNode = VNode | string | VNodeArrayChildren;
3
+ export interface InlineMarkdownConfig {
4
+ graphs: boolean;
5
+ }
6
+ export type UseInlineMarkdown = ComputedRef<(() => null) | (() => MarkdownNode | MarkdownNode[])>;
7
+ /**
8
+ * Creates a Vue render function for a subset of Markdown.
9
+ *
10
+ * `marked` is used only to tokenize the source. We parse those
11
+ * tokens and map supported syntax to VNodes instead of rendering and
12
+ * injecting HTML - this allows Vue to continue to do the sanitizing.
13
+ *
14
+ * Unsupported tokens, including raw HTML, remain plain text and
15
+ * are escaped by Vue.
16
+ *
17
+ * Enable `graphs` to preserve paragraph wrappers; otherwise
18
+ * all contents are rendered inline.
19
+ */
20
+ export declare function useInlineMarkdown(markdown: MaybeRefOrGetter<string>, conf?: InlineMarkdownConfig): UseInlineMarkdown;
@@ -0,0 +1,116 @@
1
+ import { type Component, type Ref } from "vue";
2
+ import type { DisplayInput, FieldsSchemaInput } from "../composables/useFieldsSchema";
3
+ /**
4
+ * Removes repeater-owned properties from every supported input type while
5
+ * keeping required properties such as `options`.
6
+ */
7
+ type RepeaterSchemaInput<T, K extends PropertyKey> = T extends unknown ? Omit<T, Extract<keyof T, K>> : never;
8
+ /**
9
+ * The input repeated by a field repeater. Its name and value come from the
10
+ * repeater definition.
11
+ */
12
+ export type RepeaterInput = RepeaterSchemaInput<FieldsSchemaInput, "name" | "modelValue">;
13
+ /**
14
+ * An input inside a collection row. Names are relative to the row, and initial
15
+ * values come from the repeater model.
16
+ */
17
+ export type RepeaterCollectionInput = RepeaterSchemaInput<FieldsSchemaInput, "modelValue">;
18
+ export type IndexPosition = "prefix" | "suffix";
19
+ export interface RepeaterBase<T> {
20
+ type: "repeater";
21
+ name: string;
22
+ modelValue?: T[];
23
+ title?: string;
24
+ help?: string;
25
+ /**
26
+ * Adds an visible index to labels at the specified position
27
+ * e.g. Email 1, Email 2 | 1. Contact, 2. Contact
28
+ */
29
+ indexPosition?: IndexPosition;
30
+ /**
31
+ * Limits Add/Remove actions and validates the number of items. Existing model
32
+ * values are shown as-is, even when they fall outside these limits.
33
+ */
34
+ min?: number;
35
+ max?: number;
36
+ addText?: string;
37
+ disabled?: boolean;
38
+ span?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "full";
39
+ start?: boolean;
40
+ show?: boolean;
41
+ }
42
+ /**
43
+ * Repeats one input and stores each input's value in the resulting array.
44
+ *
45
+ * @example
46
+ * {
47
+ * type: "repeater",
48
+ * name: "phone_numbers",
49
+ * title: "Phone numbers",
50
+ * help: "Add each number where you can be reached.",
51
+ * indexPosition: "suffix",
52
+ * addText: "Add phone number",
53
+ * field: { type: "tel", label: "Phone number" }
54
+ * }
55
+ */
56
+ export interface FieldRepeater extends RepeaterBase<unknown> {
57
+ field: RepeaterInput;
58
+ fields?: never;
59
+ }
60
+ /**
61
+ * Repeats a set of named inputs and stores each row as an object. Nested
62
+ * repeaters are not supported.
63
+ *
64
+ * @example
65
+ * {
66
+ * type: "repeater",
67
+ * name: "household_members",
68
+ * title: "Household members",
69
+ * itemTitle: "Household member",
70
+ * help: "Include everyone who lives in your household.",
71
+ * indexPosition: "prefix",
72
+ * min: 1,
73
+ * fields: [
74
+ * { type: "text", name: "name", label: "Name" },
75
+ * { type: "number", name: "age", label: "Age" }
76
+ * ]
77
+ * }
78
+ */
79
+ export interface CollectionRepeater extends RepeaterBase<Record<string, any>> {
80
+ field?: never;
81
+ itemTitle?: string;
82
+ fields: RepeaterCollectionInput[];
83
+ }
84
+ export type Repeater = FieldRepeater | CollectionRepeater;
85
+ /** A repeater with the component properties needed for display. */
86
+ export type DisplayRepeater = Repeater & {
87
+ $component: Component;
88
+ $props: Record<string, any>;
89
+ show: boolean;
90
+ };
91
+ /**
92
+ * Returns true when a schema field repeats one input.
93
+ */
94
+ export declare const isFieldRepeater: (field: any) => field is FieldRepeater;
95
+ /**
96
+ * Returns true when a schema field repeats a collection of inputs.
97
+ */
98
+ export declare const isCollectionRepeater: (field: any) => field is CollectionRepeater;
99
+ export interface RowField {
100
+ key: string;
101
+ input: DisplayInput;
102
+ }
103
+ export interface RepeaterRow {
104
+ key: string;
105
+ type: "field" | "collection";
106
+ title?: string;
107
+ fields: RowField[];
108
+ }
109
+ /**
110
+ * Builds repeater rows for display and handles adding, updating, and removing
111
+ * their values. Used by useFieldsSchema.
112
+ */
113
+ export declare const useRepeater: (model: Ref<Record<string, any>>, toDisplayInput: (input: FieldsSchemaInput, onUpdate?: ($val: any) => void) => DisplayInput) => {
114
+ toDisplayRepeater: (repeater: Repeater) => DisplayRepeater;
115
+ };
116
+ export {};
package/types/entry.d.ts CHANGED
@@ -4,6 +4,7 @@ import type { HttpPromise, HttpError, QueryParams, ReqOptions, ReqPayload, Trail
4
4
  import { emailPattern, looseToNumber, numericInputTypes, phonePattern, urlPattern, textInputTypes, useInputField } from "./composables/forms";
5
5
  import { debounce as debounceFn, debounceLeading } from "./helpers/Debounce";
6
6
  import { throttle as throttleFn } from "./helpers/Throttle";
7
+ import bytes from "./helpers/Bytes";
7
8
  declare const install: Exclude<Plugin["install"], undefined>;
8
9
  export default install;
9
10
  export * from "./composables/index";
@@ -12,4 +13,4 @@ export { BaseAPI, isHttpCancel, isHttpError, setBaseAPIDefaults };
12
13
  export type { HttpPromise, HttpError, QueryParams, ReqOptions, ReqPayload, TrailsPromise, TrailsPromisePaged, TrailsResp, TrailsRespPaged, };
13
14
  export type * from "./composables/forms";
14
15
  export { emailPattern, looseToNumber, phonePattern, urlPattern, numericInputTypes, textInputTypes, useInputField, };
15
- export { debounceFn, debounceLeading, throttleFn };
16
+ export { bytes, debounceFn, debounceLeading, throttleFn };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * bytes converts and formats common file sizes.
3
+ *
4
+ * @example
5
+ * bytes.megabytes(5) // 5242880
6
+ * bytes.format(5242880) // "5.0 MB"
7
+ */
8
+ declare const bytes: {
9
+ kilobytes: (value: number) => number;
10
+ megabytes: (value: number) => number;
11
+ gigabytes: (value: number) => number;
12
+ format: (value: number) => string;
13
+ };
14
+ export default bytes;
@@ -0,0 +1,21 @@
1
+ /**
2
+ * getProperty reads a nested property from an object using a dot-separated path.
3
+ * Numeric path segments can be used to access array elements, such as
4
+ * `items.0.name`.
5
+ *
6
+ * `defaultValue` is returned when the path cannot be fully traversed or when the
7
+ * resolved value is `undefined`.
8
+ *
9
+ * Type safety is intentionally limited because `path` is a dynamic string.
10
+ * TypeScript does not verify that the path exists on `O` or that its value is
11
+ * `T`; supplying `T` is a caller assertion and does not perform runtime
12
+ * validation. Omit `T` to receive `any | undefined` when no default is
13
+ * provided.
14
+ *
15
+ * @example
16
+ * getProperty<User, string>(user, "profile.name") // string | undefined
17
+ * getProperty<User, string>(user, "profile.name", "Unknown") // string
18
+ */
19
+ declare function getProperty<O extends Record<string, any> = Record<string, any>, T = any>(obj: O, path: string): T | undefined;
20
+ declare function getProperty<O extends Record<string, any> = Record<string, any>, T = any>(obj: O, path: string, defaultValue: T): T;
21
+ export default getProperty;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * setProperty assigns a value on an object using a dot-separated path. It
3
+ * returns a new object without mutating `obj`. Each container along the path
4
+ * is cloned, while containers outside the path retain their references.
5
+ *
6
+ * Numeric path segments create arrays when a container is missing, so
7
+ * `items.0.name` creates an object within an `items` array. Other missing
8
+ * containers are created as objects.
9
+ *
10
+ * Paths containing `__proto__`, `constructor`, or `prototype` are ignored to
11
+ * avoid modifying object prototypes.
12
+ *
13
+ * @example
14
+ * setProperty(user, "profile.name", "Ada")
15
+ * setProperty(form, "items.0.name", "First item")
16
+ */
17
+ declare function setProperty<O extends Record<string, any>>(obj: O, path: string, value: unknown): O;
18
+ export default setProperty;
@@ -11,8 +11,8 @@ declare const _default: import("vue").DefineComponent<__VLS_PublicProps, {
11
11
  "onUpdate:modelValue"?: ((value: string | number | null | undefined) => any) | undefined;
12
12
  }>, {
13
13
  label: string;
14
- modelValue: string | number | null;
15
14
  help: string;
15
+ modelValue: string | number | null;
16
16
  placeholder: string;
17
17
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {
18
18
  input: HTMLInputElement;
@@ -9,8 +9,8 @@ declare const _default: import("vue").DefineComponent<__VLS_PublicProps, {}, {},
9
9
  "onUpdate:modelValue"?: ((value: boolean | null | undefined) => any) | undefined;
10
10
  }>, {
11
11
  label: string;
12
- modelValue: boolean | null;
13
12
  help: string;
13
+ modelValue: boolean | null;
14
14
  placeholder: string;
15
15
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
16
16
  export default _default;
@@ -0,0 +1,16 @@
1
+ import { type OptionsInput } from "../../composables/forms";
2
+ type __VLS_Props = OptionsInput;
3
+ type __VLS_PublicProps = {
4
+ modelValue?: OptionsInput["modelValue"];
5
+ } & __VLS_Props;
6
+ declare const _default: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
7
+ "update:modelValue": (value: string | number | null | undefined) => any;
8
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
9
+ "onUpdate:modelValue"?: ((value: string | number | null | undefined) => any) | undefined;
10
+ }>, {
11
+ label: string;
12
+ help: string;
13
+ modelValue: string | number | null;
14
+ placeholder: string;
15
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
16
+ export default _default;