@aerogel/core 0.0.0-next.71f28064caa2ea968f0e99396b672de218176260 → 0.0.0-next.7eac6e53dcf9fe93d7abdc4bc97e5ce69970efa7
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/aerogel-core.cjs.js +1 -1
- package/dist/aerogel-core.cjs.js.map +1 -1
- package/dist/aerogel-core.d.ts +153 -33
- package/dist/aerogel-core.esm.js +1 -1
- package/dist/aerogel-core.esm.js.map +1 -1
- package/package.json +2 -2
- package/src/components/AGAppSnackbars.vue +1 -1
- package/src/components/forms/AGForm.vue +9 -10
- package/src/components/headless/forms/AGHeadlessButton.vue +9 -4
- package/src/components/headless/forms/AGHeadlessInput.ts +4 -3
- package/src/components/headless/forms/AGHeadlessInput.vue +1 -1
- package/src/components/headless/forms/AGHeadlessInputInput.vue +31 -3
- package/src/components/headless/forms/index.ts +1 -0
- package/src/components/lib/AGMarkdown.vue +3 -3
- package/src/components/modals/AGConfirmModal.ts +9 -3
- package/src/components/modals/AGConfirmModal.vue +2 -2
- package/src/components/modals/AGPromptModal.ts +9 -3
- package/src/components/modals/AGPromptModal.vue +2 -2
- package/src/forms/Form.ts +26 -7
- package/src/forms/index.ts +1 -0
- package/src/forms/utils.ts +15 -0
- package/src/lang/Lang.ts +8 -4
- package/src/services/Cache.ts +43 -0
- package/src/services/index.ts +3 -1
- package/src/ui/UI.ts +9 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aerogel/core",
|
|
3
3
|
"description": "The Lightest Solid",
|
|
4
|
-
"version": "0.0.0-next.
|
|
4
|
+
"version": "0.0.0-next.7eac6e53dcf9fe93d7abdc4bc97e5ce69970efa7",
|
|
5
5
|
"main": "dist/aerogel-core.cjs.js",
|
|
6
6
|
"module": "dist/aerogel-core.esm.js",
|
|
7
7
|
"types": "dist/aerogel-core.d.ts",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@headlessui/vue": "^1.7.14",
|
|
38
|
-
"@noeldemartin/utils": "0.5.1-next.
|
|
38
|
+
"@noeldemartin/utils": "0.5.1-next.4fd89de2cbde6c7e1cfa4d5f9bdac234e9cd3d98",
|
|
39
39
|
"dompurify": "^3.0.3",
|
|
40
40
|
"marked": "^5.0.4",
|
|
41
41
|
"pinia": "^2.1.6",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div aria-live="assertive" class="
|
|
2
|
+
<div aria-live="assertive" class="pointer-events-none fixed inset-0 z-50 flex items-end px-4 py-6 sm:p-6">
|
|
3
3
|
<div class="flex w-full flex-col items-end space-y-4">
|
|
4
4
|
<component
|
|
5
5
|
:is="snackbar.component"
|
|
@@ -1,26 +1,25 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<form @submit.prevent="submit">
|
|
2
|
+
<form @submit.prevent="form?.submit()">
|
|
3
3
|
<slot />
|
|
4
4
|
</form>
|
|
5
5
|
</template>
|
|
6
6
|
|
|
7
7
|
<script setup lang="ts">
|
|
8
|
-
import { provide } from 'vue';
|
|
8
|
+
import { provide, watchEffect } from 'vue';
|
|
9
9
|
|
|
10
10
|
import { objectProp } from '@/utils/vue';
|
|
11
11
|
import type Form from '@/forms/Form';
|
|
12
12
|
|
|
13
|
+
let offSubmit: (() => void) | undefined;
|
|
13
14
|
const props = defineProps({ form: objectProp<Form>() });
|
|
14
|
-
|
|
15
15
|
const emit = defineEmits<{ submit: [] }>();
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
watchEffect((onCleanup) => {
|
|
18
|
+
offSubmit?.();
|
|
19
|
+
offSubmit = props.form?.on('submit', () => emit('submit'));
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
21
|
+
onCleanup(() => offSubmit?.());
|
|
22
|
+
});
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
}
|
|
24
|
+
provide('form', props.form);
|
|
26
25
|
</script>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<component :is="component.
|
|
2
|
+
<component :is="component.as" v-bind="component.props">
|
|
3
3
|
<slot />
|
|
4
4
|
</component>
|
|
5
5
|
</template>
|
|
@@ -11,6 +11,7 @@ import { objectWithoutEmpty } from '@noeldemartin/utils';
|
|
|
11
11
|
import { booleanProp, objectProp, stringProp } from '@/utils/vue';
|
|
12
12
|
|
|
13
13
|
const props = defineProps({
|
|
14
|
+
as: objectProp(),
|
|
14
15
|
href: stringProp(),
|
|
15
16
|
url: stringProp(),
|
|
16
17
|
route: stringProp(),
|
|
@@ -20,9 +21,13 @@ const props = defineProps({
|
|
|
20
21
|
});
|
|
21
22
|
|
|
22
23
|
const component = computed(() => {
|
|
24
|
+
if (props.as) {
|
|
25
|
+
return { as: props.as, props: {} };
|
|
26
|
+
}
|
|
27
|
+
|
|
23
28
|
if (props.route) {
|
|
24
29
|
return {
|
|
25
|
-
|
|
30
|
+
as: 'router-link',
|
|
26
31
|
props: {
|
|
27
32
|
to: objectWithoutEmpty({
|
|
28
33
|
name: props.route,
|
|
@@ -35,7 +40,7 @@ const component = computed(() => {
|
|
|
35
40
|
|
|
36
41
|
if (props.href || props.url) {
|
|
37
42
|
return {
|
|
38
|
-
|
|
43
|
+
as: 'a',
|
|
39
44
|
props: {
|
|
40
45
|
target: '_blank',
|
|
41
46
|
href: props.href || props.url,
|
|
@@ -44,7 +49,7 @@ const component = computed(() => {
|
|
|
44
49
|
}
|
|
45
50
|
|
|
46
51
|
return {
|
|
47
|
-
|
|
52
|
+
as: 'button',
|
|
48
53
|
props: { type: props.submit ? 'submit' : 'button' },
|
|
49
54
|
};
|
|
50
55
|
});
|
|
@@ -2,22 +2,23 @@ import type { ComputedRef, DeepReadonly, ExtractPropTypes, Ref } from 'vue';
|
|
|
2
2
|
|
|
3
3
|
import { mixedProp, stringProp } from '@/utils';
|
|
4
4
|
import { extractComponentProps } from '@/components/utils';
|
|
5
|
+
import type { FormFieldValue } from '@/forms/Form';
|
|
5
6
|
|
|
6
7
|
export interface IAGHeadlessInput {
|
|
7
8
|
id: string;
|
|
8
9
|
name: ComputedRef<string | null>;
|
|
9
10
|
label: ComputedRef<string | null>;
|
|
10
11
|
description: ComputedRef<string | boolean | null>;
|
|
11
|
-
value: ComputedRef<
|
|
12
|
+
value: ComputedRef<FormFieldValue | null>;
|
|
12
13
|
errors: DeepReadonly<Ref<string[] | null>>;
|
|
13
|
-
update(value:
|
|
14
|
+
update(value: FormFieldValue | null): void;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export const inputProps = {
|
|
17
18
|
name: stringProp(),
|
|
18
19
|
label: stringProp(),
|
|
19
20
|
description: stringProp(),
|
|
20
|
-
modelValue: mixedProp<
|
|
21
|
+
modelValue: mixedProp<FormFieldValue>([String, Number, Boolean]),
|
|
21
22
|
};
|
|
22
23
|
|
|
23
24
|
export function useInputProps(): typeof inputProps {
|
|
@@ -35,7 +35,7 @@ const api: IAGHeadlessInput = {
|
|
|
35
35
|
description: computed(() => props.description),
|
|
36
36
|
value: computed(() => {
|
|
37
37
|
if (form && props.name) {
|
|
38
|
-
return form.getFieldValue(props.name)
|
|
38
|
+
return form.getFieldValue(props.name);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
return props.modelValue;
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
ref="$input"
|
|
5
5
|
:name="name"
|
|
6
6
|
:type="type"
|
|
7
|
-
:value="value"
|
|
8
7
|
:aria-invalid="input.errors ? 'true' : 'false'"
|
|
9
8
|
:aria-describedby="
|
|
10
9
|
input.errors ? `${input.id}-error` : input.description ? `${input.id}-description` : undefined
|
|
@@ -15,10 +14,11 @@
|
|
|
15
14
|
</template>
|
|
16
15
|
|
|
17
16
|
<script setup lang="ts">
|
|
18
|
-
import { computed, ref } from 'vue';
|
|
17
|
+
import { computed, ref, watchEffect } from 'vue';
|
|
19
18
|
|
|
20
19
|
import { injectReactiveOrFail, stringProp } from '@/utils';
|
|
21
20
|
import type { IAGHeadlessInput } from '@/components/headless/forms/AGHeadlessInput';
|
|
21
|
+
import type { FormFieldValue } from '@/forms/Form';
|
|
22
22
|
|
|
23
23
|
import { onFormFocus } from './composition';
|
|
24
24
|
|
|
@@ -46,8 +46,36 @@ function update() {
|
|
|
46
46
|
return;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
input.update(
|
|
49
|
+
input.update(getValue());
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function getValue(): FormFieldValue | null {
|
|
53
|
+
if (!$input.value) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
switch (props.type) {
|
|
58
|
+
case 'checkbox':
|
|
59
|
+
return $input.value.checked;
|
|
60
|
+
case 'date':
|
|
61
|
+
return $input.value.valueAsDate;
|
|
62
|
+
default:
|
|
63
|
+
return $input.value.value;
|
|
64
|
+
}
|
|
50
65
|
}
|
|
51
66
|
|
|
52
67
|
onFormFocus(input, () => $input.value?.focus());
|
|
68
|
+
watchEffect(() => {
|
|
69
|
+
if (!$input.value) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (props.type === 'date') {
|
|
74
|
+
$input.value.valueAsDate = value.value as Date;
|
|
75
|
+
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
$input.value.value = value.value as string;
|
|
80
|
+
});
|
|
53
81
|
</script>
|
|
@@ -8,6 +8,7 @@ export { default as AGHeadlessInputDescription } from './AGHeadlessInputDescript
|
|
|
8
8
|
export { default as AGHeadlessInputError } from './AGHeadlessInputError.vue';
|
|
9
9
|
export { default as AGHeadlessInputInput } from './AGHeadlessInputInput.vue';
|
|
10
10
|
export { default as AGHeadlessInputLabel } from './AGHeadlessInputLabel.vue';
|
|
11
|
+
export { default as AGHeadlessInputTextArea } from './AGHeadlessInputTextArea.vue';
|
|
11
12
|
export { default as AGHeadlessSelect } from './AGHeadlessSelect.vue';
|
|
12
13
|
export { default as AGHeadlessSelectButton } from './AGHeadlessSelectButton.vue';
|
|
13
14
|
export { default as AGHeadlessSelectError } from './AGHeadlessSelectError.vue';
|
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
import { computed, h, useAttrs } from 'vue';
|
|
7
7
|
|
|
8
8
|
import { renderMarkdown } from '@/utils/markdown';
|
|
9
|
-
import { booleanProp,
|
|
9
|
+
import { booleanProp, mixedProp, stringProp } from '@/utils/vue';
|
|
10
10
|
import { translate } from '@/lang';
|
|
11
11
|
|
|
12
12
|
const props = defineProps({
|
|
13
13
|
as: stringProp(),
|
|
14
14
|
inline: booleanProp(),
|
|
15
15
|
langKey: stringProp(),
|
|
16
|
-
langParams:
|
|
16
|
+
langParams: mixedProp<number | Record<string, unknown>>(),
|
|
17
17
|
text: stringProp(),
|
|
18
18
|
});
|
|
19
19
|
|
|
@@ -34,8 +34,8 @@ const html = computed(() => {
|
|
|
34
34
|
});
|
|
35
35
|
const root = () =>
|
|
36
36
|
h(props.as ?? (props.inline ? 'span' : 'div'), {
|
|
37
|
-
class: props.inline ? '' : 'prose',
|
|
38
37
|
innerHTML: html.value,
|
|
39
38
|
...attrs,
|
|
39
|
+
class: `${attrs.class ?? ''} ${props.inline ? '' : 'prose'}`,
|
|
40
40
|
});
|
|
41
41
|
</script>
|
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
import { computed } from 'vue';
|
|
2
2
|
import type { ExtractPropTypes } from 'vue';
|
|
3
|
-
import type { ObjectWithoutEmpty } from '@noeldemartin/utils';
|
|
3
|
+
import type { ObjectWithoutEmpty, SubPartial } from '@noeldemartin/utils';
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { Colors } from '@/components/constants';
|
|
6
|
+
import { enumProp, requiredStringProp, stringProp } from '@/utils';
|
|
6
7
|
import { translateWithDefault } from '@/lang';
|
|
7
8
|
|
|
8
9
|
export const confirmModalProps = {
|
|
9
10
|
title: stringProp(),
|
|
10
11
|
message: requiredStringProp(),
|
|
11
12
|
acceptText: stringProp(),
|
|
13
|
+
acceptColor: enumProp(Colors, Colors.Primary),
|
|
12
14
|
cancelText: stringProp(),
|
|
15
|
+
cancelColor: enumProp(Colors, Colors.Clear),
|
|
13
16
|
};
|
|
14
17
|
|
|
15
|
-
export type AGConfirmModalProps =
|
|
18
|
+
export type AGConfirmModalProps = SubPartial<
|
|
19
|
+
ObjectWithoutEmpty<ExtractPropTypes<typeof confirmModalProps>>,
|
|
20
|
+
'acceptColor' | 'cancelColor'
|
|
21
|
+
>;
|
|
16
22
|
|
|
17
23
|
export function useConfirmModalProps(): typeof confirmModalProps {
|
|
18
24
|
return confirmModalProps;
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
<AGMarkdown :text="message" />
|
|
4
4
|
|
|
5
5
|
<div class="mt-2 flex flex-row-reverse gap-2">
|
|
6
|
-
<AGButton @click="close(true)">
|
|
6
|
+
<AGButton :color="acceptColor" @click="close(true)">
|
|
7
7
|
{{ renderedAcceptText }}
|
|
8
8
|
</AGButton>
|
|
9
|
-
<AGButton color="
|
|
9
|
+
<AGButton :color="cancelColor" @click="close()">
|
|
10
10
|
{{ renderedCancelText }}
|
|
11
11
|
</AGButton>
|
|
12
12
|
</div>
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { computed } from 'vue';
|
|
2
2
|
import type { ExtractPropTypes } from 'vue';
|
|
3
|
-
import type { ObjectWithoutEmpty } from '@noeldemartin/utils';
|
|
3
|
+
import type { ObjectWithoutEmpty, SubPartial } from '@noeldemartin/utils';
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { Colors } from '@/components/constants';
|
|
6
|
+
import { enumProp, requiredStringProp, stringProp } from '@/utils';
|
|
6
7
|
import { translateWithDefault } from '@/lang';
|
|
7
8
|
|
|
8
9
|
export const promptModalProps = {
|
|
@@ -12,10 +13,15 @@ export const promptModalProps = {
|
|
|
12
13
|
defaultValue: stringProp(),
|
|
13
14
|
placeholder: stringProp(),
|
|
14
15
|
acceptText: stringProp(),
|
|
16
|
+
acceptColor: enumProp(Colors, Colors.Primary),
|
|
15
17
|
cancelText: stringProp(),
|
|
18
|
+
cancelColor: enumProp(Colors, Colors.Clear),
|
|
16
19
|
};
|
|
17
20
|
|
|
18
|
-
export type AGPromptModalProps =
|
|
21
|
+
export type AGPromptModalProps = SubPartial<
|
|
22
|
+
ObjectWithoutEmpty<ExtractPropTypes<typeof promptModalProps>>,
|
|
23
|
+
'acceptColor' | 'cancelColor'
|
|
24
|
+
>;
|
|
19
25
|
|
|
20
26
|
export function usePromptModalProps(): typeof promptModalProps {
|
|
21
27
|
return promptModalProps;
|
|
@@ -6,10 +6,10 @@
|
|
|
6
6
|
<AGInput name="draft" :placeholder="placeholder" :label="label" />
|
|
7
7
|
|
|
8
8
|
<div class="mt-2 flex flex-row-reverse gap-2">
|
|
9
|
-
<AGButton submit>
|
|
9
|
+
<AGButton :color="acceptColor" submit>
|
|
10
10
|
{{ renderedAcceptText }}
|
|
11
11
|
</AGButton>
|
|
12
|
-
<AGButton color="
|
|
12
|
+
<AGButton :color="cancelColor" @click="close()">
|
|
13
13
|
{{ renderedCancelText }}
|
|
14
14
|
</AGButton>
|
|
15
15
|
</div>
|
package/src/forms/Form.ts
CHANGED
|
@@ -8,6 +8,7 @@ export const FormFieldTypes = {
|
|
|
8
8
|
Number: 'number',
|
|
9
9
|
Boolean: 'boolean',
|
|
10
10
|
Object: 'object',
|
|
11
|
+
Date: 'date',
|
|
11
12
|
} as const;
|
|
12
13
|
|
|
13
14
|
export interface FormFieldDefinition<TType extends FormFieldType = FormFieldType, TRules extends string = string> {
|
|
@@ -40,11 +41,14 @@ export type GetFormFieldValue<TType> = TType extends typeof FormFieldTypes.Strin
|
|
|
40
41
|
? boolean
|
|
41
42
|
: TType extends typeof FormFieldTypes.Object
|
|
42
43
|
? object
|
|
44
|
+
: TType extends typeof FormFieldTypes.Date
|
|
45
|
+
? Date
|
|
43
46
|
: never;
|
|
44
47
|
|
|
45
48
|
const validForms: WeakMap<Form, ComputedRef<boolean>> = new WeakMap();
|
|
46
49
|
|
|
47
|
-
export type
|
|
50
|
+
export type SubmitFormListener = () => unknown;
|
|
51
|
+
export type FocusFormListener = (input: string) => unknown;
|
|
48
52
|
|
|
49
53
|
export default class Form<Fields extends FormFieldDefinitions = FormFieldDefinitions> extends MagicObject {
|
|
50
54
|
|
|
@@ -54,7 +58,7 @@ export default class Form<Fields extends FormFieldDefinitions = FormFieldDefinit
|
|
|
54
58
|
private _data: FormData<Fields>;
|
|
55
59
|
private _submitted: Ref<boolean>;
|
|
56
60
|
private _errors: FormErrors<Fields>;
|
|
57
|
-
private _listeners:
|
|
61
|
+
private _listeners: { focus?: FocusFormListener[]; submit?: SubmitFormListener[] } = {};
|
|
58
62
|
|
|
59
63
|
constructor(fields: Fields) {
|
|
60
64
|
super();
|
|
@@ -92,6 +96,10 @@ export default class Form<Fields extends FormFieldDefinitions = FormFieldDefinit
|
|
|
92
96
|
return this._data[field] as unknown as GetFormFieldValue<Fields[T]['type']>;
|
|
93
97
|
}
|
|
94
98
|
|
|
99
|
+
public data(): FormData<Fields> {
|
|
100
|
+
return { ...this._data };
|
|
101
|
+
}
|
|
102
|
+
|
|
95
103
|
public validate(): boolean {
|
|
96
104
|
const errors = Object.entries(this._fields).reduce((formErrors, [name, definition]) => {
|
|
97
105
|
formErrors[name] = this.getFieldErrors(name, definition);
|
|
@@ -114,17 +122,28 @@ export default class Form<Fields extends FormFieldDefinitions = FormFieldDefinit
|
|
|
114
122
|
public submit(): boolean {
|
|
115
123
|
this._submitted.value = true;
|
|
116
124
|
|
|
117
|
-
|
|
125
|
+
const valid = this.validate();
|
|
126
|
+
|
|
127
|
+
valid && this._listeners['submit']?.forEach((listener) => listener());
|
|
128
|
+
|
|
129
|
+
return valid;
|
|
118
130
|
}
|
|
119
131
|
|
|
120
|
-
public on(event:
|
|
132
|
+
public on(event: 'focus', listener: FocusFormListener): () => void;
|
|
133
|
+
public on(event: 'submit', listener: SubmitFormListener): () => void;
|
|
134
|
+
public on(event: 'focus' | 'submit', listener: FocusFormListener | SubmitFormListener): () => void {
|
|
121
135
|
this._listeners[event] ??= [];
|
|
122
|
-
this._listeners[event]?.push(listener);
|
|
123
136
|
|
|
124
|
-
|
|
137
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
138
|
+
this._listeners[event]?.push(listener as any);
|
|
139
|
+
|
|
140
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
141
|
+
return () => this.off(event as any, listener);
|
|
125
142
|
}
|
|
126
143
|
|
|
127
|
-
public off(event:
|
|
144
|
+
public off(event: 'focus', listener: FocusFormListener): void;
|
|
145
|
+
public off(event: 'submit', listener: SubmitFormListener): void;
|
|
146
|
+
public off(event: 'focus' | 'submit', listener: FocusFormListener | SubmitFormListener): void {
|
|
128
147
|
arrayRemove(this._listeners[event] ?? [], listener);
|
|
129
148
|
}
|
|
130
149
|
|
package/src/forms/index.ts
CHANGED
package/src/forms/utils.ts
CHANGED
|
@@ -8,6 +8,13 @@ export function booleanInput(defaultValue?: boolean): FormFieldDefinition<typeof
|
|
|
8
8
|
};
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
export function dateInput(defaultValue?: Date): FormFieldDefinition<typeof FormFieldTypes.Date> {
|
|
12
|
+
return {
|
|
13
|
+
default: defaultValue,
|
|
14
|
+
type: FormFieldTypes.Date,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
11
18
|
export function requiredBooleanInput(
|
|
12
19
|
defaultValue?: boolean,
|
|
13
20
|
): FormFieldDefinition<typeof FormFieldTypes.Boolean, 'required'> {
|
|
@@ -18,6 +25,14 @@ export function requiredBooleanInput(
|
|
|
18
25
|
};
|
|
19
26
|
}
|
|
20
27
|
|
|
28
|
+
export function requiredDateInput(defaultValue?: Date): FormFieldDefinition<typeof FormFieldTypes.Date> {
|
|
29
|
+
return {
|
|
30
|
+
default: defaultValue,
|
|
31
|
+
type: FormFieldTypes.Date,
|
|
32
|
+
rules: 'required',
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
21
36
|
export function requiredNumberInput(
|
|
22
37
|
defaultValue?: number,
|
|
23
38
|
): FormFieldDefinition<typeof FormFieldTypes.Number, 'required'> {
|
package/src/lang/Lang.ts
CHANGED
|
@@ -4,8 +4,8 @@ import App from '@/services/App';
|
|
|
4
4
|
import Service from '@/services/Service';
|
|
5
5
|
|
|
6
6
|
export interface LangProvider {
|
|
7
|
-
translate(key: string, parameters?: Record<string, unknown>): string;
|
|
8
|
-
translateWithDefault(key: string, defaultMessage: string, parameters?: Record<string, unknown>): string;
|
|
7
|
+
translate(key: string, parameters?: Record<string, unknown> | number): string;
|
|
8
|
+
translateWithDefault(key: string, defaultMessage: string, parameters?: Record<string, unknown> | number): string;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export class LangService extends Service {
|
|
@@ -35,11 +35,15 @@ export class LangService extends Service {
|
|
|
35
35
|
this.provider = provider;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
public translate(key: string, parameters?: Record<string, unknown>): string {
|
|
38
|
+
public translate(key: string, parameters?: Record<string, unknown> | number): string {
|
|
39
39
|
return this.provider.translate(key, parameters) ?? key;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
public translateWithDefault(
|
|
42
|
+
public translateWithDefault(
|
|
43
|
+
key: string,
|
|
44
|
+
defaultMessage: string,
|
|
45
|
+
parameters: Record<string, unknown> | number = {},
|
|
46
|
+
): string {
|
|
43
47
|
return this.provider.translateWithDefault(key, defaultMessage, parameters);
|
|
44
48
|
}
|
|
45
49
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { PromisedValue, facade, tap } from '@noeldemartin/utils';
|
|
2
|
+
|
|
3
|
+
import Service from '@/services/Service';
|
|
4
|
+
|
|
5
|
+
export class CacheService extends Service {
|
|
6
|
+
|
|
7
|
+
private cache?: PromisedValue<Cache> = undefined;
|
|
8
|
+
|
|
9
|
+
public async get(url: string): Promise<Response | null> {
|
|
10
|
+
const cache = await this.open();
|
|
11
|
+
const response = await cache.match(url);
|
|
12
|
+
|
|
13
|
+
return response ?? null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
public async store(url: string, response: Response): Promise<void> {
|
|
17
|
+
const cache = await this.open();
|
|
18
|
+
|
|
19
|
+
await cache.put(url, response);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public async replace(url: string, response: Response): Promise<void> {
|
|
23
|
+
const cache = await this.open();
|
|
24
|
+
const keys = await cache.keys(url);
|
|
25
|
+
|
|
26
|
+
if (keys.length === 0) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
await cache.put(url, response);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
protected async open(): Promise<Cache> {
|
|
34
|
+
return (this.cache =
|
|
35
|
+
this.cache ??
|
|
36
|
+
tap(new PromisedValue<Cache>(), (cache) => {
|
|
37
|
+
caches.open('app').then((instance) => cache.resolve(instance));
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export default facade(CacheService);
|
package/src/services/index.ts
CHANGED
|
@@ -3,16 +3,18 @@ import type { App as VueApp } from 'vue';
|
|
|
3
3
|
import { definePlugin } from '@/plugins';
|
|
4
4
|
|
|
5
5
|
import App from './App';
|
|
6
|
+
import Cache from './Cache';
|
|
6
7
|
import Events from './Events';
|
|
7
8
|
import Service from './Service';
|
|
8
9
|
import { getPiniaStore } from './store';
|
|
9
10
|
|
|
10
11
|
export * from './App';
|
|
12
|
+
export * from './Cache';
|
|
11
13
|
export * from './Events';
|
|
12
14
|
export * from './Service';
|
|
13
15
|
export * from './store';
|
|
14
16
|
|
|
15
|
-
export { App, Events, Service };
|
|
17
|
+
export { App, Cache, Events, Service };
|
|
16
18
|
|
|
17
19
|
const defaultServices = {
|
|
18
20
|
$app: App,
|
package/src/ui/UI.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { Component } from 'vue';
|
|
|
4
4
|
import type { ObjectValues } from '@noeldemartin/utils';
|
|
5
5
|
|
|
6
6
|
import Events from '@/services/Events';
|
|
7
|
+
import type { Color } from '@/components/constants';
|
|
7
8
|
import type { SnackbarAction, SnackbarColor } from '@/components/headless/snackbars';
|
|
8
9
|
import type { AGAlertModalProps, AGConfirmModalProps, AGLoadingModalProps, AGPromptModalProps } from '@/components';
|
|
9
10
|
|
|
@@ -34,7 +35,9 @@ export type UIComponent = ObjectValues<typeof UIComponents>;
|
|
|
34
35
|
|
|
35
36
|
export interface ConfirmOptions {
|
|
36
37
|
acceptText?: string;
|
|
38
|
+
acceptColor?: Color;
|
|
37
39
|
cancelText?: string;
|
|
40
|
+
cancelColor?: Color;
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
export interface PromptOptions {
|
|
@@ -42,7 +45,10 @@ export interface PromptOptions {
|
|
|
42
45
|
defaultValue?: string;
|
|
43
46
|
placeholder?: string;
|
|
44
47
|
acceptText?: string;
|
|
48
|
+
acceptColor?: Color;
|
|
45
49
|
cancelText?: string;
|
|
50
|
+
cancelColor?: Color;
|
|
51
|
+
trim?: boolean;
|
|
46
52
|
}
|
|
47
53
|
|
|
48
54
|
export interface ShowSnackbarOptions {
|
|
@@ -115,6 +121,7 @@ export class UIService extends Service {
|
|
|
115
121
|
messageOrOptions?: string | PromptOptions,
|
|
116
122
|
options?: PromptOptions,
|
|
117
123
|
): Promise<string | null> {
|
|
124
|
+
const trim = options?.trim ?? true;
|
|
118
125
|
const getProperties = (): AGPromptModalProps => {
|
|
119
126
|
if (typeof messageOrOptions !== 'string') {
|
|
120
127
|
return {
|
|
@@ -134,7 +141,8 @@ export class UIService extends Service {
|
|
|
134
141
|
this.requireComponent(UIComponents.PromptModal),
|
|
135
142
|
getProperties(),
|
|
136
143
|
);
|
|
137
|
-
const
|
|
144
|
+
const rawResult = await modal.beforeClose;
|
|
145
|
+
const result = trim && typeof rawResult === 'string' ? rawResult?.trim() : rawResult;
|
|
138
146
|
|
|
139
147
|
return result ?? null;
|
|
140
148
|
}
|