@aerogel/core 0.0.0-next.6c539d8e63b397d4bb6c3d61a7f20a4d108b1cdd → 0.0.0-next.7035064d9ec6a82a936ee8dfcc4b58ed2e25a399
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 +328 -72
- package/dist/aerogel-core.esm.js +1 -1
- package/dist/aerogel-core.esm.js.map +1 -1
- package/package.json +2 -2
- package/src/bootstrap/index.ts +12 -2
- package/src/components/headless/modals/AGHeadlessModal.ts +3 -1
- package/src/components/headless/modals/AGHeadlessModal.vue +10 -4
- package/src/components/headless/modals/AGHeadlessModalPanel.vue +10 -6
- package/src/components/headless/modals/AGHeadlessModalTitle.vue +14 -4
- package/src/components/lib/AGMarkdown.vue +14 -1
- package/src/components/lib/AGProgressBar.vue +30 -0
- package/src/components/lib/index.ts +1 -0
- package/src/components/modals/AGAlertModal.ts +5 -2
- package/src/components/modals/AGConfirmModal.ts +13 -5
- package/src/components/modals/AGConfirmModal.vue +1 -1
- package/src/components/modals/AGErrorReportModal.ts +5 -2
- package/src/components/modals/AGLoadingModal.ts +10 -4
- package/src/components/modals/AGModal.ts +1 -0
- package/src/components/modals/AGModalContext.vue +14 -4
- package/src/components/modals/AGPromptModal.ts +9 -4
- package/src/errors/JobCancelledError.ts +3 -0
- package/src/errors/utils.ts +16 -0
- package/src/forms/Form.ts +10 -3
- package/src/forms/index.ts +2 -1
- package/src/forms/utils.ts +20 -4
- package/src/forms/validation.ts +19 -0
- package/src/jobs/Job.ts +144 -2
- package/src/jobs/index.ts +4 -1
- package/src/jobs/listeners.ts +3 -0
- package/src/jobs/status.ts +4 -0
- package/src/services/App.state.ts +9 -1
- package/src/services/App.ts +5 -0
- package/src/services/Events.ts +13 -3
- package/src/services/Service.ts +107 -44
- package/src/services/Storage.ts +20 -0
- package/src/services/index.ts +7 -2
- package/src/services/utils.ts +18 -0
- package/src/testing/setup.ts +11 -3
- package/src/ui/UI.ts +108 -38
- package/src/utils/composition/persistent.test.ts +33 -0
- package/src/utils/composition/persistent.ts +11 -0
- package/src/utils/composition/state.test.ts +47 -0
- package/src/utils/composition/state.ts +24 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/markdown.test.ts +50 -0
- package/src/utils/markdown.ts +17 -2
- package/src/utils/vue.ts +4 -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.7035064d9ec6a82a936ee8dfcc4b58ed2e25a399",
|
|
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.
|
|
38
|
+
"@noeldemartin/utils": "0.6.0-next.036a147180df61c600c4599df30816ac860dbf06",
|
|
39
39
|
"dompurify": "^3.0.3",
|
|
40
40
|
"marked": "^5.0.4",
|
|
41
41
|
"pinia": "^2.1.6",
|
package/src/bootstrap/index.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import Aerogel from 'virtual:aerogel';
|
|
2
|
+
|
|
1
3
|
import { createApp } from 'vue';
|
|
2
|
-
import type { App, Component } from 'vue';
|
|
4
|
+
import type { App as AppInstance, Component } from 'vue';
|
|
3
5
|
|
|
6
|
+
import App from '@/services/App';
|
|
4
7
|
import directives from '@/directives';
|
|
5
8
|
import errors from '@/errors';
|
|
6
9
|
import Events from '@/services/Events';
|
|
@@ -13,9 +16,11 @@ import type { AerogelOptions } from '@/bootstrap/options';
|
|
|
13
16
|
|
|
14
17
|
export { AerogelOptions };
|
|
15
18
|
|
|
16
|
-
export async function bootstrapApplication(app:
|
|
19
|
+
export async function bootstrapApplication(app: AppInstance, options: AerogelOptions = {}): Promise<void> {
|
|
17
20
|
const plugins = [testing, directives, errors, lang, services, ui, ...(options.plugins ?? [])];
|
|
18
21
|
|
|
22
|
+
App.instance = app;
|
|
23
|
+
|
|
19
24
|
await installPlugins(plugins, app, options);
|
|
20
25
|
await options.install?.(app);
|
|
21
26
|
await Events.emit('application-ready');
|
|
@@ -24,6 +29,11 @@ export async function bootstrapApplication(app: App, options: AerogelOptions = {
|
|
|
24
29
|
export async function bootstrap(rootComponent: Component, options: AerogelOptions = {}): Promise<void> {
|
|
25
30
|
const app = createApp(rootComponent);
|
|
26
31
|
|
|
32
|
+
if (Aerogel.environment === 'development') {
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
34
|
+
(window as any).$aerogel = app;
|
|
35
|
+
}
|
|
36
|
+
|
|
27
37
|
await bootstrapApplication(app, options);
|
|
28
38
|
|
|
29
39
|
app.mount('#app');
|
|
@@ -13,6 +13,7 @@ export interface IAGHeadlessModalDefaultSlotProps {
|
|
|
13
13
|
|
|
14
14
|
export const modalProps = {
|
|
15
15
|
cancellable: booleanProp(true),
|
|
16
|
+
inline: booleanProp(),
|
|
16
17
|
title: stringProp(),
|
|
17
18
|
};
|
|
18
19
|
|
|
@@ -28,7 +29,8 @@ export function extractModalProps<T extends ExtractPropTypes<typeof modalProps>>
|
|
|
28
29
|
|
|
29
30
|
export function useModalExpose($modal: Ref<IAGHeadlessModal | undefined>): IAGModal {
|
|
30
31
|
return {
|
|
31
|
-
|
|
32
|
+
inline: computed(() => !!$modal.value?.inline),
|
|
32
33
|
cancellable: computed(() => !!$modal.value?.cancellable),
|
|
34
|
+
close: async () => $modal.value?.close(),
|
|
33
35
|
};
|
|
34
36
|
}
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
2
|
+
<component
|
|
3
|
+
:is="rootComponent"
|
|
4
|
+
ref="$root"
|
|
5
|
+
:open="true"
|
|
6
|
+
@close="cancellable && close()"
|
|
7
|
+
>
|
|
3
8
|
<slot :close="close" />
|
|
4
|
-
</
|
|
9
|
+
</component>
|
|
5
10
|
</template>
|
|
6
11
|
|
|
7
12
|
<script setup lang="ts">
|
|
8
|
-
import { ref, toRef } from 'vue';
|
|
13
|
+
import { computed, ref, toRef } from 'vue';
|
|
9
14
|
import { Dialog } from '@headlessui/vue';
|
|
10
15
|
import type { VNode } from 'vue';
|
|
11
16
|
|
|
@@ -26,6 +31,7 @@ const { modal } = injectReactiveOrFail<IAGModalContext>(
|
|
|
26
31
|
'could not obtain modal reference from <AGHeadlessModal>, ' +
|
|
27
32
|
'did you render this component manually? Show it using $ui.openModal() instead',
|
|
28
33
|
);
|
|
34
|
+
const rootComponent = computed(() => (modal.properties.inline ? 'div' : Dialog));
|
|
29
35
|
|
|
30
36
|
async function hide(): Promise<void> {
|
|
31
37
|
if (!$root.value?.$el) {
|
|
@@ -82,5 +88,5 @@ useEvent('show-modal', async ({ id }) => {
|
|
|
82
88
|
});
|
|
83
89
|
|
|
84
90
|
defineSlots<{ default(props: IAGHeadlessModalDefaultSlotProps): VNode[] }>();
|
|
85
|
-
defineExpose<IAGHeadlessModal>({ close, cancellable: toRef(props, 'cancellable') });
|
|
91
|
+
defineExpose<IAGHeadlessModal>({ close, cancellable: toRef(props, 'cancellable'), inline: toRef(props, 'inline') });
|
|
86
92
|
</script>
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
2
|
+
<component :is="rootComponent">
|
|
3
3
|
<slot />
|
|
4
4
|
|
|
5
5
|
<template v-if="childModal">
|
|
6
|
-
<div
|
|
7
|
-
|
|
6
|
+
<div
|
|
7
|
+
class="pointer-events-none inset-0 z-50 bg-black/30"
|
|
8
|
+
:class="childModal.properties.inline ? 'absolute' : 'fixed'"
|
|
9
|
+
/>
|
|
10
|
+
<AGModalContext :child-index="childIndex + 1" :modal="childModal" />
|
|
8
11
|
</template>
|
|
9
|
-
</
|
|
12
|
+
</component>
|
|
10
13
|
</template>
|
|
11
14
|
|
|
12
15
|
<script setup lang="ts">
|
|
@@ -19,10 +22,11 @@ import type { IAGModalContext } from '@/components/modals/AGModalContext';
|
|
|
19
22
|
|
|
20
23
|
import AGModalContext from '../../modals/AGModalContext.vue';
|
|
21
24
|
|
|
22
|
-
const modal = injectReactiveOrFail<IAGModalContext>(
|
|
25
|
+
const { modal, childIndex } = injectReactiveOrFail<IAGModalContext>(
|
|
23
26
|
'modal',
|
|
24
27
|
'could not obtain modal reference from <AGHeadlessModalPanel>, ' +
|
|
25
28
|
'did you render this component manually? Show it using $ui.openModal() instead',
|
|
26
29
|
);
|
|
27
|
-
const
|
|
30
|
+
const rootComponent = computed(() => (modal.properties.inline ? 'div' : DialogPanel));
|
|
31
|
+
const childModal = computed(() => UI.modals[childIndex] ?? null);
|
|
28
32
|
</script>
|
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
2
|
+
<component :is="rootComponent" v-bind="rootProps">
|
|
3
3
|
<slot />
|
|
4
|
-
</
|
|
4
|
+
</component>
|
|
5
5
|
</template>
|
|
6
6
|
|
|
7
7
|
<script setup lang="ts">
|
|
8
|
+
import { computed } from 'vue';
|
|
8
9
|
import { DialogTitle } from '@headlessui/vue';
|
|
9
10
|
|
|
10
|
-
import { stringProp } from '@/utils/vue';
|
|
11
|
+
import { injectReactiveOrFail, stringProp } from '@/utils/vue';
|
|
12
|
+
import type { IAGModalContext } from '@/components/modals/AGModalContext';
|
|
11
13
|
|
|
12
|
-
defineProps({ as: stringProp('h2') });
|
|
14
|
+
const props = defineProps({ as: stringProp('h2') });
|
|
15
|
+
|
|
16
|
+
const { modal } = injectReactiveOrFail<IAGModalContext>(
|
|
17
|
+
'modal',
|
|
18
|
+
'could not obtain modal reference from <AGHeadlessModalPanel>, ' +
|
|
19
|
+
'did you render this component manually? Show it using $ui.openModal() instead',
|
|
20
|
+
);
|
|
21
|
+
const rootComponent = computed(() => (modal.properties.inline ? 'div' : DialogTitle));
|
|
22
|
+
const rootProps = computed(() => (modal.properties.inline ? {} : { as: props.as }));
|
|
13
23
|
</script>
|
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
<script setup lang="ts">
|
|
6
6
|
import { computed, h, useAttrs } from 'vue';
|
|
7
|
+
import { isInstanceOf } from '@noeldemartin/utils';
|
|
7
8
|
|
|
8
9
|
import { renderMarkdown } from '@/utils/markdown';
|
|
9
|
-
import { booleanProp, mixedProp, stringProp } from '@/utils/vue';
|
|
10
|
+
import { booleanProp, mixedProp, objectProp, stringProp } from '@/utils/vue';
|
|
10
11
|
import { translate } from '@/lang';
|
|
11
12
|
|
|
12
13
|
const props = defineProps({
|
|
@@ -15,6 +16,7 @@ const props = defineProps({
|
|
|
15
16
|
langKey: stringProp(),
|
|
16
17
|
langParams: mixedProp<number | Record<string, unknown>>(),
|
|
17
18
|
text: stringProp(),
|
|
19
|
+
actions: objectProp<Record<string, () => unknown>>(),
|
|
18
20
|
});
|
|
19
21
|
|
|
20
22
|
const attrs = useAttrs();
|
|
@@ -35,7 +37,18 @@ const html = computed(() => {
|
|
|
35
37
|
const root = () =>
|
|
36
38
|
h(props.as ?? (props.inline ? 'span' : 'div'), {
|
|
37
39
|
innerHTML: html.value,
|
|
40
|
+
onClick,
|
|
38
41
|
...attrs,
|
|
39
42
|
class: `${attrs.class ?? ''} ${props.inline ? '' : 'prose'}`,
|
|
40
43
|
});
|
|
44
|
+
|
|
45
|
+
async function onClick(event: Event) {
|
|
46
|
+
const { target } = event;
|
|
47
|
+
|
|
48
|
+
if (isInstanceOf(target, HTMLElement) && target.dataset.markdownAction) {
|
|
49
|
+
props.actions?.[target.dataset.markdownAction]?.();
|
|
50
|
+
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
41
54
|
</script>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="mt-1 h-2 w-full min-w-[min(400px,80vw)] overflow-hidden rounded-full bg-gray-200">
|
|
3
|
+
<div :class="barClasses" :style="`transform:translateX(-${(1 - progress) * 100}%)`" />
|
|
4
|
+
<span class="sr-only">
|
|
5
|
+
{{
|
|
6
|
+
$td('ui.progress', '{progress}% complete', {
|
|
7
|
+
progress: progress * 100,
|
|
8
|
+
})
|
|
9
|
+
}}
|
|
10
|
+
</span>
|
|
11
|
+
</div>
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<script setup lang="ts">
|
|
15
|
+
import { computed } from 'vue';
|
|
16
|
+
|
|
17
|
+
import { requiredNumberProp, stringProp } from '@/utils/vue';
|
|
18
|
+
|
|
19
|
+
const props = defineProps({
|
|
20
|
+
progress: requiredNumberProp(),
|
|
21
|
+
class: stringProp(''),
|
|
22
|
+
});
|
|
23
|
+
const barClasses = computed(() => {
|
|
24
|
+
const classes = props.class ?? '';
|
|
25
|
+
|
|
26
|
+
return `h-full w-full transition-transform duration-500 ease-linear ${
|
|
27
|
+
classes.includes('bg-') ? classes : `${classes} bg-gray-700`
|
|
28
|
+
}`;
|
|
29
|
+
});
|
|
30
|
+
</script>
|
|
@@ -2,4 +2,5 @@ export { default as AGErrorMessage } from './AGErrorMessage.vue';
|
|
|
2
2
|
export { default as AGLink } from './AGLink.vue';
|
|
3
3
|
export { default as AGMarkdown } from './AGMarkdown.vue';
|
|
4
4
|
export { default as AGMeasured } from './AGMeasured.vue';
|
|
5
|
+
export { default as AGProgressBar } from './AGProgressBar.vue';
|
|
5
6
|
export { default as AGStartupCrash } from './AGStartupCrash.vue';
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import type { ExtractPropTypes } from 'vue';
|
|
2
|
-
import type {
|
|
2
|
+
import type { ObjectWithout, Pretty } from '@noeldemartin/utils';
|
|
3
3
|
|
|
4
4
|
import { requiredStringProp, stringProp } from '@/utils';
|
|
5
|
+
import type { AcceptRefs } from '@/utils';
|
|
5
6
|
|
|
6
7
|
export const alertModalProps = {
|
|
7
8
|
title: stringProp(),
|
|
8
9
|
message: requiredStringProp(),
|
|
9
10
|
};
|
|
10
11
|
|
|
11
|
-
export type AGAlertModalProps =
|
|
12
|
+
export type AGAlertModalProps = Pretty<
|
|
13
|
+
AcceptRefs<ObjectWithout<ExtractPropTypes<typeof alertModalProps>, null | undefined>>
|
|
14
|
+
>;
|
|
12
15
|
|
|
13
16
|
export function useAlertModalProps(): typeof alertModalProps {
|
|
14
17
|
return alertModalProps;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { computed } from 'vue';
|
|
2
2
|
import type { ExtractPropTypes } from 'vue';
|
|
3
|
-
import type {
|
|
3
|
+
import type { ObjectWithout, Pretty, SubPartial } from '@noeldemartin/utils';
|
|
4
4
|
|
|
5
5
|
import { Colors } from '@/components/constants';
|
|
6
|
-
import { enumProp, requiredStringProp, stringProp } from '@/utils';
|
|
6
|
+
import { enumProp, objectProp, requiredStringProp, stringProp } from '@/utils';
|
|
7
7
|
import { translateWithDefault } from '@/lang';
|
|
8
|
+
import type { AcceptRefs } from '@/utils';
|
|
9
|
+
import type { ConfirmCheckboxes } from '@/ui';
|
|
8
10
|
|
|
9
11
|
export const confirmModalProps = {
|
|
10
12
|
title: stringProp(),
|
|
@@ -13,11 +15,17 @@ export const confirmModalProps = {
|
|
|
13
15
|
acceptColor: enumProp(Colors, Colors.Primary),
|
|
14
16
|
cancelText: stringProp(),
|
|
15
17
|
cancelColor: enumProp(Colors, Colors.Clear),
|
|
18
|
+
checkboxes: objectProp<ConfirmCheckboxes>(),
|
|
19
|
+
actions: objectProp<Record<string, () => unknown>>(),
|
|
16
20
|
};
|
|
17
21
|
|
|
18
|
-
export type AGConfirmModalProps =
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
export type AGConfirmModalProps = Pretty<
|
|
23
|
+
AcceptRefs<
|
|
24
|
+
SubPartial<
|
|
25
|
+
ObjectWithout<ExtractPropTypes<typeof confirmModalProps>, null | undefined>,
|
|
26
|
+
'acceptColor' | 'cancelColor'
|
|
27
|
+
>
|
|
28
|
+
>
|
|
21
29
|
>;
|
|
22
30
|
|
|
23
31
|
export function useConfirmModalProps(): typeof confirmModalProps {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<AGModal v-slot="{ close }: IAGModalDefaultSlotProps" :cancellable="false" :title="title">
|
|
3
|
-
<AGMarkdown :text="message" />
|
|
3
|
+
<AGMarkdown :text="message" :actions="actions" />
|
|
4
4
|
|
|
5
5
|
<div class="mt-2 flex flex-row-reverse gap-2">
|
|
6
6
|
<AGButton :color="acceptColor" @click="close(true)">
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { computed, ref } from 'vue';
|
|
2
2
|
import type { Component, ExtractPropTypes } from 'vue';
|
|
3
|
-
import type {
|
|
3
|
+
import type { ObjectWithout, Pretty } from '@noeldemartin/utils';
|
|
4
4
|
|
|
5
5
|
import { requiredArrayProp } from '@/utils/vue';
|
|
6
6
|
import { translateWithDefault } from '@/lang';
|
|
7
|
+
import type { AcceptRefs } from '@/utils/vue';
|
|
7
8
|
import type { ErrorReport } from '@/errors';
|
|
8
9
|
|
|
9
10
|
export interface IAGErrorReportModalButtonsDefaultSlotProps {
|
|
@@ -18,7 +19,9 @@ export const errorReportModalProps = {
|
|
|
18
19
|
reports: requiredArrayProp<ErrorReport>(),
|
|
19
20
|
};
|
|
20
21
|
|
|
21
|
-
export type AGErrorReportModalProps =
|
|
22
|
+
export type AGErrorReportModalProps = Pretty<
|
|
23
|
+
AcceptRefs<ObjectWithout<ExtractPropTypes<typeof errorReportModalProps>, null | undefined>>
|
|
24
|
+
>;
|
|
22
25
|
|
|
23
26
|
export function useErrorReportModalProps(): typeof errorReportModalProps {
|
|
24
27
|
return errorReportModalProps;
|
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
import { computed } from 'vue';
|
|
2
2
|
import type { ExtractPropTypes } from 'vue';
|
|
3
|
-
import type {
|
|
3
|
+
import type { ObjectWithout } from '@noeldemartin/utils';
|
|
4
4
|
|
|
5
|
-
import { stringProp } from '@/utils';
|
|
5
|
+
import { numberProp, stringProp } from '@/utils';
|
|
6
6
|
import { translateWithDefault } from '@/lang';
|
|
7
|
+
import type { AcceptRefs } from '@/utils';
|
|
7
8
|
|
|
8
9
|
export const loadingModalProps = {
|
|
10
|
+
title: stringProp(),
|
|
9
11
|
message: stringProp(),
|
|
12
|
+
progress: numberProp(),
|
|
10
13
|
};
|
|
11
14
|
|
|
12
|
-
export type AGLoadingModalProps =
|
|
15
|
+
export type AGLoadingModalProps = AcceptRefs<
|
|
16
|
+
ObjectWithout<ExtractPropTypes<typeof loadingModalProps>, null | undefined>
|
|
17
|
+
>;
|
|
13
18
|
|
|
14
19
|
export function useLoadingModalProps(): typeof loadingModalProps {
|
|
15
20
|
return loadingModalProps;
|
|
@@ -18,6 +23,7 @@ export function useLoadingModalProps(): typeof loadingModalProps {
|
|
|
18
23
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
|
19
24
|
export function useLoadingModal(props: ExtractPropTypes<typeof loadingModalProps>) {
|
|
20
25
|
const renderedMessage = computed(() => props.message ?? translateWithDefault('ui.loading', 'Loading...'));
|
|
26
|
+
const showProgress = computed(() => typeof props.progress === 'number');
|
|
21
27
|
|
|
22
|
-
return { renderedMessage };
|
|
28
|
+
return { renderedMessage, showProgress };
|
|
23
29
|
}
|
|
@@ -1,18 +1,28 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<component :is="modal.component" v-bind="
|
|
2
|
+
<component :is="modal.component" v-bind="modalProperties" />
|
|
3
3
|
</template>
|
|
4
4
|
|
|
5
5
|
<script setup lang="ts">
|
|
6
|
-
import { provide, toRef } from 'vue';
|
|
6
|
+
import { computed, provide, toRef, unref } from 'vue';
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { numberProp, requiredObjectProp } from '@/utils/vue';
|
|
9
9
|
import type { Modal } from '@/ui/UI.state';
|
|
10
10
|
|
|
11
11
|
import type { IAGModalContext } from './AGModalContext';
|
|
12
12
|
|
|
13
13
|
const props = defineProps({
|
|
14
14
|
modal: requiredObjectProp<Modal>(),
|
|
15
|
-
childIndex:
|
|
15
|
+
childIndex: numberProp(0),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const modalProperties = computed(() => {
|
|
19
|
+
const properties = {} as typeof props.modal.properties;
|
|
20
|
+
|
|
21
|
+
for (const property in props.modal.properties) {
|
|
22
|
+
properties[property] = unref(props.modal.properties[property]);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return properties;
|
|
16
26
|
});
|
|
17
27
|
|
|
18
28
|
provide<IAGModalContext>('modal', {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { computed } from 'vue';
|
|
2
2
|
import type { ExtractPropTypes } from 'vue';
|
|
3
|
-
import type {
|
|
3
|
+
import type { ObjectWithout, Pretty, SubPartial } from '@noeldemartin/utils';
|
|
4
4
|
|
|
5
5
|
import { Colors } from '@/components/constants';
|
|
6
6
|
import { enumProp, requiredStringProp, stringProp } from '@/utils';
|
|
7
7
|
import { translateWithDefault } from '@/lang';
|
|
8
|
+
import type { AcceptRefs } from '@/utils';
|
|
8
9
|
|
|
9
10
|
export const promptModalProps = {
|
|
10
11
|
title: stringProp(),
|
|
@@ -18,9 +19,13 @@ export const promptModalProps = {
|
|
|
18
19
|
cancelColor: enumProp(Colors, Colors.Clear),
|
|
19
20
|
};
|
|
20
21
|
|
|
21
|
-
export type AGPromptModalProps =
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
export type AGPromptModalProps = Pretty<
|
|
23
|
+
AcceptRefs<
|
|
24
|
+
SubPartial<
|
|
25
|
+
ObjectWithout<ExtractPropTypes<typeof promptModalProps>, null | undefined>,
|
|
26
|
+
'acceptColor' | 'cancelColor'
|
|
27
|
+
>
|
|
28
|
+
>
|
|
24
29
|
>;
|
|
25
30
|
|
|
26
31
|
export function usePromptModalProps(): typeof promptModalProps {
|
package/src/errors/utils.ts
CHANGED
|
@@ -2,7 +2,23 @@ import { JSError, isObject, toString } from '@noeldemartin/utils';
|
|
|
2
2
|
import { translateWithDefault } from '@/lang/utils';
|
|
3
3
|
import type { ErrorSource } from './Errors.state';
|
|
4
4
|
|
|
5
|
+
const handlers: ErrorHandler[] = [];
|
|
6
|
+
|
|
7
|
+
export type ErrorHandler = (error: ErrorSource) => string | undefined;
|
|
8
|
+
|
|
9
|
+
export function registerErrorHandler(handler: ErrorHandler): void {
|
|
10
|
+
handlers.push(handler);
|
|
11
|
+
}
|
|
12
|
+
|
|
5
13
|
export function getErrorMessage(error: ErrorSource): string {
|
|
14
|
+
for (const handler of handlers) {
|
|
15
|
+
const result = handler(error);
|
|
16
|
+
|
|
17
|
+
if (result) {
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
6
22
|
if (typeof error === 'string') {
|
|
7
23
|
return error;
|
|
8
24
|
}
|
package/src/forms/Form.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { MagicObject, arrayRemove, fail, toString } from '@noeldemartin/utils';
|
|
2
1
|
import { computed, nextTick, reactive, readonly, ref } from 'vue';
|
|
2
|
+
import { MagicObject, arrayRemove, fail, toString } from '@noeldemartin/utils';
|
|
3
|
+
import { validate } from './validation';
|
|
3
4
|
import type { ObjectValues } from '@noeldemartin/utils';
|
|
4
5
|
import type { ComputedRef, DeepReadonly, Ref, UnwrapNestedRefs } from 'vue';
|
|
5
6
|
|
|
@@ -184,9 +185,15 @@ export default class Form<Fields extends FormFieldDefinitions = FormFieldDefinit
|
|
|
184
185
|
|
|
185
186
|
private getFieldErrors(name: keyof Fields, definition: FormFieldDefinition): string[] | null {
|
|
186
187
|
const errors = [];
|
|
188
|
+
const value = this._data[name];
|
|
189
|
+
const rules = definition.rules?.split('|') ?? [];
|
|
190
|
+
|
|
191
|
+
for (const rule of rules) {
|
|
192
|
+
if (rule !== 'required' && (value === null || value === undefined)) {
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
187
195
|
|
|
188
|
-
|
|
189
|
-
errors.push('required');
|
|
196
|
+
errors.push(...validate(value, rule));
|
|
190
197
|
}
|
|
191
198
|
|
|
192
199
|
return errors.length > 0 ? errors : null;
|
package/src/forms/index.ts
CHANGED
package/src/forms/utils.ts
CHANGED
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
import { FormFieldTypes } from './Form';
|
|
2
2
|
import type { FormFieldDefinition } from './Form';
|
|
3
3
|
|
|
4
|
-
export function booleanInput(
|
|
4
|
+
export function booleanInput(
|
|
5
|
+
defaultValue?: boolean,
|
|
6
|
+
options: { rules?: string } = {},
|
|
7
|
+
): FormFieldDefinition<typeof FormFieldTypes.Boolean> {
|
|
5
8
|
return {
|
|
6
9
|
default: defaultValue,
|
|
7
10
|
type: FormFieldTypes.Boolean,
|
|
11
|
+
rules: options.rules,
|
|
8
12
|
};
|
|
9
13
|
}
|
|
10
14
|
|
|
11
|
-
export function dateInput(
|
|
15
|
+
export function dateInput(
|
|
16
|
+
defaultValue?: Date,
|
|
17
|
+
options: { rules?: string } = {},
|
|
18
|
+
): FormFieldDefinition<typeof FormFieldTypes.Date> {
|
|
12
19
|
return {
|
|
13
20
|
default: defaultValue,
|
|
14
21
|
type: FormFieldTypes.Date,
|
|
22
|
+
rules: options.rules,
|
|
15
23
|
};
|
|
16
24
|
}
|
|
17
25
|
|
|
@@ -53,16 +61,24 @@ export function requiredStringInput(
|
|
|
53
61
|
};
|
|
54
62
|
}
|
|
55
63
|
|
|
56
|
-
export function numberInput(
|
|
64
|
+
export function numberInput(
|
|
65
|
+
defaultValue?: number,
|
|
66
|
+
options: { rules?: string } = {},
|
|
67
|
+
): FormFieldDefinition<typeof FormFieldTypes.Number> {
|
|
57
68
|
return {
|
|
58
69
|
default: defaultValue,
|
|
59
70
|
type: FormFieldTypes.Number,
|
|
71
|
+
rules: options.rules,
|
|
60
72
|
};
|
|
61
73
|
}
|
|
62
74
|
|
|
63
|
-
export function stringInput(
|
|
75
|
+
export function stringInput(
|
|
76
|
+
defaultValue?: string,
|
|
77
|
+
options: { rules?: string } = {},
|
|
78
|
+
): FormFieldDefinition<typeof FormFieldTypes.String> {
|
|
64
79
|
return {
|
|
65
80
|
default: defaultValue,
|
|
66
81
|
type: FormFieldTypes.String,
|
|
82
|
+
rules: options.rules,
|
|
67
83
|
};
|
|
68
84
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { arrayFrom } from '@noeldemartin/utils';
|
|
2
|
+
|
|
3
|
+
const builtInRules: Record<string, FormFieldValidator> = {
|
|
4
|
+
required: (value) => (value ? undefined : 'required'),
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export type FormFieldValidator<T = unknown> = (value: T) => string | string[] | undefined;
|
|
8
|
+
|
|
9
|
+
export const validators: Record<string, FormFieldValidator> = { ...builtInRules };
|
|
10
|
+
|
|
11
|
+
export function defineFormValidationRule<T>(rule: string, validator: FormFieldValidator<T>): void {
|
|
12
|
+
validators[rule] = validator as FormFieldValidator;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function validate(value: unknown, rule: string): string[] {
|
|
16
|
+
const errors = validators[rule]?.(value);
|
|
17
|
+
|
|
18
|
+
return errors ? arrayFrom(errors) : [];
|
|
19
|
+
}
|