@ramathibodi/nuxt-commons 4.0.10 → 4.0.12
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/README.md +8 -0
- package/dist/module.json +1 -1
- package/dist/runtime/components/document/TemplateBuilder.d.vue.ts +8 -3
- package/dist/runtime/components/document/TemplateBuilder.vue +22 -42
- package/dist/runtime/components/document/TemplateBuilder.vue.d.ts +8 -3
- package/dist/runtime/components/form/ActionPad.vue +1 -0
- package/dist/runtime/components/form/Birthdate.d.vue.ts +3 -3
- package/dist/runtime/components/form/Birthdate.vue.d.ts +3 -3
- package/dist/runtime/components/form/Date.vue +11 -6
- package/dist/runtime/components/form/Dialog.d.vue.ts +1 -5
- package/dist/runtime/components/form/Dialog.vue +1 -0
- package/dist/runtime/components/form/Dialog.vue.d.ts +1 -5
- package/dist/runtime/components/form/EditPad.vue +1 -0
- package/dist/runtime/components/form/Pad.d.vue.ts +24 -0
- package/dist/runtime/components/form/Pad.vue +12 -7
- package/dist/runtime/components/form/Pad.vue.d.ts +24 -0
- package/dist/runtime/components/form/Time.vue +10 -5
- package/dist/runtime/components/form/images/Edit.d.vue.ts +1 -3
- package/dist/runtime/components/form/images/Edit.vue.d.ts +1 -3
- package/dist/runtime/components/model/AutoRefreshChip.d.vue.ts +16 -0
- package/dist/runtime/components/model/AutoRefreshChip.vue +34 -0
- package/dist/runtime/components/model/AutoRefreshChip.vue.d.ts +16 -0
- package/dist/runtime/components/model/Table.d.vue.ts +91 -61
- package/dist/runtime/components/model/Table.vue +24 -5
- package/dist/runtime/components/model/Table.vue.d.ts +91 -61
- package/dist/runtime/components/model/iterator.d.vue.ts +103 -71
- package/dist/runtime/components/model/iterator.vue +24 -5
- package/dist/runtime/components/model/iterator.vue.d.ts +103 -71
- package/dist/runtime/composables/apiModel.d.ts +2 -2
- package/dist/runtime/composables/apiModel.js +3 -3
- package/dist/runtime/composables/autoRefresh.d.ts +42 -0
- package/dist/runtime/composables/autoRefresh.js +57 -0
- package/dist/runtime/composables/document/template.js +10 -1
- package/dist/runtime/composables/document/templateInputTypes.d.ts +228 -0
- package/dist/runtime/composables/document/templateInputTypes.js +128 -0
- package/dist/runtime/composables/graphqlModel.d.ts +2 -2
- package/dist/runtime/composables/graphqlModel.js +3 -3
- package/dist/runtime/composables/modelAutoRefresh.d.ts +29 -0
- package/dist/runtime/composables/modelAutoRefresh.js +16 -0
- package/dist/runtime/composables/utils/validation.d.ts +4 -0
- package/dist/runtime/composables/utils/validation.js +2 -0
- package/package.json +4 -2
- package/scripts/generate-ai-summary.mjs +88 -0
- package/scripts/scaffold-playground-pages.mjs +207 -0
package/README.md
CHANGED
|
@@ -6,6 +6,14 @@ Nuxt module for Nuxt 3 and Nuxt 4 that provides shared runtime building blocks f
|
|
|
6
6
|
- runtime plugins for permission/dialog/default behavior
|
|
7
7
|
- utility and lab exports
|
|
8
8
|
|
|
9
|
+
## Documentation & playground
|
|
10
|
+
|
|
11
|
+
The live playground is the project's documentation site — every component has a real demo page running the real module against the real Vuetify theme, with a Vuetify-docs-style API panel (Props / Emits / Slots / Exposed) sourced from `docs/ai-summary.json`. Bug-repro fixtures live under *Scenarios*.
|
|
12
|
+
|
|
13
|
+
→ **https://ramacare.gitlab.rama.mahidol.ac.th/frontend/rama-modules/** _(published from `master` by the `pages` CI job; if the project later moves to a unique Pages domain, update `NUXT_APP_BASE_URL` in the CI job accordingly)_
|
|
14
|
+
|
|
15
|
+
Run it locally with `pnpm run dev`. See `CLAUDE.md` > *Playground* for the conventions every new/modified component must follow.
|
|
16
|
+
|
|
9
17
|
## Install
|
|
10
18
|
|
|
11
19
|
```bash
|
package/dist/module.json
CHANGED
|
@@ -5,7 +5,12 @@ interface Props {
|
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
7
|
* Public props accepted by DocumentTemplateBuilder.
|
|
8
|
-
*
|
|
8
|
+
*
|
|
9
|
+
* To extend the Input Type dropdown with app-specific entries, call
|
|
10
|
+
* `registerDocumentTemplateInputType()` from a Nuxt plugin (app-wide) or
|
|
11
|
+
* `useDocumentTemplateInputType()` inside `<script setup>` of a page
|
|
12
|
+
* (page-scoped, auto-cleanup on unmount). Both flow through the same
|
|
13
|
+
* runtime registry that this component reads via `useDocumentTemplateInputTypes()`.
|
|
9
14
|
*/
|
|
10
15
|
type __VLS_Props = Props;
|
|
11
16
|
type __VLS_ModelProps = {
|
|
@@ -13,9 +18,9 @@ type __VLS_ModelProps = {
|
|
|
13
18
|
};
|
|
14
19
|
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
15
20
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
16
|
-
"update:modelValue": (
|
|
21
|
+
"update:modelValue": (value: string | Record<string, any>[] | undefined) => any;
|
|
17
22
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
18
|
-
"onUpdate:modelValue"?: ((
|
|
23
|
+
"onUpdate:modelValue"?: ((value: string | Record<string, any>[] | undefined) => any) | undefined;
|
|
19
24
|
}>, {
|
|
20
25
|
title: string;
|
|
21
26
|
disableAdvanceMode: boolean;
|
|
@@ -3,6 +3,9 @@ import { computed, ref, watch } from "vue";
|
|
|
3
3
|
import * as prettier from "prettier";
|
|
4
4
|
import prettierPluginHtml from "prettier/plugins/html";
|
|
5
5
|
import { useDocumentTemplate, validationRulesRegex, optionStringToChoiceObject } from "../../composables/document/template";
|
|
6
|
+
import {
|
|
7
|
+
useDocumentTemplateInputTypes
|
|
8
|
+
} from "../../composables/document/templateInputTypes";
|
|
6
9
|
import { autoActionHeader, templateToHeader } from "../../composables/document/templateFormTable";
|
|
7
10
|
import { cloneDeep } from "lodash-es";
|
|
8
11
|
import VueJsonPretty from "vue-json-pretty";
|
|
@@ -113,47 +116,24 @@ async function convertToAdvanceMode() {
|
|
|
113
116
|
modelValue.value = await prettier.format(useDocumentTemplate(templateItems.value).replaceAll(">", ">\n"), { parser: "html", plugins: [prettierPluginHtml] });
|
|
114
117
|
}
|
|
115
118
|
}
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
{ label: "Table Data", value: "FormTableData" },
|
|
135
|
-
{ label: "[Decoration] Header", value: "Header" },
|
|
136
|
-
{ label: "[Decoration] Separator", value: "Separator" },
|
|
137
|
-
{ label: "[Advanced] Hidden Field", value: "FormHidden" },
|
|
138
|
-
{ label: "[Advanced] Inherit Form", value: "DocumentForm" },
|
|
139
|
-
{ label: "[Advanced] Custom Code", value: "CustomCode" }
|
|
140
|
-
]);
|
|
141
|
-
const requireOption = ref(["VSelect", "VAutocomplete", "VCombobox", "VRadio", "VRadioInline", "MasterAutocomplete", "FormTable", "FormTableData", "DocumentForm", "FormCheckboxGroup"]);
|
|
142
|
-
const notRequireVariable = ref(["Header", "Separator", "CustomCode", "DocumentForm"]);
|
|
143
|
-
const notRequireLabel = ref(["Separator", "CustomCode", "FormFile", "FormHidden", "DocumentForm"]);
|
|
144
|
-
const notRequireWidth = ref(["Separator", "FormHidden", "DocumentForm"]);
|
|
145
|
-
const notRequireOptions = ref(["Separator", "CustomCode", "FormFile"]);
|
|
146
|
-
const notRequireRules = ref(["Separator", "Header", "CustomCode", "FormHidden", "DocumentForm"]);
|
|
147
|
-
const notRequireInputAttributes = ref(["CustomCode", "Header", "DocumentForm"]);
|
|
148
|
-
const notRequireColumnAttributes = ref(["Separator", "FormHidden", "DocumentForm"]);
|
|
149
|
-
const notRequireAdvancedSetting = ref(["Separator", "CustomCode"]);
|
|
150
|
-
const notRequireClassAndStyle = ref(["FormHidden"]);
|
|
151
|
-
const hasSpecificOption = ref(["FormHidden", "FormTable", "FormTableData"]);
|
|
152
|
-
const choiceOption = ref(["VRadio", "VRadioInline", "VSelect", "VAutocomplete", "VCombobox", "FormCheckboxGroup"]);
|
|
153
|
-
const inputOptionsLabel = ref({
|
|
154
|
-
"MasterAutocomplete": "Group Key",
|
|
155
|
-
"Header": "Class",
|
|
156
|
-
"DocumentForm": "Template Code"
|
|
119
|
+
const inputTypes = computed(() => useDocumentTemplateInputTypes());
|
|
120
|
+
const inputTypeChoice = computed(() => inputTypes.value.map((t) => ({ label: t.label, value: t.value })));
|
|
121
|
+
const requireOption = computed(() => inputTypes.value.filter((t) => t.requiresOptions).map((t) => t.value));
|
|
122
|
+
const notRequireVariable = computed(() => inputTypes.value.filter((t) => t.needsVariableName === false).map((t) => t.value));
|
|
123
|
+
const notRequireLabel = computed(() => inputTypes.value.filter((t) => t.needsLabel === false).map((t) => t.value));
|
|
124
|
+
const notRequireWidth = computed(() => inputTypes.value.filter((t) => t.needsWidth === false).map((t) => t.value));
|
|
125
|
+
const notRequireOptions = computed(() => inputTypes.value.filter((t) => t.showsOptions === false).map((t) => t.value));
|
|
126
|
+
const notRequireRules = computed(() => inputTypes.value.filter((t) => t.hidesValidationRules).map((t) => t.value));
|
|
127
|
+
const notRequireInputAttributes = computed(() => inputTypes.value.filter((t) => t.hidesInputAttributes).map((t) => t.value));
|
|
128
|
+
const notRequireColumnAttributes = computed(() => inputTypes.value.filter((t) => t.hidesColumnAttributes).map((t) => t.value));
|
|
129
|
+
const notRequireAdvancedSetting = computed(() => inputTypes.value.filter((t) => t.hidesAdvancedSettings).map((t) => t.value));
|
|
130
|
+
const notRequireClassAndStyle = computed(() => inputTypes.value.filter((t) => t.hidesClassAndStyle).map((t) => t.value));
|
|
131
|
+
const hasSpecificOption = computed(() => inputTypes.value.filter((t) => t.hasSpecificOptionEditor).map((t) => t.value));
|
|
132
|
+
const choiceOption = computed(() => inputTypes.value.filter((t) => t.optionsAsChoice).map((t) => t.value));
|
|
133
|
+
const inputOptionsLabel = computed(() => {
|
|
134
|
+
const acc = {};
|
|
135
|
+
for (const t of inputTypes.value) if (t.optionsLabel) acc[t.value] = t.optionsLabel;
|
|
136
|
+
return acc;
|
|
157
137
|
});
|
|
158
138
|
const ruleOptions = (inputType) => (value) => {
|
|
159
139
|
if (choiceOption.value.includes(inputType) && !/^[^'",]+(,[^'",]+)*$/.test(value)) return "Invalid options format";
|
|
@@ -382,7 +362,7 @@ const ruleOptions = (inputType) => (value) => {
|
|
|
382
362
|
label="Validation Rules"
|
|
383
363
|
:rules="[rules.regex(validationRulesRegex)]"
|
|
384
364
|
/>
|
|
385
|
-
<b>Available rules :</b> require, requireIf(condition), requireTrue, requireTrueIf(condition), numeric, range(min,max), integer, unique, length(length), lengthGreater(length), lengthLess(length), telephone, email, regex(regex), idcard, DateFuture, DatetimeFuture, DateHappen, DatetimeHappen, DateAfter(date), DateBefore(Date), DateEqual(date)
|
|
365
|
+
<b>Available rules :</b> require, requireIf(condition), requireTrue, requireTrueIf(condition), requireNotEmpty, numeric, range(min,max), integer, unique, length(length), lengthGreater(length), lengthLess(length), telephone, email, regex(regex), idcard, DateFuture, DatetimeFuture, DateHappen, DatetimeHappen, DateAfter(date), DateBefore(Date), DateEqual(date)
|
|
386
366
|
</v-col>
|
|
387
367
|
</v-row>
|
|
388
368
|
</v-container>
|
|
@@ -5,7 +5,12 @@ interface Props {
|
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
7
|
* Public props accepted by DocumentTemplateBuilder.
|
|
8
|
-
*
|
|
8
|
+
*
|
|
9
|
+
* To extend the Input Type dropdown with app-specific entries, call
|
|
10
|
+
* `registerDocumentTemplateInputType()` from a Nuxt plugin (app-wide) or
|
|
11
|
+
* `useDocumentTemplateInputType()` inside `<script setup>` of a page
|
|
12
|
+
* (page-scoped, auto-cleanup on unmount). Both flow through the same
|
|
13
|
+
* runtime registry that this component reads via `useDocumentTemplateInputTypes()`.
|
|
9
14
|
*/
|
|
10
15
|
type __VLS_Props = Props;
|
|
11
16
|
type __VLS_ModelProps = {
|
|
@@ -13,9 +18,9 @@ type __VLS_ModelProps = {
|
|
|
13
18
|
};
|
|
14
19
|
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
15
20
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
16
|
-
"update:modelValue": (
|
|
21
|
+
"update:modelValue": (value: string | Record<string, any>[] | undefined) => any;
|
|
17
22
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
18
|
-
"onUpdate:modelValue"?: ((
|
|
23
|
+
"onUpdate:modelValue"?: ((value: string | Record<string, any>[] | undefined) => any) | undefined;
|
|
19
24
|
}>, {
|
|
20
25
|
title: string;
|
|
21
26
|
disableAdvanceMode: boolean;
|
|
@@ -17,6 +17,7 @@ const formData = ref({});
|
|
|
17
17
|
const formDataOriginalValue = ref();
|
|
18
18
|
const emit = defineEmits(["create", "update"]);
|
|
19
19
|
function save() {
|
|
20
|
+
formPadRef.value?.flushSanitize?.();
|
|
20
21
|
if (props.skipValidation || formPadRef.value?.isValid) {
|
|
21
22
|
isSaving.value = true;
|
|
22
23
|
emit(isCreating.value ? "create" : "update", cloneDeep(formData.value), callback);
|
|
@@ -26,10 +26,10 @@ type __VLS_ModelProps = {
|
|
|
26
26
|
};
|
|
27
27
|
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
28
28
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
29
|
-
"update:modelValue": (
|
|
30
|
-
"update:dobPrecision": (value: "year" | "yearMonthDay" | "yearMonth" | "estimated" | undefined) =>
|
|
29
|
+
"update:modelValue": (value: string | undefined) => any;
|
|
30
|
+
"update:dobPrecision": (value: "year" | "yearMonthDay" | "yearMonth" | "estimated" | undefined) => any;
|
|
31
31
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
32
|
-
"onUpdate:modelValue"?: ((
|
|
32
|
+
"onUpdate:modelValue"?: ((value: string | undefined) => any) | undefined;
|
|
33
33
|
"onUpdate:dobPrecision"?: ((value: "year" | "yearMonthDay" | "yearMonth" | "estimated" | undefined) => any) | undefined;
|
|
34
34
|
}>, {
|
|
35
35
|
flow: ("month" | "year" | "calendar" | "time" | "minutes" | "hours" | "seconds")[];
|
|
@@ -26,10 +26,10 @@ type __VLS_ModelProps = {
|
|
|
26
26
|
};
|
|
27
27
|
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
28
28
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
29
|
-
"update:modelValue": (
|
|
30
|
-
"update:dobPrecision": (value: "year" | "yearMonthDay" | "yearMonth" | "estimated" | undefined) =>
|
|
29
|
+
"update:modelValue": (value: string | undefined) => any;
|
|
30
|
+
"update:dobPrecision": (value: "year" | "yearMonthDay" | "yearMonth" | "estimated" | undefined) => any;
|
|
31
31
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
32
|
-
"onUpdate:modelValue"?: ((
|
|
32
|
+
"onUpdate:modelValue"?: ((value: string | undefined) => any) | undefined;
|
|
33
33
|
"onUpdate:dobPrecision"?: ((value: "year" | "yearMonthDay" | "yearMonth" | "estimated" | undefined) => any) | undefined;
|
|
34
34
|
}>, {
|
|
35
35
|
flow: ("month" | "year" | "calendar" | "time" | "minutes" | "hours" | "seconds")[];
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { ref, watch, watchEffect, nextTick, computed } from "vue";
|
|
3
3
|
import { VTextField } from "vuetify/components/VTextField";
|
|
4
|
-
import Datepicker from "@vuepic/vue-datepicker";
|
|
4
|
+
import { VueDatePicker as Datepicker } from "@vuepic/vue-datepicker";
|
|
5
5
|
import "@vuepic/vue-datepicker/dist/main.css";
|
|
6
6
|
import { isArray, isString } from "lodash-es";
|
|
7
7
|
import { Datetime } from "../../utils/datetime";
|
|
8
8
|
import { useRules } from "../../composables/utils/validation";
|
|
9
|
-
import { th } from "date-fns/locale";
|
|
9
|
+
import { th, enUS } from "date-fns/locale";
|
|
10
10
|
const props = defineProps({
|
|
11
11
|
locale: { type: String, required: false, default: "TH" },
|
|
12
12
|
format: { type: String, required: false, default: "shortDate" },
|
|
@@ -60,6 +60,8 @@ const computedRules = computed(() => {
|
|
|
60
60
|
});
|
|
61
61
|
const selectedDate = ref(null);
|
|
62
62
|
const displayedDate = ref(null);
|
|
63
|
+
const datepickerLocale = computed(() => props.locale === "TH" ? th : enUS);
|
|
64
|
+
const datepickerFlow = computed(() => props.flow.length ? { steps: props.flow, partial: true } : void 0);
|
|
63
65
|
const isMenuOpen = ref(false);
|
|
64
66
|
const isTextFieldFocused = ref(false);
|
|
65
67
|
const hasTextFieldInput = ref(false);
|
|
@@ -197,14 +199,13 @@ defineExpose({
|
|
|
197
199
|
<Datepicker
|
|
198
200
|
v-model="selectedDate"
|
|
199
201
|
model-type="yyyy-MM-dd"
|
|
200
|
-
:
|
|
201
|
-
:flow="
|
|
202
|
+
:time-config="{ enableTimePicker: false }"
|
|
203
|
+
:flow="datepickerFlow"
|
|
202
204
|
:min-date="props.minDate"
|
|
203
205
|
:max-date="props.maxDate"
|
|
204
206
|
auto-apply
|
|
205
207
|
inline
|
|
206
|
-
:locale="
|
|
207
|
-
:format-locale="locale == 'TH' ? th : void 0"
|
|
208
|
+
:locale="datepickerLocale"
|
|
208
209
|
@update:model-value="updateDatePicker"
|
|
209
210
|
>
|
|
210
211
|
<template #year="{ value }" v-if="locale == 'TH'">
|
|
@@ -216,3 +217,7 @@ defineExpose({
|
|
|
216
217
|
</Datepicker>
|
|
217
218
|
</v-menu>
|
|
218
219
|
</template>
|
|
220
|
+
|
|
221
|
+
<style scoped>
|
|
222
|
+
:deep(.dp__button.dp__overlay_action){display:none}
|
|
223
|
+
</style>
|
|
@@ -45,13 +45,9 @@ type __VLS_Slots = {} & {
|
|
|
45
45
|
action?: (props: typeof __VLS_69) => any;
|
|
46
46
|
};
|
|
47
47
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
48
|
-
|
|
49
|
-
update: (...args: any[]) => void;
|
|
50
|
-
"update:modelValue": (value: boolean) => void;
|
|
48
|
+
"update:modelValue": (value: boolean) => any;
|
|
51
49
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
52
50
|
"onUpdate:modelValue"?: ((value: boolean) => any) | undefined;
|
|
53
|
-
onCreate?: ((...args: any[]) => any) | undefined;
|
|
54
|
-
onUpdate?: ((...args: any[]) => any) | undefined;
|
|
55
51
|
}>, {
|
|
56
52
|
readonly: boolean;
|
|
57
53
|
saveCaption: string;
|
|
@@ -20,6 +20,7 @@ const formData = ref({});
|
|
|
20
20
|
const formDataOriginalValue = ref();
|
|
21
21
|
const emit = defineEmits(["create", "update"]);
|
|
22
22
|
function save() {
|
|
23
|
+
formPadRef.value?.flushSanitize?.();
|
|
23
24
|
if (formPadRef.value.isValid) {
|
|
24
25
|
isSaving.value = true;
|
|
25
26
|
emit(isCreating.value ? "create" : "update", cloneDeep(formData.value), props.saveAndStay ? stayCallback : callback);
|
|
@@ -45,13 +45,9 @@ type __VLS_Slots = {} & {
|
|
|
45
45
|
action?: (props: typeof __VLS_69) => any;
|
|
46
46
|
};
|
|
47
47
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
48
|
-
|
|
49
|
-
update: (...args: any[]) => void;
|
|
50
|
-
"update:modelValue": (value: boolean) => void;
|
|
48
|
+
"update:modelValue": (value: boolean) => any;
|
|
51
49
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
52
50
|
"onUpdate:modelValue"?: ((value: boolean) => any) | undefined;
|
|
53
|
-
onCreate?: ((...args: any[]) => any) | undefined;
|
|
54
|
-
onUpdate?: ((...args: any[]) => any) | undefined;
|
|
55
51
|
}>, {
|
|
56
52
|
readonly: boolean;
|
|
57
53
|
saveCaption: string;
|
|
@@ -18,6 +18,7 @@ const formData = ref({});
|
|
|
18
18
|
const formDataOriginalValue = ref();
|
|
19
19
|
const emit = defineEmits(["create", "update"]);
|
|
20
20
|
function save() {
|
|
21
|
+
formPadRef.value?.flushSanitize?.();
|
|
21
22
|
if (props.skipValidation || formPadRef.value?.isValid) {
|
|
22
23
|
isSaving.value = true;
|
|
23
24
|
emit(isCreating.value ? "create" : "update", cloneDeep(formData.value), callback);
|
|
@@ -12,6 +12,7 @@ interface Props {
|
|
|
12
12
|
dirtyOnCreate?: boolean;
|
|
13
13
|
sanitizeDelay?: number;
|
|
14
14
|
}
|
|
15
|
+
declare function flushSanitize(): void;
|
|
15
16
|
declare function reset(): void;
|
|
16
17
|
declare function validate(): void;
|
|
17
18
|
declare function resetValidate(): void;
|
|
@@ -75,6 +76,17 @@ declare var __VLS_10: {
|
|
|
75
76
|
* This doc block is consumed by vue-docgen for generated API documentation.
|
|
76
77
|
*/
|
|
77
78
|
) => string | true;
|
|
79
|
+
requireNotEmpty: (customError?: string) => (
|
|
80
|
+
/**
|
|
81
|
+
* FormPad is a schema-driven form field component that binds model data, renders field UI, and emits normalized updates.
|
|
82
|
+
* This doc block is consumed by vue-docgen for generated API documentation.
|
|
83
|
+
*/
|
|
84
|
+
value: any
|
|
85
|
+
/**
|
|
86
|
+
* FormPad is a schema-driven form field component that binds model data, renders field UI, and emits normalized updates.
|
|
87
|
+
* This doc block is consumed by vue-docgen for generated API documentation.
|
|
88
|
+
*/
|
|
89
|
+
) => string | true;
|
|
78
90
|
numeric: (customError?: string) => (
|
|
79
91
|
/**
|
|
80
92
|
* FormPad is a schema-driven form field component that binds model data, renders field UI, and emits normalized updates.
|
|
@@ -381,6 +393,17 @@ declare var __VLS_10: {
|
|
|
381
393
|
* This doc block is consumed by vue-docgen for generated API documentation.
|
|
382
394
|
*/
|
|
383
395
|
) => string | true;
|
|
396
|
+
requireNotEmpty: (customError?: string) => (
|
|
397
|
+
/**
|
|
398
|
+
* FormPad is a schema-driven form field component that binds model data, renders field UI, and emits normalized updates.
|
|
399
|
+
* This doc block is consumed by vue-docgen for generated API documentation.
|
|
400
|
+
*/
|
|
401
|
+
value: any
|
|
402
|
+
/**
|
|
403
|
+
* FormPad is a schema-driven form field component that binds model data, renders field UI, and emits normalized updates.
|
|
404
|
+
* This doc block is consumed by vue-docgen for generated API documentation.
|
|
405
|
+
*/
|
|
406
|
+
) => string | true;
|
|
384
407
|
numeric: (customError?: string) => (
|
|
385
408
|
/**
|
|
386
409
|
* FormPad is a schema-driven form field component that binds model data, renders field UI, and emits normalized updates.
|
|
@@ -650,6 +673,7 @@ declare const __VLS_base: import("vue").DefineComponent<Props, {
|
|
|
650
673
|
reset: typeof reset;
|
|
651
674
|
validate: typeof validate;
|
|
652
675
|
resetValidate: typeof resetValidate;
|
|
676
|
+
flushSanitize: typeof flushSanitize;
|
|
653
677
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
654
678
|
"update:modelValue": (...args: any[]) => void;
|
|
655
679
|
}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{
|
|
@@ -61,11 +61,11 @@ function scheduleIdle(fn) {
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
const sanitizeBlankStrings = debounce((val, original) => {
|
|
64
|
-
scheduleIdle(() =>
|
|
64
|
+
scheduleIdle(() => sanitizeBlankStringsSync(val, original));
|
|
65
65
|
}, props.sanitizeDelay);
|
|
66
|
-
function
|
|
66
|
+
function sanitizeBlankStringsSync(val, original) {
|
|
67
67
|
if (!original && props.originalData) {
|
|
68
|
-
|
|
68
|
+
sanitizeBlankStringsSync(val, props.originalData);
|
|
69
69
|
return;
|
|
70
70
|
}
|
|
71
71
|
if (isArray(val)) {
|
|
@@ -74,7 +74,7 @@ function sanitizeBlankStringsRaw(val, original) {
|
|
|
74
74
|
if (isBlankString(item)) {
|
|
75
75
|
val.splice(i, 1);
|
|
76
76
|
} else if (isPlainObject(item) || isArray(item)) {
|
|
77
|
-
|
|
77
|
+
sanitizeBlankStringsSync(item, {});
|
|
78
78
|
} else {
|
|
79
79
|
if (item && typeof item === "string") val[i] = item.replace(/[ \t]+$/, "");
|
|
80
80
|
}
|
|
@@ -89,13 +89,17 @@ function sanitizeBlankStringsRaw(val, original) {
|
|
|
89
89
|
else val[key] = null;
|
|
90
90
|
} else if (isPlainObject(v) || isArray(v)) {
|
|
91
91
|
let originalChild = original && (isPlainObject(original[key]) || isArray(original[key])) ? original[key] : {};
|
|
92
|
-
|
|
92
|
+
sanitizeBlankStringsSync(v, originalChild);
|
|
93
93
|
} else {
|
|
94
94
|
if (v && typeof v === "string") val[key] = v.replace(/[ \t]+$/, "");
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
|
+
function flushSanitize() {
|
|
100
|
+
sanitizeBlankStrings.cancel();
|
|
101
|
+
sanitizeBlankStringsSync(formData.value, props.originalData ?? {});
|
|
102
|
+
}
|
|
99
103
|
function autoSanitizedDisplay(item, separator = ",") {
|
|
100
104
|
const isEmptyScalar = (v) => v === null || v === void 0 || typeof v === "string" && v.trim() === "" || typeof v === "number" && Number.isNaN(v);
|
|
101
105
|
const toStr = (v) => {
|
|
@@ -257,7 +261,8 @@ defineExpose({
|
|
|
257
261
|
readonly,
|
|
258
262
|
reset,
|
|
259
263
|
validate,
|
|
260
|
-
resetValidate
|
|
264
|
+
resetValidate,
|
|
265
|
+
flushSanitize
|
|
261
266
|
});
|
|
262
267
|
</script>
|
|
263
268
|
|
|
@@ -308,5 +313,5 @@ defineExpose({
|
|
|
308
313
|
</template>
|
|
309
314
|
|
|
310
315
|
<style>
|
|
311
|
-
.form-data-dirty:not(.v-input--error) :not(.v-chip):not(.v-chip *){color:color-mix(in srgb,currentColor 70%,rgb(var(--v-theme-primary)))!important;text-shadow:0 0 .02em currentColor}
|
|
316
|
+
.form-data-dirty:not(.v-input--error) :not(.v-chip):not(.v-chip *):not(.v-toolbar):not(.v-toolbar *){color:color-mix(in srgb,currentColor 70%,rgb(var(--v-theme-primary)))!important;text-shadow:0 0 .02em currentColor}
|
|
312
317
|
</style>
|
|
@@ -12,6 +12,7 @@ interface Props {
|
|
|
12
12
|
dirtyOnCreate?: boolean;
|
|
13
13
|
sanitizeDelay?: number;
|
|
14
14
|
}
|
|
15
|
+
declare function flushSanitize(): void;
|
|
15
16
|
declare function reset(): void;
|
|
16
17
|
declare function validate(): void;
|
|
17
18
|
declare function resetValidate(): void;
|
|
@@ -75,6 +76,17 @@ declare var __VLS_10: {
|
|
|
75
76
|
* This doc block is consumed by vue-docgen for generated API documentation.
|
|
76
77
|
*/
|
|
77
78
|
) => string | true;
|
|
79
|
+
requireNotEmpty: (customError?: string) => (
|
|
80
|
+
/**
|
|
81
|
+
* FormPad is a schema-driven form field component that binds model data, renders field UI, and emits normalized updates.
|
|
82
|
+
* This doc block is consumed by vue-docgen for generated API documentation.
|
|
83
|
+
*/
|
|
84
|
+
value: any
|
|
85
|
+
/**
|
|
86
|
+
* FormPad is a schema-driven form field component that binds model data, renders field UI, and emits normalized updates.
|
|
87
|
+
* This doc block is consumed by vue-docgen for generated API documentation.
|
|
88
|
+
*/
|
|
89
|
+
) => string | true;
|
|
78
90
|
numeric: (customError?: string) => (
|
|
79
91
|
/**
|
|
80
92
|
* FormPad is a schema-driven form field component that binds model data, renders field UI, and emits normalized updates.
|
|
@@ -381,6 +393,17 @@ declare var __VLS_10: {
|
|
|
381
393
|
* This doc block is consumed by vue-docgen for generated API documentation.
|
|
382
394
|
*/
|
|
383
395
|
) => string | true;
|
|
396
|
+
requireNotEmpty: (customError?: string) => (
|
|
397
|
+
/**
|
|
398
|
+
* FormPad is a schema-driven form field component that binds model data, renders field UI, and emits normalized updates.
|
|
399
|
+
* This doc block is consumed by vue-docgen for generated API documentation.
|
|
400
|
+
*/
|
|
401
|
+
value: any
|
|
402
|
+
/**
|
|
403
|
+
* FormPad is a schema-driven form field component that binds model data, renders field UI, and emits normalized updates.
|
|
404
|
+
* This doc block is consumed by vue-docgen for generated API documentation.
|
|
405
|
+
*/
|
|
406
|
+
) => string | true;
|
|
384
407
|
numeric: (customError?: string) => (
|
|
385
408
|
/**
|
|
386
409
|
* FormPad is a schema-driven form field component that binds model data, renders field UI, and emits normalized updates.
|
|
@@ -650,6 +673,7 @@ declare const __VLS_base: import("vue").DefineComponent<Props, {
|
|
|
650
673
|
reset: typeof reset;
|
|
651
674
|
validate: typeof validate;
|
|
652
675
|
resetValidate: typeof resetValidate;
|
|
676
|
+
flushSanitize: typeof flushSanitize;
|
|
653
677
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
654
678
|
"update:modelValue": (...args: any[]) => void;
|
|
655
679
|
}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
<script setup>
|
|
2
|
-
import { ref, watch, watchEffect, nextTick } from "vue";
|
|
2
|
+
import { ref, watch, watchEffect, nextTick, computed } from "vue";
|
|
3
3
|
import { VTextField } from "vuetify/components/VTextField";
|
|
4
|
-
import Datepicker from "@vuepic/vue-datepicker";
|
|
4
|
+
import { VueDatePicker as Datepicker } from "@vuepic/vue-datepicker";
|
|
5
5
|
import "@vuepic/vue-datepicker/dist/main.css";
|
|
6
|
+
import { th, enUS } from "date-fns/locale";
|
|
6
7
|
import { Datetime } from "../../utils/datetime";
|
|
7
8
|
const props = defineProps({
|
|
8
9
|
enableSeconds: { type: Boolean, required: false, default: false },
|
|
@@ -13,6 +14,11 @@ const props = defineProps({
|
|
|
13
14
|
const emit = defineEmits(["update:modelValue"]);
|
|
14
15
|
const time = ref(null);
|
|
15
16
|
const tempTime = ref(null);
|
|
17
|
+
const datepickerLocale = computed(() => props.locale === "TH" ? th : enUS);
|
|
18
|
+
const datepickerTimeConfig = computed(() => ({
|
|
19
|
+
enableSeconds: props.enableSeconds,
|
|
20
|
+
minutesGridIncrement: 1
|
|
21
|
+
}));
|
|
16
22
|
const isMenuOpen = ref(false);
|
|
17
23
|
const isTextFieldFocused = ref(false);
|
|
18
24
|
const isTextFieldTyped = ref(false);
|
|
@@ -130,13 +136,12 @@ defineExpose({
|
|
|
130
136
|
<Datepicker
|
|
131
137
|
v-model="time"
|
|
132
138
|
model-type="HH:mm:ss"
|
|
133
|
-
:
|
|
134
|
-
minutes-grid-increment="1"
|
|
139
|
+
:time-config="datepickerTimeConfig"
|
|
135
140
|
time-picker
|
|
136
141
|
auto-apply
|
|
137
142
|
:close-on-auto-apply="false"
|
|
138
143
|
inline
|
|
139
|
-
:locale="
|
|
144
|
+
:locale="datepickerLocale"
|
|
140
145
|
@update:model-value="setDatePicker"
|
|
141
146
|
/>
|
|
142
147
|
</v-menu>
|
|
@@ -16,11 +16,9 @@ type __VLS_ModelProps = {
|
|
|
16
16
|
};
|
|
17
17
|
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
18
18
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
19
|
-
|
|
20
|
-
"update:modelValue": (value: string | undefined) => void;
|
|
19
|
+
"update:modelValue": (value: string | undefined) => any;
|
|
21
20
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
22
21
|
"onUpdate:modelValue"?: ((value: string | undefined) => any) | undefined;
|
|
23
|
-
onClose?: ((...args: any[]) => any) | undefined;
|
|
24
22
|
}>, {
|
|
25
23
|
maxWidth: string | number;
|
|
26
24
|
imageFormat: ImageFormat;
|
|
@@ -16,11 +16,9 @@ type __VLS_ModelProps = {
|
|
|
16
16
|
};
|
|
17
17
|
type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
|
|
18
18
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
19
|
-
|
|
20
|
-
"update:modelValue": (value: string | undefined) => void;
|
|
19
|
+
"update:modelValue": (value: string | undefined) => any;
|
|
21
20
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
22
21
|
"onUpdate:modelValue"?: ((value: string | undefined) => any) | undefined;
|
|
23
|
-
onClose?: ((...args: any[]) => any) | undefined;
|
|
24
22
|
}>, {
|
|
25
23
|
maxWidth: string | number;
|
|
26
24
|
imageFormat: ImageFormat;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ModelAutoRefreshChip renders the toolbar countdown/pause chip for ModelTable and
|
|
3
|
+
* ModelIterator. Pass the handle returned by `useAutoRefresh` as `control`. Renders
|
|
4
|
+
* nothing unless auto-refresh is enabled, so callers can mount it unconditionally.
|
|
5
|
+
* This doc block is consumed by vue-docgen for generated API documentation.
|
|
6
|
+
*/
|
|
7
|
+
import type { UseAutoRefreshHandle } from '../../composables/autoRefresh.js';
|
|
8
|
+
interface Props {
|
|
9
|
+
control: UseAutoRefreshHandle;
|
|
10
|
+
color?: string;
|
|
11
|
+
}
|
|
12
|
+
declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{}>, {
|
|
13
|
+
color: string;
|
|
14
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
15
|
+
declare const _default: typeof __VLS_export;
|
|
16
|
+
export default _default;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
const props = defineProps({
|
|
3
|
+
control: { type: Object, required: true },
|
|
4
|
+
color: { type: String, required: false, default: "primary" }
|
|
5
|
+
});
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<template>
|
|
9
|
+
<v-chip
|
|
10
|
+
v-if="props.control.enabled.value"
|
|
11
|
+
class="ml-2"
|
|
12
|
+
density="compact"
|
|
13
|
+
:color="props.color"
|
|
14
|
+
variant="elevated"
|
|
15
|
+
>
|
|
16
|
+
<v-icon
|
|
17
|
+
size="small"
|
|
18
|
+
start
|
|
19
|
+
>
|
|
20
|
+
mdi mdi-autorenew
|
|
21
|
+
</v-icon>
|
|
22
|
+
<span v-if="props.control.isLoading.value">—</span>
|
|
23
|
+
<span v-else-if="!props.control.isActive.value">paused</span>
|
|
24
|
+
<span v-else>{{ props.control.remainingSeconds.value }}s</span>
|
|
25
|
+
<v-btn
|
|
26
|
+
class="ml-1"
|
|
27
|
+
density="compact"
|
|
28
|
+
variant="text"
|
|
29
|
+
size="x-small"
|
|
30
|
+
:icon="props.control.isUserPaused.value ? 'mdi mdi-play' : 'mdi mdi-pause'"
|
|
31
|
+
@click="props.control.togglePause"
|
|
32
|
+
/>
|
|
33
|
+
</v-chip>
|
|
34
|
+
</template>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ModelAutoRefreshChip renders the toolbar countdown/pause chip for ModelTable and
|
|
3
|
+
* ModelIterator. Pass the handle returned by `useAutoRefresh` as `control`. Renders
|
|
4
|
+
* nothing unless auto-refresh is enabled, so callers can mount it unconditionally.
|
|
5
|
+
* This doc block is consumed by vue-docgen for generated API documentation.
|
|
6
|
+
*/
|
|
7
|
+
import type { UseAutoRefreshHandle } from '../../composables/autoRefresh.js';
|
|
8
|
+
interface Props {
|
|
9
|
+
control: UseAutoRefreshHandle;
|
|
10
|
+
color?: string;
|
|
11
|
+
}
|
|
12
|
+
declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{}>, {
|
|
13
|
+
color: string;
|
|
14
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
15
|
+
declare const _default: typeof __VLS_export;
|
|
16
|
+
export default _default;
|