@aerogel/core 0.0.0-next.fd1bd21aea7a9ab8c4eab69a5f5776db5de8bf35 → 0.1.0
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.css +1 -0
- package/dist/aerogel-core.d.ts +2263 -1698
- package/dist/aerogel-core.js +3809 -0
- package/dist/aerogel-core.js.map +1 -0
- package/package.json +32 -37
- package/src/bootstrap/bootstrap.test.ts +4 -8
- package/src/bootstrap/index.ts +25 -16
- package/src/bootstrap/options.ts +1 -1
- package/src/components/AppLayout.vue +14 -0
- package/src/components/{AGAppModals.vue → AppModals.vue} +3 -4
- package/src/components/AppOverlays.vue +9 -0
- package/src/components/AppToasts.vue +16 -0
- package/src/components/contracts/AlertModal.ts +19 -0
- package/src/components/contracts/Button.ts +16 -0
- package/src/components/contracts/ConfirmModal.ts +48 -0
- package/src/components/contracts/DropdownMenu.ts +25 -0
- package/src/components/contracts/ErrorReportModal.ts +33 -0
- package/src/components/contracts/Input.ts +26 -0
- package/src/components/contracts/LoadingModal.ts +26 -0
- package/src/components/contracts/Modal.ts +21 -0
- package/src/components/contracts/PromptModal.ts +34 -0
- package/src/components/contracts/Select.ts +45 -0
- package/src/components/contracts/Toast.ts +15 -0
- package/src/components/contracts/index.ts +11 -0
- package/src/components/headless/HeadlessButton.vue +51 -0
- package/src/components/headless/HeadlessInput.vue +59 -0
- package/src/components/headless/{forms/AGHeadlessInputDescription.vue → HeadlessInputDescription.vue} +7 -8
- package/src/components/headless/{forms/AGHeadlessInputError.vue → HeadlessInputError.vue} +4 -8
- package/src/components/headless/HeadlessInputInput.vue +86 -0
- package/src/components/headless/{forms/AGHeadlessInputLabel.vue → HeadlessInputLabel.vue} +3 -7
- package/src/components/headless/{forms/AGHeadlessInputTextArea.vue → HeadlessInputTextArea.vue} +11 -11
- package/src/components/headless/HeadlessModal.vue +57 -0
- package/src/components/headless/HeadlessModalContent.vue +30 -0
- package/src/components/headless/HeadlessModalDescription.vue +12 -0
- package/src/components/headless/HeadlessModalOverlay.vue +12 -0
- package/src/components/headless/HeadlessModalTitle.vue +12 -0
- package/src/components/headless/HeadlessSelect.vue +120 -0
- package/src/components/headless/{forms/AGHeadlessSelectError.vue → HeadlessSelectError.vue} +5 -6
- package/src/components/headless/HeadlessSelectLabel.vue +25 -0
- package/src/components/headless/HeadlessSelectOption.vue +34 -0
- package/src/components/headless/HeadlessSelectOptions.vue +42 -0
- package/src/components/headless/HeadlessSelectTrigger.vue +22 -0
- package/src/components/headless/HeadlessSelectValue.vue +18 -0
- package/src/components/headless/HeadlessSwitch.vue +96 -0
- package/src/components/headless/HeadlessToast.vue +18 -0
- package/src/components/headless/HeadlessToastAction.vue +13 -0
- package/src/components/headless/index.ts +20 -3
- package/src/components/index.ts +6 -9
- package/src/components/ui/AdvancedOptions.vue +18 -0
- package/src/components/ui/AlertModal.vue +17 -0
- package/src/components/ui/Button.vue +115 -0
- package/src/components/ui/Checkbox.vue +56 -0
- package/src/components/ui/ConfirmModal.vue +50 -0
- package/src/components/ui/DropdownMenu.vue +32 -0
- package/src/components/ui/DropdownMenuOption.vue +22 -0
- package/src/components/ui/DropdownMenuOptions.vue +44 -0
- package/src/components/ui/EditableContent.vue +82 -0
- package/src/components/ui/ErrorLogs.vue +19 -0
- package/src/components/ui/ErrorLogsModal.vue +48 -0
- package/src/components/ui/ErrorMessage.vue +15 -0
- package/src/components/ui/ErrorReportModal.vue +73 -0
- package/src/components/{modals/AGErrorReportModalButtons.vue → ui/ErrorReportModalButtons.vue} +34 -27
- package/src/components/ui/ErrorReportModalTitle.vue +24 -0
- package/src/components/{forms/AGForm.vue → ui/Form.vue} +4 -5
- package/src/components/ui/Input.vue +56 -0
- package/src/components/ui/Link.vue +12 -0
- package/src/components/ui/LoadingModal.vue +34 -0
- package/src/components/ui/Markdown.vue +97 -0
- package/src/components/ui/Modal.vue +131 -0
- package/src/components/ui/ModalContext.vue +31 -0
- package/src/components/ui/ProgressBar.vue +51 -0
- package/src/components/ui/PromptModal.vue +38 -0
- package/src/components/ui/Select.vue +27 -0
- package/src/components/ui/SelectLabel.vue +21 -0
- package/src/components/ui/SelectOption.vue +29 -0
- package/src/components/ui/SelectOptions.vue +35 -0
- package/src/components/ui/SelectTrigger.vue +29 -0
- package/src/components/ui/Setting.vue +31 -0
- package/src/components/ui/SettingsModal.vue +15 -0
- package/src/components/ui/StartupCrash.vue +76 -0
- package/src/components/ui/Switch.vue +11 -0
- package/src/components/ui/TextArea.vue +56 -0
- package/src/components/ui/Toast.vue +46 -0
- package/src/components/ui/index.ts +35 -0
- package/src/directives/index.ts +9 -5
- package/src/directives/measure.ts +33 -8
- package/src/errors/Errors.state.ts +2 -1
- package/src/errors/Errors.ts +56 -33
- package/src/errors/JobCancelledError.ts +3 -0
- package/src/errors/index.ts +15 -8
- package/src/errors/settings/Debug.vue +14 -0
- package/src/errors/settings/index.ts +10 -0
- package/src/errors/utils.ts +17 -1
- package/src/forms/FormController.test.ts +113 -0
- package/src/forms/{Form.ts → FormController.ts} +73 -42
- package/src/forms/index.ts +3 -3
- package/src/forms/utils.ts +65 -24
- package/src/forms/validation.ts +50 -0
- package/src/index.css +76 -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/DefaultLangProvider.ts +46 -0
- package/src/lang/Lang.state.ts +11 -0
- package/src/lang/Lang.ts +48 -21
- package/src/lang/index.ts +12 -6
- package/src/lang/settings/Language.vue +48 -0
- package/src/lang/settings/index.ts +10 -0
- package/src/plugins/Plugin.ts +1 -1
- package/src/plugins/index.ts +10 -7
- package/src/services/App.state.ts +36 -3
- package/src/services/App.ts +19 -3
- package/src/services/Cache.ts +1 -1
- package/src/services/Events.test.ts +8 -8
- package/src/services/Events.ts +16 -12
- package/src/services/Service.ts +135 -59
- package/src/services/Storage.ts +20 -0
- package/src/services/index.ts +16 -7
- package/src/services/utils.ts +18 -0
- package/src/testing/index.ts +8 -3
- package/src/testing/setup.ts +11 -0
- package/src/ui/UI.state.ts +14 -12
- package/src/ui/UI.ts +250 -123
- package/src/ui/index.ts +28 -28
- package/src/ui/utils.ts +16 -0
- package/src/utils/app.ts +7 -0
- package/src/utils/classes.ts +41 -0
- package/src/utils/composition/events.ts +4 -6
- package/src/utils/composition/forms.ts +20 -4
- 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 +33 -0
- package/src/utils/index.ts +6 -1
- package/src/utils/markdown.test.ts +50 -0
- package/src/utils/markdown.ts +53 -6
- package/src/utils/types.ts +3 -0
- package/src/utils/vue.ts +38 -132
- package/dist/aerogel-core.cjs.js +0 -2
- package/dist/aerogel-core.cjs.js.map +0 -1
- package/dist/aerogel-core.esm.js +0 -2
- package/dist/aerogel-core.esm.js.map +0 -1
- package/histoire.config.ts +0 -7
- package/noeldemartin.config.js +0 -5
- package/postcss.config.js +0 -6
- package/src/assets/histoire.css +0 -3
- package/src/components/AGAppLayout.vue +0 -16
- package/src/components/AGAppOverlays.vue +0 -41
- package/src/components/AGAppSnackbars.vue +0 -13
- package/src/components/constants.ts +0 -8
- package/src/components/forms/AGButton.vue +0 -44
- package/src/components/forms/AGCheckbox.vue +0 -41
- package/src/components/forms/AGInput.vue +0 -40
- package/src/components/forms/AGSelect.story.vue +0 -46
- package/src/components/forms/AGSelect.vue +0 -60
- package/src/components/forms/index.ts +0 -5
- package/src/components/headless/forms/AGHeadlessButton.vue +0 -56
- package/src/components/headless/forms/AGHeadlessInput.ts +0 -32
- package/src/components/headless/forms/AGHeadlessInput.vue +0 -57
- package/src/components/headless/forms/AGHeadlessInputInput.vue +0 -81
- package/src/components/headless/forms/AGHeadlessSelect.ts +0 -42
- package/src/components/headless/forms/AGHeadlessSelect.vue +0 -77
- package/src/components/headless/forms/AGHeadlessSelectButton.vue +0 -24
- package/src/components/headless/forms/AGHeadlessSelectLabel.vue +0 -24
- package/src/components/headless/forms/AGHeadlessSelectOption.ts +0 -4
- package/src/components/headless/forms/AGHeadlessSelectOption.vue +0 -39
- package/src/components/headless/forms/AGHeadlessSelectOptions.ts +0 -3
- package/src/components/headless/forms/composition.ts +0 -10
- package/src/components/headless/forms/index.ts +0 -17
- package/src/components/headless/modals/AGHeadlessModal.ts +0 -34
- package/src/components/headless/modals/AGHeadlessModal.vue +0 -86
- package/src/components/headless/modals/AGHeadlessModalPanel.vue +0 -28
- package/src/components/headless/modals/AGHeadlessModalTitle.vue +0 -13
- package/src/components/headless/modals/index.ts +0 -4
- package/src/components/headless/snackbars/AGHeadlessSnackbar.vue +0 -10
- package/src/components/headless/snackbars/index.ts +0 -40
- package/src/components/lib/AGErrorMessage.vue +0 -16
- package/src/components/lib/AGLink.vue +0 -9
- package/src/components/lib/AGMarkdown.vue +0 -41
- package/src/components/lib/AGMeasured.vue +0 -15
- package/src/components/lib/AGStartupCrash.vue +0 -31
- package/src/components/lib/index.ts +0 -5
- package/src/components/modals/AGAlertModal.ts +0 -15
- package/src/components/modals/AGAlertModal.vue +0 -14
- package/src/components/modals/AGConfirmModal.ts +0 -27
- package/src/components/modals/AGConfirmModal.vue +0 -26
- package/src/components/modals/AGErrorReportModal.ts +0 -46
- package/src/components/modals/AGErrorReportModal.vue +0 -54
- package/src/components/modals/AGErrorReportModalTitle.vue +0 -25
- package/src/components/modals/AGLoadingModal.ts +0 -23
- package/src/components/modals/AGLoadingModal.vue +0 -15
- package/src/components/modals/AGModal.ts +0 -10
- package/src/components/modals/AGModal.vue +0 -39
- package/src/components/modals/AGModalContext.ts +0 -8
- package/src/components/modals/AGModalContext.vue +0 -22
- package/src/components/modals/AGModalTitle.vue +0 -9
- package/src/components/modals/AGPromptModal.ts +0 -30
- package/src/components/modals/AGPromptModal.vue +0 -34
- package/src/components/modals/index.ts +0 -17
- package/src/components/snackbars/AGSnackbar.vue +0 -36
- package/src/components/snackbars/index.ts +0 -3
- package/src/components/utils.ts +0 -10
- package/src/directives/initial-focus.ts +0 -11
- package/src/forms/Form.test.ts +0 -58
- package/src/forms/composition.ts +0 -6
- package/src/main.histoire.ts +0 -1
- package/src/utils/tailwindcss.test.ts +0 -26
- package/src/utils/tailwindcss.ts +0 -7
- package/tailwind.config.js +0 -4
- package/tsconfig.json +0 -11
- package/vite.config.ts +0 -14
- /package/src/{main.ts → index.ts} +0 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<component :is="as">
|
|
3
|
+
<slot />
|
|
4
|
+
</component>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup lang="ts">
|
|
8
|
+
import { computed, inject, provide, readonly } from 'vue';
|
|
9
|
+
import { uuid } from '@noeldemartin/utils';
|
|
10
|
+
|
|
11
|
+
import type FormController from '@aerogel/core/forms/FormController';
|
|
12
|
+
import type { FormFieldValue } from '@aerogel/core/forms/FormController';
|
|
13
|
+
import type { InputEmits, InputExpose, InputProps } from '@aerogel/core/components/contracts/Input';
|
|
14
|
+
|
|
15
|
+
const { as = 'div', name, label, description, modelValue } = defineProps<InputProps & { as?: string }>();
|
|
16
|
+
const emit = defineEmits<InputEmits>();
|
|
17
|
+
const form = inject<FormController | null>('form', null);
|
|
18
|
+
const errors = computed(() => {
|
|
19
|
+
if (!form || !name) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return form.errors[name] ?? null;
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const expose = {
|
|
27
|
+
id: `input-${uuid()}`,
|
|
28
|
+
name: computed(() => name),
|
|
29
|
+
label: computed(() => label),
|
|
30
|
+
description: computed(() => description),
|
|
31
|
+
value: computed(() => {
|
|
32
|
+
if (form && name) {
|
|
33
|
+
return form.getFieldValue(name);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return modelValue;
|
|
37
|
+
}),
|
|
38
|
+
errors: readonly(errors),
|
|
39
|
+
required: computed(() => {
|
|
40
|
+
if (!name || !form) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return form.getFieldRules(name).includes('required');
|
|
45
|
+
}),
|
|
46
|
+
update(value) {
|
|
47
|
+
if (form && name) {
|
|
48
|
+
form.setFieldValue(name, value as FormFieldValue);
|
|
49
|
+
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
emit('update:modelValue', value);
|
|
54
|
+
},
|
|
55
|
+
} satisfies InputExpose;
|
|
56
|
+
|
|
57
|
+
provide('input', expose);
|
|
58
|
+
defineExpose(expose);
|
|
59
|
+
</script>
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<slot :id="`${input.id}-description`">
|
|
3
|
-
<
|
|
3
|
+
<Markdown
|
|
4
4
|
v-if="show"
|
|
5
5
|
v-bind="$attrs"
|
|
6
6
|
:id="`${input.id}-description`"
|
|
7
|
-
:text
|
|
7
|
+
:text
|
|
8
8
|
/>
|
|
9
9
|
</slot>
|
|
10
10
|
</template>
|
|
@@ -12,16 +12,15 @@
|
|
|
12
12
|
<script setup lang="ts">
|
|
13
13
|
import { computed } from 'vue';
|
|
14
14
|
|
|
15
|
-
import
|
|
16
|
-
|
|
17
|
-
import
|
|
18
|
-
import type { IAGHeadlessInput } from './AGHeadlessInput';
|
|
15
|
+
import Markdown from '@aerogel/core/components/ui/Markdown.vue';
|
|
16
|
+
import { injectReactiveOrFail } from '@aerogel/core/utils/vue';
|
|
17
|
+
import type { InputExpose } from '@aerogel/core/components/contracts/Input';
|
|
19
18
|
|
|
20
19
|
defineOptions({ inheritAttrs: false });
|
|
21
20
|
|
|
22
|
-
const input = injectReactiveOrFail<
|
|
21
|
+
const input = injectReactiveOrFail<InputExpose>(
|
|
23
22
|
'input',
|
|
24
|
-
'<
|
|
23
|
+
'<HeadlessInputDescription> must be a child of a <HeadlessInput>',
|
|
25
24
|
);
|
|
26
25
|
const text = computed(() => (typeof input.description === 'string' ? input.description : ''));
|
|
27
26
|
const show = computed(() => !!input.description);
|
|
@@ -7,15 +7,11 @@
|
|
|
7
7
|
<script setup lang="ts">
|
|
8
8
|
import { computed } from 'vue';
|
|
9
9
|
|
|
10
|
-
import { injectReactiveOrFail } from '
|
|
11
|
-
import { translateWithDefault } from '
|
|
10
|
+
import { injectReactiveOrFail } from '@aerogel/core/utils/vue';
|
|
11
|
+
import { translateWithDefault } from '@aerogel/core/lang/utils';
|
|
12
|
+
import type { InputExpose } from '@aerogel/core/components/contracts/Input';
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const input = injectReactiveOrFail<IAGHeadlessInput>(
|
|
16
|
-
'input',
|
|
17
|
-
'<AGHeadlessInputError> must be a child of a <AGHeadlessInput>',
|
|
18
|
-
);
|
|
14
|
+
const input = injectReactiveOrFail<InputExpose>('input', '<HeadlessInputError> must be a child of a <HeadlessInput>');
|
|
19
15
|
const errorMessage = computed(() => {
|
|
20
16
|
if (!input.errors) {
|
|
21
17
|
return null;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<input
|
|
3
|
+
:id="input.id"
|
|
4
|
+
ref="$inputRef"
|
|
5
|
+
:name
|
|
6
|
+
:checked
|
|
7
|
+
:type="renderedType"
|
|
8
|
+
:required="input.required ?? undefined"
|
|
9
|
+
:aria-invalid="input.errors ? 'true' : 'false'"
|
|
10
|
+
:aria-describedby="
|
|
11
|
+
input.errors ? `${input.id}-error` : input.description ? `${input.id}-description` : undefined
|
|
12
|
+
"
|
|
13
|
+
@input="update"
|
|
14
|
+
>
|
|
15
|
+
</template>
|
|
16
|
+
|
|
17
|
+
<script setup lang="ts">
|
|
18
|
+
import { computed, inject, useTemplateRef, watchEffect } from 'vue';
|
|
19
|
+
|
|
20
|
+
import { injectReactiveOrFail } from '@aerogel/core/utils/vue';
|
|
21
|
+
import { onFormFocus } from '@aerogel/core/utils/composition/forms';
|
|
22
|
+
import type FormController from '@aerogel/core/forms/FormController';
|
|
23
|
+
import type { FormFieldValue } from '@aerogel/core/forms/FormController';
|
|
24
|
+
import type { InputExpose } from '@aerogel/core/components/contracts/Input';
|
|
25
|
+
|
|
26
|
+
const { type } = defineProps<{ type?: string }>();
|
|
27
|
+
const $input = useTemplateRef('$inputRef');
|
|
28
|
+
const input = injectReactiveOrFail<InputExpose>('input', '<HeadlessInputInput> must be a child of a <HeadlessInput>');
|
|
29
|
+
const form = inject<FormController | null>('form', null);
|
|
30
|
+
const name = computed(() => input.name ?? undefined);
|
|
31
|
+
const value = computed(() => input.value);
|
|
32
|
+
const renderedType = computed(() => {
|
|
33
|
+
if (type) {
|
|
34
|
+
return type;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const fieldType = (name.value && form?.getFieldType(name.value)) ?? '';
|
|
38
|
+
|
|
39
|
+
return ['text', 'email', 'number', 'tel', 'url'].includes(fieldType) ? fieldType : 'text';
|
|
40
|
+
});
|
|
41
|
+
const checked = computed(() => {
|
|
42
|
+
if (type !== 'checkbox') {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return !!value.value;
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
function update() {
|
|
50
|
+
if (!$input.value) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
input.update(getValue());
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function getValue(): FormFieldValue | null {
|
|
58
|
+
if (!$input.value) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
switch (type) {
|
|
63
|
+
case 'checkbox':
|
|
64
|
+
return $input.value.checked;
|
|
65
|
+
case 'date':
|
|
66
|
+
return $input.value.valueAsDate;
|
|
67
|
+
default:
|
|
68
|
+
return $input.value.value;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
onFormFocus(input, () => $input.value?.focus());
|
|
73
|
+
watchEffect(() => {
|
|
74
|
+
if (!$input.value) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (type === 'date' && value.value instanceof Date) {
|
|
79
|
+
$input.value.valueAsDate = value.value;
|
|
80
|
+
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
$input.value.value = (value.value as string) ?? null;
|
|
85
|
+
});
|
|
86
|
+
</script>
|
|
@@ -9,14 +9,10 @@
|
|
|
9
9
|
<script setup lang="ts">
|
|
10
10
|
import { computed, useSlots } from 'vue';
|
|
11
11
|
|
|
12
|
-
import { injectReactiveOrFail } from '
|
|
12
|
+
import { injectReactiveOrFail } from '@aerogel/core/utils/vue';
|
|
13
|
+
import type { InputExpose } from '@aerogel/core/components/contracts/Input';
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const input = injectReactiveOrFail<IAGHeadlessInput>(
|
|
17
|
-
'input',
|
|
18
|
-
'<AGHeadlessInputLabel> must be a child of a <AGHeadlessInput>',
|
|
19
|
-
);
|
|
15
|
+
const input = injectReactiveOrFail<InputExpose>('input', '<HeadlessInputLabel> must be a child of a <HeadlessInput>');
|
|
20
16
|
const slots = useSlots();
|
|
21
17
|
const show = computed(() => !!(input.label || slots.default));
|
|
22
18
|
</script>
|
package/src/components/headless/{forms/AGHeadlessInputTextArea.vue → HeadlessInputTextArea.vue}
RENAMED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<textarea
|
|
3
3
|
:id="input.id"
|
|
4
|
-
ref="$
|
|
5
|
-
:name
|
|
6
|
-
:value
|
|
4
|
+
ref="$textAreaRef"
|
|
5
|
+
:name
|
|
6
|
+
:value
|
|
7
|
+
:required="input.required ?? undefined"
|
|
7
8
|
:aria-invalid="input.errors ? 'true' : 'false'"
|
|
8
9
|
:aria-describedby="
|
|
9
10
|
input.errors ? `${input.id}-error` : input.description ? `${input.id}-description` : undefined
|
|
@@ -13,17 +14,16 @@
|
|
|
13
14
|
</template>
|
|
14
15
|
|
|
15
16
|
<script setup lang="ts">
|
|
16
|
-
import { computed,
|
|
17
|
+
import { computed, useTemplateRef } from 'vue';
|
|
17
18
|
|
|
18
|
-
import {
|
|
19
|
-
import
|
|
19
|
+
import { onFormFocus } from '@aerogel/core/utils/composition/forms';
|
|
20
|
+
import { injectReactiveOrFail } from '@aerogel/core/utils/vue';
|
|
21
|
+
import type { InputExpose } from '@aerogel/core/components/contracts/Input';
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const $textArea = ref<HTMLTextAreaElement>();
|
|
24
|
-
const input = injectReactiveOrFail<IAGHeadlessInput>(
|
|
23
|
+
const $textArea = useTemplateRef('$textAreaRef');
|
|
24
|
+
const input = injectReactiveOrFail<InputExpose>(
|
|
25
25
|
'input',
|
|
26
|
-
'<
|
|
26
|
+
'<HeadlessInputTextArea> must be a child of a <HeadlessInput>',
|
|
27
27
|
);
|
|
28
28
|
const name = computed(() => input.name ?? undefined);
|
|
29
29
|
const value = computed(() => input.value as string);
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<DialogRoot :ref="forwardRef" open @update:open="persistent || close()">
|
|
3
|
+
<DialogPortal>
|
|
4
|
+
<slot :close="close" />
|
|
5
|
+
</DialogPortal>
|
|
6
|
+
</DialogRoot>
|
|
7
|
+
</template>
|
|
8
|
+
|
|
9
|
+
<script setup lang="ts" generic="T = void">
|
|
10
|
+
import { provide, ref } from 'vue';
|
|
11
|
+
import { DialogPortal, DialogRoot, useForwardExpose } from 'reka-ui';
|
|
12
|
+
import type { DialogContent } from 'reka-ui';
|
|
13
|
+
import type { Nullable } from '@noeldemartin/utils';
|
|
14
|
+
|
|
15
|
+
import Events from '@aerogel/core/services/Events';
|
|
16
|
+
import { useEvent } from '@aerogel/core/utils/composition/events';
|
|
17
|
+
import { injectReactiveOrFail } from '@aerogel/core/utils/vue';
|
|
18
|
+
import type { AcceptRefs } from '@aerogel/core/utils/vue';
|
|
19
|
+
import type { UIModalContext } from '@aerogel/core/ui/UI';
|
|
20
|
+
import type { ModalExpose, ModalProps, ModalSlots } from '@aerogel/core/components/contracts/Modal';
|
|
21
|
+
|
|
22
|
+
const $content = ref<Nullable<InstanceType<typeof DialogContent>>>(null);
|
|
23
|
+
const { modal } = injectReactiveOrFail<UIModalContext>(
|
|
24
|
+
'modal',
|
|
25
|
+
'could not obtain modal reference from <HeadlessModal>, ' +
|
|
26
|
+
'did you render this component manually? Show it using $ui.modal() instead',
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
defineProps<ModalProps>();
|
|
30
|
+
defineSlots<ModalSlots<T>>();
|
|
31
|
+
defineExpose<AcceptRefs<ModalExpose<T>>>({ close, $content });
|
|
32
|
+
|
|
33
|
+
const { forwardRef } = useForwardExpose();
|
|
34
|
+
const closed = ref(false);
|
|
35
|
+
|
|
36
|
+
provide('$modalContentRef', $content);
|
|
37
|
+
|
|
38
|
+
useEvent('close-modal', async ({ id, result }) => {
|
|
39
|
+
if (id !== modal.id) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
await close(result);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
async function close(result?: unknown) {
|
|
47
|
+
if (closed.value) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
await Events.emit('modal-will-close', { modal, result });
|
|
52
|
+
|
|
53
|
+
closed.value = true;
|
|
54
|
+
|
|
55
|
+
await Events.emit('modal-has-closed', { modal, result });
|
|
56
|
+
}
|
|
57
|
+
</script>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<DialogContent ref="$contentRef">
|
|
3
|
+
<slot />
|
|
4
|
+
|
|
5
|
+
<ModalContext v-if="childModal" :child-index="childIndex + 1" :modal="childModal" />
|
|
6
|
+
</DialogContent>
|
|
7
|
+
</template>
|
|
8
|
+
|
|
9
|
+
<script setup lang="ts">
|
|
10
|
+
import { computed, useTemplateRef, watchEffect } from 'vue';
|
|
11
|
+
import { DialogContent } from 'reka-ui';
|
|
12
|
+
import type { Ref } from 'vue';
|
|
13
|
+
|
|
14
|
+
import ModalContext from '@aerogel/core/components/ui/ModalContext.vue';
|
|
15
|
+
import UI from '@aerogel/core/ui/UI';
|
|
16
|
+
import { injectOrFail, injectReactiveOrFail } from '@aerogel/core/utils/vue';
|
|
17
|
+
import type { UIModalContext } from '@aerogel/core/ui/UI';
|
|
18
|
+
import type { ModalContentInstance } from '@aerogel/core/components/contracts/Modal';
|
|
19
|
+
|
|
20
|
+
const { childIndex = 0 } = injectReactiveOrFail<UIModalContext>(
|
|
21
|
+
'modal',
|
|
22
|
+
'could not obtain modal reference from <HeadlessModalContent>, ' +
|
|
23
|
+
'did you render this component manually? Show it using $ui.modal() instead',
|
|
24
|
+
);
|
|
25
|
+
const $modalContentRef = injectOrFail<Ref<ModalContentInstance>>('$modalContentRef');
|
|
26
|
+
const $content = useTemplateRef('$contentRef');
|
|
27
|
+
const childModal = computed(() => UI.modals[childIndex] ?? null);
|
|
28
|
+
|
|
29
|
+
watchEffect(() => ($modalContentRef.value = $content.value));
|
|
30
|
+
</script>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<DialogDescription v-bind="$props">
|
|
3
|
+
<slot />
|
|
4
|
+
</DialogDescription>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup lang="ts">
|
|
8
|
+
import { DialogDescription } from 'reka-ui';
|
|
9
|
+
import type { DialogDescriptionProps } from 'reka-ui';
|
|
10
|
+
|
|
11
|
+
defineProps<DialogDescriptionProps>();
|
|
12
|
+
</script>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<DialogOverlay v-bind="$props">
|
|
3
|
+
<slot />
|
|
4
|
+
</DialogOverlay>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup lang="ts">
|
|
8
|
+
import { DialogOverlay } from 'reka-ui';
|
|
9
|
+
import type { DialogOverlayProps } from 'reka-ui';
|
|
10
|
+
|
|
11
|
+
defineProps<DialogOverlayProps>();
|
|
12
|
+
</script>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<DialogTitle v-bind="$props">
|
|
3
|
+
<slot />
|
|
4
|
+
</DialogTitle>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup lang="ts">
|
|
8
|
+
import { DialogTitle } from 'reka-ui';
|
|
9
|
+
import type { DialogTitleProps } from 'reka-ui';
|
|
10
|
+
|
|
11
|
+
defineProps<DialogTitleProps>();
|
|
12
|
+
</script>
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<SelectRoot
|
|
3
|
+
v-slot="{ open }"
|
|
4
|
+
:model-value="acceptableValue"
|
|
5
|
+
:by="compareOptions as Closure<[AcceptableValue, AcceptableValue], boolean>"
|
|
6
|
+
@update:model-value="update($event)"
|
|
7
|
+
>
|
|
8
|
+
<component :is="as" v-bind="$attrs">
|
|
9
|
+
<slot :model-value :open>
|
|
10
|
+
<HeadlessSelectTrigger />
|
|
11
|
+
<HeadlessSelectOptions />
|
|
12
|
+
</slot>
|
|
13
|
+
</component>
|
|
14
|
+
</SelectRoot>
|
|
15
|
+
</template>
|
|
16
|
+
|
|
17
|
+
<script setup lang="ts" generic="T extends Nullable<FormFieldValue>">
|
|
18
|
+
import { computed, inject, provide, readonly } from 'vue';
|
|
19
|
+
import { value as evaluate, toString, uuid } from '@noeldemartin/utils';
|
|
20
|
+
import { SelectRoot } from 'reka-ui';
|
|
21
|
+
import type { AcceptableValue } from 'reka-ui';
|
|
22
|
+
import type { Closure, Nullable } from '@noeldemartin/utils';
|
|
23
|
+
|
|
24
|
+
import { translateWithDefault } from '@aerogel/core/lang';
|
|
25
|
+
import { hasSelectOptionLabel } from '@aerogel/core/components/contracts/Select';
|
|
26
|
+
import type FormController from '@aerogel/core/forms/FormController';
|
|
27
|
+
import type { SelectEmits, SelectExpose, SelectProps } from '@aerogel/core/components/contracts/Select';
|
|
28
|
+
import type { FormFieldValue } from '@aerogel/core/forms/FormController';
|
|
29
|
+
|
|
30
|
+
import HeadlessSelectTrigger from './HeadlessSelectTrigger.vue';
|
|
31
|
+
import HeadlessSelectOptions from './HeadlessSelectOptions.vue';
|
|
32
|
+
|
|
33
|
+
defineOptions({ inheritAttrs: false });
|
|
34
|
+
|
|
35
|
+
const {
|
|
36
|
+
name,
|
|
37
|
+
as = 'div',
|
|
38
|
+
label,
|
|
39
|
+
options,
|
|
40
|
+
labelClass,
|
|
41
|
+
optionsClass,
|
|
42
|
+
renderOption,
|
|
43
|
+
compareOptions = (a, b) => a === b,
|
|
44
|
+
description,
|
|
45
|
+
placeholder,
|
|
46
|
+
modelValue,
|
|
47
|
+
align,
|
|
48
|
+
side,
|
|
49
|
+
} = defineProps<SelectProps<T>>();
|
|
50
|
+
const emit = defineEmits<SelectEmits<T>>();
|
|
51
|
+
const form = inject<FormController | null>('form', null);
|
|
52
|
+
const computedValue = computed(() => {
|
|
53
|
+
if (form && name) {
|
|
54
|
+
return form.getFieldValue(name) as T;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return modelValue as T;
|
|
58
|
+
});
|
|
59
|
+
const acceptableValue = computed(() => computedValue.value as AcceptableValue);
|
|
60
|
+
const errors = computed(() => {
|
|
61
|
+
if (!form || !name) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return form.errors[name] ?? null;
|
|
66
|
+
});
|
|
67
|
+
const computedOptions = computed(() => {
|
|
68
|
+
if (!options) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return options.map((option) => ({
|
|
73
|
+
key: uuid(),
|
|
74
|
+
label: renderOption
|
|
75
|
+
? renderOption(option)
|
|
76
|
+
: hasSelectOptionLabel(option)
|
|
77
|
+
? evaluate(option.label as string)
|
|
78
|
+
: toString(option),
|
|
79
|
+
value: option as AcceptableValue,
|
|
80
|
+
}));
|
|
81
|
+
});
|
|
82
|
+
const expose = {
|
|
83
|
+
labelClass,
|
|
84
|
+
optionsClass,
|
|
85
|
+
align,
|
|
86
|
+
side,
|
|
87
|
+
value: computedValue,
|
|
88
|
+
id: `select-${uuid()}`,
|
|
89
|
+
name: computed(() => name),
|
|
90
|
+
label: computed(() => label),
|
|
91
|
+
description: computed(() => description),
|
|
92
|
+
placeholder: computed(() => placeholder ?? translateWithDefault('ui.select', 'Select an option')),
|
|
93
|
+
options: computedOptions,
|
|
94
|
+
selectedOption: computed(() => computedOptions.value?.find((option) => option.value === modelValue)),
|
|
95
|
+
errors: readonly(errors),
|
|
96
|
+
required: computed(() => {
|
|
97
|
+
if (!name || !form) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return form.getFieldRules(name).includes('required');
|
|
102
|
+
}),
|
|
103
|
+
update(value) {
|
|
104
|
+
if (form && name) {
|
|
105
|
+
form.setFieldValue(name, value as FormFieldValue);
|
|
106
|
+
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
emit('update:modelValue', value);
|
|
111
|
+
},
|
|
112
|
+
} satisfies SelectExpose<T>;
|
|
113
|
+
|
|
114
|
+
function update(value: AcceptableValue) {
|
|
115
|
+
expose.update(value as T);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
provide('select', expose);
|
|
119
|
+
defineExpose(expose);
|
|
120
|
+
</script>
|
|
@@ -7,14 +7,13 @@
|
|
|
7
7
|
<script setup lang="ts">
|
|
8
8
|
import { computed } from 'vue';
|
|
9
9
|
|
|
10
|
-
import { injectReactiveOrFail } from '
|
|
11
|
-
import { translateWithDefault } from '
|
|
10
|
+
import { injectReactiveOrFail } from '@aerogel/core/utils/vue';
|
|
11
|
+
import { translateWithDefault } from '@aerogel/core/lang/utils';
|
|
12
|
+
import type { SelectExpose } from '@aerogel/core/components/contracts/Select';
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const select = injectReactiveOrFail<IAGHeadlessSelect>(
|
|
14
|
+
const select = injectReactiveOrFail<SelectExpose>(
|
|
16
15
|
'select',
|
|
17
|
-
'<
|
|
16
|
+
'<HeadlessSelectError> must be a child of a <HeadlessSelect>',
|
|
18
17
|
);
|
|
19
18
|
const errorMessage = computed(() => {
|
|
20
19
|
if (!select.errors) {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Label v-if="show" :for="select.id" v-bind="$props">
|
|
3
|
+
<slot>
|
|
4
|
+
{{ select.label }}
|
|
5
|
+
</slot>
|
|
6
|
+
</Label>
|
|
7
|
+
</template>
|
|
8
|
+
|
|
9
|
+
<script setup lang="ts">
|
|
10
|
+
import { computed, useSlots } from 'vue';
|
|
11
|
+
import { Label } from 'reka-ui';
|
|
12
|
+
import type { LabelProps } from 'reka-ui';
|
|
13
|
+
|
|
14
|
+
import { injectReactiveOrFail } from '@aerogel/core/utils/vue';
|
|
15
|
+
import type { SelectExpose } from '@aerogel/core/components/contracts/Select';
|
|
16
|
+
|
|
17
|
+
defineProps<Omit<LabelProps, 'for'>>();
|
|
18
|
+
|
|
19
|
+
const select = injectReactiveOrFail<SelectExpose>(
|
|
20
|
+
'select',
|
|
21
|
+
'<HeadlessSelectLabel> must be a child of a <HeadlessSelect>',
|
|
22
|
+
);
|
|
23
|
+
const slots = useSlots();
|
|
24
|
+
const show = computed(() => !!(select.label || slots.default));
|
|
25
|
+
</script>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<SelectItem v-bind="$props">
|
|
3
|
+
<SelectItemText>
|
|
4
|
+
<slot>
|
|
5
|
+
{{ renderedLabel }}
|
|
6
|
+
</slot>
|
|
7
|
+
</SelectItemText>
|
|
8
|
+
</SelectItem>
|
|
9
|
+
</template>
|
|
10
|
+
|
|
11
|
+
<script setup lang="ts">
|
|
12
|
+
import { computed } from 'vue';
|
|
13
|
+
import { SelectItem, SelectItemText } from 'reka-ui';
|
|
14
|
+
import { toString } from '@noeldemartin/utils';
|
|
15
|
+
import type { SelectItemProps } from 'reka-ui';
|
|
16
|
+
|
|
17
|
+
import { injectReactiveOrFail } from '@aerogel/core/utils/vue';
|
|
18
|
+
import type { SelectExpose } from '@aerogel/core/components/contracts/Select';
|
|
19
|
+
|
|
20
|
+
const { value } = defineProps<SelectItemProps>();
|
|
21
|
+
const select = injectReactiveOrFail<SelectExpose>(
|
|
22
|
+
'select',
|
|
23
|
+
'<HeadlessSelectOption> must be a child of a <HeadlessSelect>',
|
|
24
|
+
);
|
|
25
|
+
const renderedLabel = computed(() => {
|
|
26
|
+
const itemOption = select.options?.find((option) => option.value === value);
|
|
27
|
+
|
|
28
|
+
if (itemOption) {
|
|
29
|
+
return itemOption.label;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return toString(value);
|
|
33
|
+
});
|
|
34
|
+
</script>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<SelectPortal>
|
|
3
|
+
<SelectContent
|
|
4
|
+
position="popper"
|
|
5
|
+
:class="renderedClasses"
|
|
6
|
+
:align="select.align"
|
|
7
|
+
:side="select.side"
|
|
8
|
+
:side-offset="4"
|
|
9
|
+
>
|
|
10
|
+
<SelectViewport :class="innerClass">
|
|
11
|
+
<slot>
|
|
12
|
+
<HeadlessSelectOption
|
|
13
|
+
v-for="option in select.options ?? []"
|
|
14
|
+
:key="option.key"
|
|
15
|
+
:value="option.value"
|
|
16
|
+
/>
|
|
17
|
+
</slot>
|
|
18
|
+
</SelectViewport>
|
|
19
|
+
</SelectContent>
|
|
20
|
+
</SelectPortal>
|
|
21
|
+
</template>
|
|
22
|
+
|
|
23
|
+
<script setup lang="ts">
|
|
24
|
+
import { SelectContent, SelectPortal, SelectViewport } from 'reka-ui';
|
|
25
|
+
import { computed } from 'vue';
|
|
26
|
+
import type { HTMLAttributes } from 'vue';
|
|
27
|
+
|
|
28
|
+
import { injectReactiveOrFail } from '@aerogel/core/utils/vue';
|
|
29
|
+
import { classes } from '@aerogel/core/utils/classes';
|
|
30
|
+
import type { SelectExpose } from '@aerogel/core/components/contracts/Select';
|
|
31
|
+
|
|
32
|
+
import HeadlessSelectOption from './HeadlessSelectOption.vue';
|
|
33
|
+
|
|
34
|
+
const { class: rootClass } = defineProps<{ class?: HTMLAttributes['class']; innerClass?: HTMLAttributes['class'] }>();
|
|
35
|
+
|
|
36
|
+
const select = injectReactiveOrFail<SelectExpose>(
|
|
37
|
+
'select',
|
|
38
|
+
'<HeadlessSelectOptions> must be a child of a <HeadlessSelect>',
|
|
39
|
+
);
|
|
40
|
+
const renderedClasses = computed(() =>
|
|
41
|
+
classes('min-w-(--reka-select-trigger-width) max-h-(--reka-select-content-available-height)', rootClass));
|
|
42
|
+
</script>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<SelectTrigger :id="select.id">
|
|
3
|
+
<slot>
|
|
4
|
+
<HeadlessSelectValue :placeholder="select.placeholder" />
|
|
5
|
+
<SelectIcon />
|
|
6
|
+
</slot>
|
|
7
|
+
</SelectTrigger>
|
|
8
|
+
</template>
|
|
9
|
+
|
|
10
|
+
<script setup lang="ts">
|
|
11
|
+
import { SelectIcon, SelectTrigger } from 'reka-ui';
|
|
12
|
+
|
|
13
|
+
import { injectReactiveOrFail } from '@aerogel/core/utils';
|
|
14
|
+
import type { SelectExpose } from '@aerogel/core/components/contracts/Select';
|
|
15
|
+
|
|
16
|
+
import HeadlessSelectValue from './HeadlessSelectValue.vue';
|
|
17
|
+
|
|
18
|
+
const select = injectReactiveOrFail<SelectExpose>(
|
|
19
|
+
'select',
|
|
20
|
+
'<HeadlessSelectTrigger> must be a child of a <HeadlessSelect>',
|
|
21
|
+
);
|
|
22
|
+
</script>
|