@xy-planning-network/trees 0.15.2 → 0.15.3
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/trees.es.js +17051 -12088
- package/dist/trees.umd.js +81 -13
- package/package.json +3 -2
- package/src/index.css +10 -0
- package/src/lib-components/forms/Combobox.vue +240 -0
- package/src/lib-components/forms/FileUpload.vue +596 -0
- package/src/lib-components/forms/FormCell.vue +52 -0
- package/src/lib-components/forms/FormDisplay.vue +96 -0
- package/src/lib-components/forms/FormGrid.vue +23 -0
- package/src/lib-components/forms/FormSection.vue +57 -0
- package/src/lib-components/forms/InputDisplay.vue +36 -0
- package/src/lib-components/forms/InputHelp.vue +9 -2
- package/src/lib-components/forms/MultiSelect.vue +404 -0
- package/src/lib-components/forms/RadioCards.vue +14 -1
- package/src/lib-components/forms/RepeaterDisplay.vue +214 -0
- package/src/lib-components/indicators/DismissableBadge.vue +31 -0
- package/types/composables/badge.d.ts +8 -0
- package/types/composables/forms.d.ts +22 -0
- package/types/composables/http.d.ts +8 -0
- package/types/composables/index.d.ts +4 -0
- package/types/composables/useFieldsSchema.d.ts +115 -0
- package/types/composables/useInlineMarkdown.d.ts +20 -0
- package/types/composables/useRepeater.d.ts +116 -0
- package/types/entry.d.ts +2 -1
- package/types/helpers/Bytes.d.ts +14 -0
- package/types/helpers/GetProperty.d.ts +21 -0
- package/types/helpers/SetProperty.d.ts +18 -0
- package/types/lib-components/forms/BaseInput.vue.d.ts +1 -1
- package/types/lib-components/forms/Checkbox.vue.d.ts +1 -1
- package/types/lib-components/forms/Combobox.vue.d.ts +16 -0
- package/types/lib-components/forms/DateRangePicker.vue.d.ts +3 -3
- package/types/lib-components/forms/DateTimeInput.vue.d.ts +1 -1
- package/types/lib-components/forms/FileUpload.vue.d.ts +19 -0
- package/types/lib-components/forms/FormCell.vue.d.ts +15 -0
- package/types/lib-components/forms/FormDisplay.vue.d.ts +30 -0
- package/types/lib-components/forms/FormGrid.vue.d.ts +19 -0
- package/types/lib-components/forms/FormSection.vue.d.ts +16 -0
- package/types/lib-components/forms/InputDisplay.vue.d.ts +14 -0
- package/types/lib-components/forms/MultiCheckboxes.vue.d.ts +1 -1
- package/types/lib-components/forms/MultiSelect.vue.d.ts +22 -0
- package/types/lib-components/forms/NumberInput.vue.d.ts +3 -3
- package/types/lib-components/forms/Radio.vue.d.ts +1 -1
- package/types/lib-components/forms/RadioCards.vue.d.ts +3 -0
- package/types/lib-components/forms/RangeCalendar.vue.d.ts +1 -1
- package/types/lib-components/forms/RepeaterDisplay.vue.d.ts +29 -0
- package/types/lib-components/forms/Select.vue.d.ts +1 -1
- package/types/lib-components/forms/TextArea.vue.d.ts +1 -1
- package/types/lib-components/forms/YesOrNoRadio.vue.d.ts +1 -1
- package/types/lib-components/index.d.ts +54 -89
- package/types/lib-components/indicators/DismissableBadge.vue.d.ts +13 -0
- package/types/lib-components/overlays/ContentModal.vue.d.ts +1 -1
- package/types/lib-components/overlays/Modal.vue.d.ts +1 -1
- 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>
|
|
@@ -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;
|