@aerogel/core 0.0.0-next.bde642c4a8096c5fc3d5e676c2115da23f4bf1d8 → 0.0.0-next.c4825c5cbe0fe3257e478c2a7ec8df27d5a72305
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 +371 -69
- package/dist/aerogel-core.esm.js +1 -1
- package/dist/aerogel-core.esm.js.map +1 -1
- package/package.json +3 -3
- package/src/bootstrap/index.ts +12 -2
- package/src/components/headless/forms/AGHeadlessInput.ts +1 -0
- package/src/components/headless/forms/AGHeadlessInput.vue +7 -0
- package/src/components/headless/forms/AGHeadlessInputInput.vue +1 -0
- package/src/components/headless/forms/AGHeadlessInputTextArea.vue +1 -0
- 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/AGMeasured.vue +1 -0
- 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 +14 -5
- package/src/components/modals/AGConfirmModal.vue +2 -2
- 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/directives/measure.ts +24 -5
- package/src/errors/JobCancelledError.ts +3 -0
- package/src/errors/utils.ts +16 -0
- package/src/forms/Form.test.ts +28 -0
- package/src/forms/Form.ts +24 -6
- 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/lang/Lang.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.state.ts +7 -0
- package/src/ui/UI.ts +136 -43
- package/src/ui/index.ts +1 -0
- package/src/ui/utils.ts +16 -0
- 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 +11 -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.c4825c5cbe0fe3257e478c2a7ec8df27d5a72305",
|
|
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",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@aerogel/vite": "*",
|
|
46
|
-
"@histoire/plugin-vue": "
|
|
46
|
+
"@histoire/plugin-vue": "0.17.6",
|
|
47
47
|
"@types/dompurify": "^3.0.2",
|
|
48
48
|
"@types/marked": "^5.0.0",
|
|
49
49
|
"tailwindcss": "^3.3.5"
|
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');
|
|
@@ -11,6 +11,7 @@ export interface IAGHeadlessInput extends HasElement {
|
|
|
11
11
|
label: ComputedRef<string | null>;
|
|
12
12
|
description: ComputedRef<string | boolean | null>;
|
|
13
13
|
value: ComputedRef<FormFieldValue | null>;
|
|
14
|
+
required: ComputedRef<boolean | null>;
|
|
14
15
|
errors: DeepReadonly<Ref<string[] | null>>;
|
|
15
16
|
update(value: FormFieldValue | null): void;
|
|
16
17
|
}
|
|
@@ -44,6 +44,13 @@ const api: IAGHeadlessInput & __SetsElement = {
|
|
|
44
44
|
return props.modelValue;
|
|
45
45
|
}),
|
|
46
46
|
errors: readonly(errors),
|
|
47
|
+
required: computed(() => {
|
|
48
|
+
if (!props.name || !form) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return form.getFieldRules(props.name).includes('required');
|
|
53
|
+
}),
|
|
47
54
|
update(value) {
|
|
48
55
|
if (form && props.name) {
|
|
49
56
|
form.setFieldValue(props.name, value);
|
|
@@ -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
|
+
barClass: stringProp(''),
|
|
22
|
+
});
|
|
23
|
+
const barClasses = computed(() => {
|
|
24
|
+
const classes = props.barClass ?? '';
|
|
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 { booleanProp, 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,18 @@ 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>>(),
|
|
20
|
+
required: booleanProp(false),
|
|
16
21
|
};
|
|
17
22
|
|
|
18
|
-
export type AGConfirmModalProps =
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
export type AGConfirmModalProps = Pretty<
|
|
24
|
+
AcceptRefs<
|
|
25
|
+
SubPartial<
|
|
26
|
+
ObjectWithout<ExtractPropTypes<typeof confirmModalProps>, null | undefined>,
|
|
27
|
+
'acceptColor' | 'cancelColor'
|
|
28
|
+
>
|
|
29
|
+
>
|
|
21
30
|
>;
|
|
22
31
|
|
|
23
32
|
export function useConfirmModalProps(): typeof confirmModalProps {
|
|
@@ -1,12 +1,12 @@
|
|
|
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)">
|
|
7
7
|
{{ renderedAcceptText }}
|
|
8
8
|
</AGButton>
|
|
9
|
-
<AGButton :color="cancelColor" @click="close()">
|
|
9
|
+
<AGButton v-if="!required" :color="cancelColor" @click="close()">
|
|
10
10
|
{{ renderedCancelText }}
|
|
11
11
|
</AGButton>
|
|
12
12
|
</div>
|
|
@@ -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 {
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { defineDirective } from '@/utils/vue';
|
|
2
|
+
import { tap } from '@noeldemartin/utils';
|
|
3
|
+
|
|
4
|
+
const resizeObservers: WeakMap<HTMLElement, ResizeObserver> = new WeakMap();
|
|
2
5
|
|
|
3
6
|
export interface ElementSize {
|
|
4
7
|
width: number;
|
|
@@ -9,13 +12,29 @@ export type MeasureDirectiveListener = (size: ElementSize) => unknown;
|
|
|
9
12
|
|
|
10
13
|
export default defineDirective({
|
|
11
14
|
mounted(element: HTMLElement, { value }) {
|
|
15
|
+
// TODO replace with argument when typed properly
|
|
16
|
+
const modifiers = { css: true, watch: true };
|
|
17
|
+
|
|
12
18
|
const listener = typeof value === 'function' ? (value as MeasureDirectiveListener) : null;
|
|
13
|
-
const
|
|
19
|
+
const update = () => {
|
|
20
|
+
const sizes = element.getBoundingClientRect();
|
|
21
|
+
|
|
22
|
+
if (modifiers.css) {
|
|
23
|
+
element.style.setProperty('--width', `${sizes.width}px`);
|
|
24
|
+
element.style.setProperty('--height', `${sizes.height}px`);
|
|
25
|
+
}
|
|
14
26
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
element.style.setProperty('--height', `${sizes.height}px`);
|
|
27
|
+
listener?.({ width: sizes.width, height: sizes.height });
|
|
28
|
+
};
|
|
18
29
|
|
|
19
|
-
|
|
30
|
+
if (modifiers.watch) {
|
|
31
|
+
resizeObservers.set(element, tap(new ResizeObserver(update)).observe(element));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
update();
|
|
35
|
+
},
|
|
36
|
+
unmounted(element) {
|
|
37
|
+
resizeObservers.get(element)?.unobserve(element);
|
|
38
|
+
resizeObservers.delete(element);
|
|
20
39
|
},
|
|
21
40
|
});
|
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.test.ts
CHANGED
|
@@ -55,4 +55,32 @@ describe('Form', () => {
|
|
|
55
55
|
expect(form.name).toBeNull();
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
+
it('trims values', () => {
|
|
59
|
+
// Arrange
|
|
60
|
+
const form = useForm({
|
|
61
|
+
trimmed: {
|
|
62
|
+
type: FormFieldTypes.String,
|
|
63
|
+
rules: 'required',
|
|
64
|
+
},
|
|
65
|
+
untrimmed: {
|
|
66
|
+
type: FormFieldTypes.String,
|
|
67
|
+
rules: 'required',
|
|
68
|
+
trim: false,
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Act
|
|
73
|
+
form.trimmed = ' ';
|
|
74
|
+
form.untrimmed = ' ';
|
|
75
|
+
|
|
76
|
+
form.submit();
|
|
77
|
+
|
|
78
|
+
// Assert
|
|
79
|
+
expect(form.valid).toBe(false);
|
|
80
|
+
expect(form.submitted).toBe(true);
|
|
81
|
+
expect(form.trimmed).toEqual('');
|
|
82
|
+
expect(form.untrimmed).toEqual(' ');
|
|
83
|
+
expect(form.errors).toEqual({ trimmed: ['required'], untrimmed: null });
|
|
84
|
+
});
|
|
85
|
+
|
|
58
86
|
});
|