@rotki/ui-library 3.0.0-beta.5 → 3.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/components/accordions/accordion/RuiAccordion.js.map +1 -1
- package/dist/components/accordions/accordion/RuiAccordion.vue_vue_type_script_setup_true_lang.js +13 -2
- package/dist/components/accordions/accordion/RuiAccordion.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/alerts/RuiAlert.vue.d.ts +2 -2
- package/dist/components/buttons/button/RuiButton.js.map +1 -1
- package/dist/components/buttons/button/RuiButton.vue.d.ts +7 -1
- package/dist/components/buttons/button/RuiButton.vue_vue_type_script_setup_true_lang.js +4 -2
- package/dist/components/buttons/button/RuiButton.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/buttons/button/button-props.d.ts +3 -0
- package/dist/components/buttons/button/button-props.js +2 -1
- package/dist/components/buttons/button/button-props.js.map +1 -1
- package/dist/components/buttons/button/button-styles.d.ts +75 -0
- package/dist/components/buttons/button/button-styles.js +135 -4
- package/dist/components/buttons/button/button-styles.js.map +1 -1
- package/dist/components/cards/RuiCard.js.map +1 -1
- package/dist/components/cards/RuiCard.vue.d.ts +6 -0
- package/dist/components/cards/RuiCard.vue_vue_type_script_setup_true_lang.js +14 -2
- package/dist/components/cards/RuiCard.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/chips/RuiChip.js.map +1 -1
- package/dist/components/chips/RuiChip.vue_vue_type_script_setup_true_lang.js +14 -0
- package/dist/components/chips/RuiChip.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/chips/chip-props.d.ts +2 -0
- package/dist/components/chips/chip-props.js +3 -0
- package/dist/components/chips/chip-props.js.map +1 -1
- package/dist/components/color-picker/RuiColorInput.js.map +1 -1
- package/dist/components/color-picker/RuiColorInput.vue_vue_type_script_setup_true_lang.js +2 -2
- package/dist/components/color-picker/RuiColorInput.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/date-time-picker/RuiDateTimePicker.js.map +1 -1
- package/dist/components/date-time-picker/RuiDateTimePicker.vue.d.ts +4 -0
- package/dist/components/date-time-picker/RuiDateTimePicker.vue_vue_type_script_setup_true_lang.js +6 -2
- package/dist/components/date-time-picker/RuiDateTimePicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/date-time-picker/RuiDateTimePickerMenu.js.map +1 -1
- package/dist/components/date-time-picker/RuiDateTimePickerMenu.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/date-time-picker/RuiDateTimePickerMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/date-time-picker/RuiTimezoneSelect.js.map +1 -1
- package/dist/components/date-time-picker/RuiTimezoneSelect.vue.d.ts +4 -0
- package/dist/components/date-time-picker/RuiTimezoneSelect.vue_vue_type_script_setup_true_lang.js +3 -2
- package/dist/components/date-time-picker/RuiTimezoneSelect.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/forms/auto-complete/RuiAutoComplete.js.map +1 -1
- package/dist/components/forms/auto-complete/RuiAutoComplete.vue_vue_type_script_setup_true_lang.js +6 -2
- package/dist/components/forms/auto-complete/RuiAutoComplete.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/forms/auto-complete/RuiAutoCompleteSelection.js.map +1 -1
- package/dist/components/forms/auto-complete/RuiAutoCompleteSelection.vue_vue_type_script_setup_true_lang.js +9 -6
- package/dist/components/forms/auto-complete/RuiAutoCompleteSelection.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/forms/auto-complete/auto-complete-props.d.ts +4 -0
- package/dist/components/forms/category-picker/RuiCategoryPicker.js.map +1 -1
- package/dist/components/forms/category-picker/RuiCategoryPicker.vue.d.ts +4 -1
- package/dist/components/forms/category-picker/RuiCategoryPicker.vue_vue_type_script_setup_true_lang.js +7 -4
- package/dist/components/forms/category-picker/RuiCategoryPicker.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/forms/check-control-styles.d.ts +27 -0
- package/dist/components/forms/check-control-styles.js +26 -1
- package/dist/components/forms/check-control-styles.js.map +1 -1
- package/dist/components/forms/checkbox/RuiCheckbox.js.map +1 -1
- package/dist/components/forms/checkbox/RuiCheckbox.vue.d.ts +6 -0
- package/dist/components/forms/checkbox/RuiCheckbox.vue_vue_type_script_setup_true_lang.js +14 -4
- package/dist/components/forms/checkbox/RuiCheckbox.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/forms/checkbox/checkbox-group/RuiCheckboxGroup.js.map +1 -1
- package/dist/components/forms/checkbox/checkbox-group/RuiCheckboxGroup.vue.d.ts +4 -0
- package/dist/components/forms/checkbox/checkbox-group/RuiCheckboxGroup.vue_vue_type_script_setup_true_lang.js +7 -2
- package/dist/components/forms/checkbox/checkbox-group/RuiCheckboxGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/forms/field-defaults/RuiFieldDefaults.js.map +1 -1
- package/dist/components/forms/field-defaults/RuiFieldDefaults.vue.d.ts +3 -0
- package/dist/components/forms/field-defaults/RuiFieldDefaults.vue_vue_type_script_setup_true_lang.js +8 -2
- package/dist/components/forms/field-defaults/RuiFieldDefaults.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/forms/file-upload/RuiFileUpload.js.map +1 -1
- package/dist/components/forms/file-upload/RuiFileUpload.vue.d.ts +4 -1
- package/dist/components/forms/file-upload/RuiFileUpload.vue_vue_type_script_setup_true_lang.js +10 -3
- package/dist/components/forms/file-upload/RuiFileUpload.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/forms/radio-button/radio/RuiRadio.js.map +1 -1
- package/dist/components/forms/radio-button/radio/RuiRadio.vue.d.ts +6 -0
- package/dist/components/forms/radio-button/radio/RuiRadio.vue_vue_type_script_setup_true_lang.js +14 -3
- package/dist/components/forms/radio-button/radio/RuiRadio.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/forms/radio-button/radio-group/RuiRadioGroup.js.map +1 -1
- package/dist/components/forms/radio-button/radio-group/RuiRadioGroup.vue.d.ts +4 -0
- package/dist/components/forms/radio-button/radio-group/RuiRadioGroup.vue_vue_type_script_setup_true_lang.js +7 -2
- package/dist/components/forms/radio-button/radio-group/RuiRadioGroup.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/forms/revealable-text-field/RuiRevealableTextField.js.map +1 -1
- package/dist/components/forms/revealable-text-field/RuiRevealableTextField.vue_vue_type_script_setup_true_lang.js +3 -2
- package/dist/components/forms/revealable-text-field/RuiRevealableTextField.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/forms/select/RuiMenuSelect.js.map +1 -1
- package/dist/components/forms/select/RuiMenuSelect.vue.d.ts +4 -0
- package/dist/components/forms/select/RuiMenuSelect.vue_vue_type_script_setup_true_lang.js +6 -2
- package/dist/components/forms/select/RuiMenuSelect.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/forms/slider/RuiSlider.js.map +1 -1
- package/dist/components/forms/slider/RuiSlider.vue.d.ts +4 -0
- package/dist/components/forms/slider/RuiSlider.vue_vue_type_script_setup_true_lang.js +6 -3
- package/dist/components/forms/slider/RuiSlider.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/forms/switch/RuiSwitch.js.map +1 -1
- package/dist/components/forms/switch/RuiSwitch.vue.d.ts +6 -0
- package/dist/components/forms/switch/RuiSwitch.vue_vue_type_script_setup_true_lang.js +25 -5
- package/dist/components/forms/switch/RuiSwitch.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/forms/text-area/RuiTextArea.js.map +1 -1
- package/dist/components/forms/text-area/RuiTextArea.vue.d.ts +4 -0
- package/dist/components/forms/text-area/RuiTextArea.vue_vue_type_script_setup_true_lang.js +6 -4
- package/dist/components/forms/text-area/RuiTextArea.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/forms/text-area/text-area-styles.d.ts +0 -1
- package/dist/components/forms/text-area/text-area-styles.js +0 -1
- package/dist/components/forms/text-area/text-area-styles.js.map +1 -1
- package/dist/components/forms/text-field/RuiTextField.js.map +1 -1
- package/dist/components/forms/text-field/RuiTextField.vue.d.ts +6 -2
- package/dist/components/forms/text-field/RuiTextField.vue_vue_type_script_setup_true_lang.js +6 -4
- package/dist/components/forms/text-field/RuiTextField.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/forms/text-field/text-field-styles.d.ts +0 -1
- package/dist/components/forms/text-field/text-field-styles.js +0 -1
- package/dist/components/forms/text-field/text-field-styles.js.map +1 -1
- package/dist/components/helpers/RuiFormTextDetail.js.map +1 -1
- package/dist/components/helpers/RuiFormTextDetail.vue.d.ts +14 -0
- package/dist/components/helpers/RuiFormTextDetail.vue_vue_type_script_setup_true_lang.js +41 -11
- package/dist/components/helpers/RuiFormTextDetail.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/loaders/RuiSkeletonBase.js.map +1 -1
- package/dist/components/loaders/RuiSkeletonBase.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/overlays/menu/RuiMenu.js.map +1 -1
- package/dist/components/overlays/menu/RuiMenu.vue.d.ts +3 -0
- package/dist/components/overlays/menu/RuiMenu.vue_vue_type_script_setup_true_lang.js +9 -1
- package/dist/components/overlays/menu/RuiMenu.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/tables/RuiDataTable.js.map +1 -1
- package/dist/components/tables/RuiDataTable.vue.d.ts +9 -0
- package/dist/components/tables/RuiDataTable.vue_vue_type_script_setup_true_lang.js +19 -7
- package/dist/components/tables/RuiDataTable.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/tables/RuiExpandButton.js.map +1 -1
- package/dist/components/tables/RuiExpandButton.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/tables/RuiTableHead.js.map +1 -1
- package/dist/components/tables/RuiTableHead.vue_vue_type_script_setup_true_lang.js +1 -1
- package/dist/components/tables/RuiTableHead.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/tables/RuiTablePagination.js.map +1 -1
- package/dist/components/tables/RuiTablePagination.vue_vue_type_script_setup_true_lang.js +3 -3
- package/dist/components/tables/RuiTablePagination.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/tables/data-table/RuiDataTableAppendRow.js.map +1 -1
- package/dist/components/tables/data-table/RuiDataTableAppendRow.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/tables/data-table/RuiDataTableBody.js.map +1 -1
- package/dist/components/tables/data-table/RuiDataTableBody.vue_vue_type_script_setup_true_lang.js +4 -3
- package/dist/components/tables/data-table/RuiDataTableBody.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/tables/data-table/RuiDataTableRow.js.map +1 -1
- package/dist/components/tables/data-table/RuiDataTableRow.vue_vue_type_script_setup_true_lang.js +4 -4
- package/dist/components/tables/data-table/RuiDataTableRow.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/tables/data-table-styles.d.ts +6 -0
- package/dist/components/tables/data-table-styles.js +15 -7
- package/dist/components/tables/data-table-styles.js.map +1 -1
- package/dist/components/tabs/tab-items/RuiTabItems.js.map +1 -1
- package/dist/components/tabs/tab-items/RuiTabItems.vue_vue_type_script_setup_true_lang.js +9 -2
- package/dist/components/tabs/tab-items/RuiTabItems.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/composables/clip-while-resizing.d.ts +15 -0
- package/dist/composables/clip-while-resizing.js +46 -0
- package/dist/composables/clip-while-resizing.js.map +1 -0
- package/dist/composables/defaults/field.d.ts +19 -3
- package/dist/composables/defaults/field.js +32 -6
- package/dist/composables/defaults/field.js.map +1 -1
- package/dist/composables/sticky-blocker.d.ts +20 -0
- package/dist/composables/sticky-blocker.js +62 -0
- package/dist/composables/sticky-blocker.js.map +1 -0
- package/dist/composables/tables/data-table/page-scroll.js +11 -1
- package/dist/composables/tables/data-table/page-scroll.js.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.js.map +1 -1
- package/dist/style.css +352 -11
- package/dist/utils/development.d.ts +6 -0
- package/dist/utils/development.js +17 -0
- package/dist/utils/development.js.map +1 -0
- package/dist/utils/form-text-detail.d.ts +15 -0
- package/dist/utils/form-text-detail.js +11 -1
- package/dist/utils/form-text-detail.js.map +1 -1
- package/dist/web-types.json +242 -3
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ContextColorsType } from '../../../consts/colors.js';
|
|
2
2
|
import type { RuiIcons } from '../../../icons/index.js';
|
|
3
3
|
import { type LabelPlacement } from '../../../composables/defaults/field.js';
|
|
4
|
+
import { type FormDetails } from '../../../utils/form-text-detail.js';
|
|
4
5
|
export interface TextFieldProps {
|
|
5
6
|
label?: string;
|
|
6
7
|
placeholder?: string;
|
|
@@ -13,6 +14,9 @@ export interface TextFieldProps {
|
|
|
13
14
|
hint?: string;
|
|
14
15
|
errorMessages?: string | string[];
|
|
15
16
|
successMessages?: string | string[];
|
|
17
|
+
/** The line under the field for its hint and messages: `reserve`, `auto` or `hidden`; falls back to the nearest `RuiFieldDefaults`, then the app default, then `reserve`. */
|
|
18
|
+
details?: FormDetails;
|
|
19
|
+
/** @deprecated Use `details="hidden"`. Removed in 4.0. */
|
|
16
20
|
hideDetails?: boolean;
|
|
17
21
|
prependIcon?: RuiIcons;
|
|
18
22
|
appendIcon?: RuiIcons;
|
|
@@ -39,14 +43,14 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
39
43
|
clear: () => any;
|
|
40
44
|
"update:modelValue": (value: string) => any;
|
|
41
45
|
blur: (event: Event) => any;
|
|
42
|
-
"focus-input": (event: Event) => any;
|
|
43
46
|
remove: (value: unknown) => any;
|
|
47
|
+
"focus-input": (event: Event) => any;
|
|
44
48
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
45
49
|
onClear?: (() => any) | undefined;
|
|
46
50
|
"onUpdate:modelValue"?: ((value: string) => any) | undefined;
|
|
47
51
|
onBlur?: ((event: Event) => any) | undefined;
|
|
48
|
-
"onFocus-input"?: ((event: Event) => any) | undefined;
|
|
49
52
|
onRemove?: ((value: unknown) => any) | undefined;
|
|
53
|
+
"onFocus-input"?: ((event: Event) => any) | undefined;
|
|
50
54
|
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
51
55
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
52
56
|
declare const _default: typeof __VLS_export;
|
package/dist/components/forms/text-field/RuiTextField.vue_vue_type_script_setup_true_lang.js
CHANGED
|
@@ -3,7 +3,7 @@ import { get, set } from "../../../vendor/@vueuse/shared/dist/index.js";
|
|
|
3
3
|
import { useFocus } from "../../../vendor/@vueuse/core/dist/index.js";
|
|
4
4
|
import RuiIcon_default from "../../icons/RuiIcon.js";
|
|
5
5
|
import RuiButton_default from "../../buttons/button/RuiButton.js";
|
|
6
|
-
import { useFormTextDetail } from "../../../utils/form-text-detail.js";
|
|
6
|
+
import { useFormDetails, useFormTextDetail } from "../../../utils/form-text-detail.js";
|
|
7
7
|
import RuiFormTextDetail_default from "../../helpers/RuiFormTextDetail.js";
|
|
8
8
|
import { useTimeoutManager } from "../../../composables/timeout-manager.js";
|
|
9
9
|
import RuiFieldLabel_default from "../field-label/RuiFieldLabel.js";
|
|
@@ -40,6 +40,7 @@ var RuiTextField_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ def
|
|
|
40
40
|
hint: { default: "" },
|
|
41
41
|
errorMessages: { default: () => [] },
|
|
42
42
|
successMessages: { default: () => [] },
|
|
43
|
+
details: { default: () => void 0 },
|
|
43
44
|
hideDetails: {
|
|
44
45
|
type: Boolean,
|
|
45
46
|
default: false
|
|
@@ -80,6 +81,7 @@ var RuiTextField_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ def
|
|
|
80
81
|
const placement = useLabelPlacement(() => __props.labelPlacement);
|
|
81
82
|
const { clear: cancelClearHide, create: delayClearHide } = useTimeoutManager();
|
|
82
83
|
const { hasError, hasSuccess, hasMessages, validation } = useFormTextDetail(() => __props.errorMessages, () => __props.successMessages);
|
|
84
|
+
const detailsMode = useFormDetails(() => __props.details, () => __props.hideDetails);
|
|
83
85
|
const inputId = computed(() => {
|
|
84
86
|
const id = attrs.id;
|
|
85
87
|
return typeof id === "string" && id ? id : generatedId;
|
|
@@ -201,9 +203,9 @@ var RuiTextField_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ def
|
|
|
201
203
|
size: 18
|
|
202
204
|
}, null, 8, ["name"])], 2)) : createCommentVNode("", true)], 2)) : createCommentVNode("", true)
|
|
203
205
|
], 34),
|
|
204
|
-
|
|
206
|
+
unref(detailsMode) !== "hidden" ? (openBlock(), createBlock(RuiFormTextDetail_default, {
|
|
205
207
|
key: 1,
|
|
206
|
-
|
|
208
|
+
details: unref(detailsMode),
|
|
207
209
|
"error-messages": __props.errorMessages,
|
|
208
210
|
"success-messages": __props.successMessages,
|
|
209
211
|
hint: __props.hint
|
|
@@ -212,7 +214,7 @@ var RuiTextField_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ def
|
|
|
212
214
|
fn: withCtx(() => [renderSlot(_ctx.$slots, "hint")]),
|
|
213
215
|
key: "0"
|
|
214
216
|
} : void 0]), 1032, [
|
|
215
|
-
"
|
|
217
|
+
"details",
|
|
216
218
|
"error-messages",
|
|
217
219
|
"success-messages",
|
|
218
220
|
"hint"
|
package/dist/components/forms/text-field/RuiTextField.vue_vue_type_script_setup_true_lang.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RuiTextField.vue_vue_type_script_setup_true_lang.js","names":["$attrs","$slots","_useModel","_openBlock","_createElementBlock","_normalizeProps","_guardReactiveProps","_unref","_createBlock","RuiFieldLabel","_createCommentVNode","_createElementVNode","_normalizeClass","_renderSlot","_createVNode","RuiIcon","_mergeProps","RuiButton","_withModifiers","_withCtx","RuiFormTextDetail","_createSlots"],"sources":["../../../../src/components/forms/text-field/RuiTextField.vue"],"sourcesContent":["<script lang=\"ts\" setup>\nimport type { ContextColorsType } from '@/consts/colors';\nimport type { RuiIcons } from '@/icons';\nimport RuiButton from '@/components/buttons/button/RuiButton.vue';\nimport RuiFieldLabel from '@/components/forms/field-label/RuiFieldLabel.vue';\nimport { textFieldStyles } from '@/components/forms/text-field/text-field-styles';\nimport RuiFormTextDetail from '@/components/helpers/RuiFormTextDetail.vue';\nimport RuiIcon from '@/components/icons/RuiIcon.vue';\nimport { type LabelPlacement, useLabelPlacement } from '@/composables/defaults/field';\nimport { useTimeoutManager } from '@/composables/timeout-manager';\nimport { useFormTextDetail } from '@/utils/form-text-detail';\nimport { getNonRootAttrs, getRootAttrs } from '@/utils/helpers';\n\nexport interface TextFieldProps {\n label?: string;\n placeholder?: string;\n disabled?: boolean;\n /** Where the label shows; falls back to the nearest `RuiFieldDefaults`, then the app default, then `top`. */\n labelPlacement?: LabelPlacement;\n color?: ContextColorsType;\n textColor?: ContextColorsType;\n dense?: boolean;\n hint?: string;\n errorMessages?: string | string[];\n successMessages?: string | string[];\n hideDetails?: boolean;\n prependIcon?: RuiIcons;\n appendIcon?: RuiIcons;\n readonly?: boolean;\n clearable?: boolean;\n required?: boolean;\n}\n\ndefineOptions({\n name: 'RuiTextField',\n inheritAttrs: false,\n});\n\nconst modelValue = defineModel<string>({ required: true });\n\nconst {\n label = '',\n placeholder = '',\n disabled = false,\n labelPlacement = undefined,\n color = undefined,\n textColor = undefined,\n dense = false,\n hint = '',\n errorMessages = [],\n successMessages = [],\n hideDetails = false,\n prependIcon = undefined,\n appendIcon = undefined,\n readonly = false,\n clearable = false,\n required = false,\n} = defineProps<TextFieldProps>();\n\nconst emit = defineEmits<{\n 'focus-input': [event: Event];\n 'blur': [event: Event];\n 'remove': [value: unknown];\n 'clear': [];\n}>();\n\ndefineSlots<{\n prepend?: () => any;\n append?: () => any;\n /** Rich hint content under the field, in place of the `hint` text; error and success messages still take precedence. */\n hint?: () => any;\n}>();\n\nconst isHovered = ref<boolean>(false);\nconst showClearButton = ref<boolean>(false);\n\nconst inputRef = useTemplateRef<HTMLInputElement>('inputRef');\n\nconst generatedId = useId();\nconst attrs = useAttrs();\n\nconst { focused } = useFocus(inputRef);\nconst placement = useLabelPlacement(() => labelPlacement);\nconst { clear: cancelClearHide, create: delayClearHide } = useTimeoutManager();\nconst { hasError, hasSuccess, hasMessages, validation } = useFormTextDetail(\n () => errorMessages,\n () => successMessages,\n);\n\nconst inputId = computed<string>(() => {\n const id = attrs.id;\n return typeof id === 'string' && id ? id : generatedId;\n});\n\nconst showClearIcon = computed<boolean>(() => clearable && !!get(modelValue) && !disabled && !readonly);\n\nconst effectiveTextColor = computed<ContextColorsType | undefined>(() => {\n if (get(hasError))\n return 'error';\n if (get(hasSuccess))\n return 'success';\n if (textColor && !get(hasMessages))\n return textColor;\n return undefined;\n});\n\nconst effectiveColor = computed<ContextColorsType | undefined>(() => get(validation) ?? color);\n\nconst ui = computed<ReturnType<typeof textFieldStyles>>(() => textFieldStyles({\n dense,\n disabled,\n readonly,\n hovered: get(isHovered),\n focused: get(focused),\n color: get(effectiveColor),\n textColor: get(effectiveTextColor),\n validation: get(validation),\n}));\n\nfunction input(event: Event): void {\n const target = event.target;\n if (target instanceof HTMLInputElement) {\n set(modelValue, target.value);\n }\n}\n\nfunction clearIconClicked(): void {\n set(modelValue, '');\n emit('clear');\n}\n\nwatch(focused, (value) => {\n if (value) {\n // a refocus within the delay keeps the button\n cancelClearHide();\n set(showClearButton, true);\n }\n else {\n delayClearHide(() => set(showClearButton, false), 500);\n }\n});\n\ndefineExpose({\n setSelectionRange: (start: number, end: number) => {\n get(inputRef)?.setSelectionRange(start, end);\n },\n focus: () => get(inputRef)?.focus(),\n element: inputRef,\n});\n</script>\n\n<template>\n <div v-bind=\"getRootAttrs($attrs)\">\n <RuiFieldLabel\n v-if=\"label\"\n :text=\"label\"\n :for=\"inputId\"\n :hidden=\"placement === 'hidden'\"\n :required=\"required\"\n :disabled=\"disabled\"\n />\n <div\n :class=\"ui.wrapper()\"\n data-id=\"wrapper\"\n @mouseenter=\"isHovered = true\"\n @mouseleave=\"isHovered = false\"\n >\n <div\n v-if=\"$slots.prepend || prependIcon\"\n :class=\"ui.prepend()\"\n data-id=\"prepend\"\n >\n <slot\n v-if=\"$slots.prepend\"\n name=\"prepend\"\n />\n <div\n v-else-if=\"prependIcon\"\n :class=\"ui.icon()\"\n >\n <RuiIcon\n :name=\"prependIcon\"\n :size=\"18\"\n />\n </div>\n </div>\n <div\n :class=\"ui.inputWrapper()\"\n @click=\"emit('focus-input', $event)\"\n >\n <input\n :id=\"inputId\"\n ref=\"inputRef\"\n :value=\"modelValue\"\n :placeholder=\"placeholder || ' '\"\n :class=\"ui.input()\"\n :disabled=\"disabled\"\n :readonly=\"readonly\"\n :aria-invalid=\"hasError\"\n v-bind=\"getNonRootAttrs($attrs)\"\n @input=\"input($event)\"\n @blur=\"emit('blur', $event)\"\n @remove=\"emit('remove', $event)\"\n />\n <fieldset :class=\"ui.fieldset()\" />\n </div>\n <div\n v-if=\"$slots.append || appendIcon || showClearIcon\"\n :class=\"ui.append()\"\n data-id=\"append\"\n >\n <RuiButton\n v-if=\"showClearIcon\"\n :class=\"[ui.clearButton(), { hidden: !showClearButton }]\"\n variant=\"text\"\n type=\"button\"\n icon\n data-id=\"clear-btn\"\n aria-label=\"Clear\"\n tabindex=\"-1\"\n @click.stop=\"clearIconClicked()\"\n >\n <RuiIcon\n name=\"lu-x\"\n size=\"16\"\n />\n </RuiButton>\n <slot\n v-if=\"$slots.append\"\n name=\"append\"\n />\n <div\n v-else-if=\"appendIcon\"\n :class=\"ui.icon()\"\n >\n <RuiIcon\n :name=\"appendIcon\"\n :size=\"18\"\n />\n </div>\n </div>\n </div>\n <RuiFormTextDetail\n v-if=\"!hideDetails\"\n :class=\"ui.details()\"\n :error-messages=\"errorMessages\"\n :success-messages=\"successMessages\"\n :hint=\"hint\"\n >\n <template\n v-if=\"$slots.hint\"\n #hint\n >\n <slot name=\"hint\" />\n </template>\n </RuiFormTextDetail>\n </div>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAsCA,MAAM,aAAaE,SAAmB,SAAA,YAAmB;EAqBzD,MAAM,OAAO;EAcb,MAAM,YAAY,IAAa,KAAK;EACpC,MAAM,kBAAkB,IAAa,KAAK;EAE1C,MAAM,WAAW,eAAiC,UAAU;EAE5D,MAAM,cAAc,MAAM;EAC1B,MAAM,QAAQ,SAAS;EAEvB,MAAM,EAAE,YAAY,SAAS,QAAQ;EACrC,MAAM,YAAY,wBAAwB,QAAA,cAAc;EACxD,MAAM,EAAE,OAAO,iBAAiB,QAAQ,mBAAmB,kBAAkB;EAC7E,MAAM,EAAE,UAAU,YAAY,aAAa,eAAe,wBAClD,QAAA,qBACA,QAAA,eACR;EAEA,MAAM,UAAU,eAAuB;GACrC,MAAM,KAAK,MAAM;GACjB,OAAO,OAAO,OAAO,YAAY,KAAK,KAAK;EAC7C,CAAC;EAED,MAAM,gBAAgB,eAAwB,QAAA,aAAa,CAAC,CAAC,IAAI,UAAU,KAAK,CAAC,QAAA,YAAY,CAAC,QAAA,QAAQ;EAEtG,MAAM,qBAAqB,eAA8C;GACvE,IAAI,IAAI,QAAQ,GACd,OAAO;GACT,IAAI,IAAI,UAAU,GAChB,OAAO;GACT,IAAI,QAAA,aAAa,CAAC,IAAI,WAAW,GAC/B,OAAO,QAAA;EAEX,CAAC;EAED,MAAM,iBAAiB,eAA8C,IAAI,UAAU,KAAK,QAAA,KAAK;EAE7F,MAAM,KAAK,eAAmD,gBAAgB;GAC5E,OAAI,QAAA;GACJ,UAAO,QAAA;GACP,UAAO,QAAA;GACP,SAAS,IAAI,SAAS;GACtB,SAAS,IAAI,OAAO;GACpB,OAAO,IAAI,cAAc;GACzB,WAAW,IAAI,kBAAkB;GACjC,YAAY,IAAI,UAAU;EAC5B,CAAC,CAAC;EAEF,SAAS,MAAM,OAAoB;GACjC,MAAM,SAAS,MAAM;GACrB,IAAI,kBAAkB,kBACpB,IAAI,YAAY,OAAO,KAAK;EAEhC;EAEA,SAAS,mBAAyB;GAChC,IAAI,YAAY,EAAE;GAClB,KAAK,OAAO;EACd;EAEA,MAAM,UAAU,UAAU;GACxB,IAAI,OAAO;IAET,gBAAgB;IAChB,IAAI,iBAAiB,IAAI;GAC3B,OAEE,qBAAqB,IAAI,iBAAiB,KAAK,GAAG,GAAG;EAEzD,CAAC;EAED,SAAa;GACX,oBAAoB,OAAe,QAAgB;IACjD,IAAI,QAAQ,CAAC,EAAE,kBAAkB,OAAO,GAAG;GAC7C;GACA,aAAa,IAAI,QAAQ,CAAC,EAAE,MAAM;GAClC,SAAS;EACX,CAAC;;GAIC,OAAAC,UAAA,GAAAC,mBAwGM,OAAAC,eAAAC,mBAxGOC,MAAA,YAAA,CAAY,CAACP,KAAAA,MAAM,CAAA,CAAA,GAAA;IAEtB,QAAA,SADRG,UAAA,GAAAK,YAOEC,uBAAA;;KALC,MAAM,QAAA;KACN,KAAKF,MAAA,OAAA;KACL,QAAQA,MAAA,SAAA,MAAS;KACjB,UAAU,QAAA;KACV,UAAU,QAAA;;;;;;;;IAEbI,mBAgFM,OAAA;KA/EH,OAAKC,eAAEL,MAAA,EAAA,CAAE,CAAC,QAAO,CAAA;KAClB,WAAQ;KACP,cAAU,OAAA,OAAA,OAAA,MAAA,WAAE,UAAA,QAAS;KACrB,cAAU,OAAA,OAAA,OAAA,MAAA,WAAE,UAAA,QAAS;;KAGdN,KAAAA,OAAO,WAAW,QAAA,eAD1BE,UAAA,GAAAC,mBAkBM,OAAA;;MAhBH,OAAKQ,eAAEL,MAAA,EAAA,CAAE,CAAC,QAAO,CAAA;MAClB,WAAQ;KAGAN,GAAAA,CAAAA,KAAAA,OAAO,UADfY,WAGE,KAAA,QAAA,WAAA,CAAA,GAAA,KAAA,GAAA,KAAA,GAAA,CAAA,IAEW,QAAA,eADbV,UAAA,GAAAC,mBAQM,OAAA;;MANH,OAAKQ,eAAEL,MAAA,EAAA,CAAE,CAAC,KAAI,CAAA;KAEfO,GAAAA,CAAAA,YAGEC,iBAAA;MAFC,MAAM,QAAA;MACN,MAAM;;KAIbJ,mBAmBM,OAAA;MAlBH,OAAKC,eAAEL,MAAA,EAAA,CAAE,CAAC,aAAY,CAAA;MACtB,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,KAAI,eAAgB,MAAM;KAElCI,GAAAA,CAAAA,mBAaE,SAbFK,WAaE;MAZC,IAAIT,MAAA,OAAA;MACD,SAAA;MAAJ,KAAI;MACH,OAAO,WAAA;MACP,aAAa,QAAA,eAAW;MACxB,OAAOA,MAAA,EAAA,CAAE,CAAC,MAAK;MACf,UAAU,QAAA;MACV,UAAU,QAAA;MACV,gBAAcA,MAAA,QAAA;KACP,GAAAA,MAAA,eAAA,CAAe,CAACP,KAAAA,MAAM,GAAA;MAC7B,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,MAAM,MAAM;MACnB,QAAI,OAAA,OAAA,OAAA,MAAA,WAAE,KAAI,QAAS,MAAM;MACzB,UAAM,OAAA,OAAA,OAAA,MAAA,WAAE,KAAI,UAAW,MAAM;KAEhCW,CAAAA,GAAAA,MAAAA,IAAAA,UAAAA,GAAAA,mBAAmC,YAAA,EAAxB,OAAKC,eAAEL,MAAA,EAAA,CAAE,CAAC,SAAQ,CAAA,EAAA,GAAA,MAAA,CAAA,CAAA,GAAA,CAAA;KAGvBN,KAAAA,OAAO,UAAU,QAAA,cAAcM,MAAA,aAAA,KADvCJ,UAAA,GAAAC,mBAkCM,OAAA;;MAhCH,OAAKQ,eAAEL,MAAA,EAAA,CAAE,CAAC,OAAM,CAAA;MACjB,WAAQ;KAGAA,GAAAA,CAAAA,MAAA,aAAA,KADRJ,UAAA,GAAAK,YAeYS,mBAAA;;MAbT,OAAKL,eAAA,CAAGL,MAAA,EAAA,CAAE,CAAC,YAAW,GAAA,EAAA,QAAA,CAAeA,MAAA,eAAA,EAAe,CAAA,CAAA;MACrD,SAAQ;MACR,MAAK;MACL,MAAA;MACA,WAAQ;MACR,cAAW;MACX,UAAS;MACR,SAAK,OAAA,OAAA,OAAA,KAAAW,eAAA,WAAO,iBAAgB,GAAA,CAAA,MAAA,CAAA;;MAE7B,SAAAC,cAGE,CAHFL,YAGEC,iBAAA;OAFA,MAAK;OACL,MAAK;;;KAIDd,GAAAA,GAAAA,CAAAA,OAAAA,CAAAA,KAAAA,mBAAAA,IAAAA,IAAAA,GAAAA,KAAAA,OAAO,SADfY,WAGE,KAAA,QAAA,UAAA,CAAA,GAAA,KAAA,GAAA,KAAA,GAAA,CAAA,IAEW,QAAA,cADbV,UAAA,GAAAC,mBAQM,OAAA;;MANH,OAAKQ,eAAEL,MAAA,EAAA,CAAE,CAAC,KAAI,CAAA;KAEfO,GAAAA,CAAAA,YAGEC,iBAAA;MAFC,MAAM,QAAA;MACN,MAAM;;;IAMN,CAAA,QAAA,eADTZ,UAAA,GAAAK,YAaoBY,2BAAA;;KAXjB,OAAKR,eAAEL,MAAA,EAAA,CAAE,CAAC,QAAO,CAAA;KACjB,kBAAgB,QAAA;KAChB,oBAAkB,QAAA;KAClB,MAAM,QAAA;IAGCN,GAAAA,YAAAA,EAAAA,GAAAA,EAAAA,GAAAA,CAAAA,KAAAA,OAAO,OAAA;KACZ,MAAA;KAED,IAAAkB,cAAoB,CAApBN,WAAoB,KAAA,QAAA,MAAA,CAAA,CAAA"}
|
|
1
|
+
{"version":3,"file":"RuiTextField.vue_vue_type_script_setup_true_lang.js","names":["$attrs","$slots","_useModel","_openBlock","_createElementBlock","_normalizeProps","_guardReactiveProps","_unref","_createBlock","RuiFieldLabel","_createCommentVNode","_createElementVNode","_normalizeClass","_renderSlot","_createVNode","RuiIcon","_mergeProps","RuiButton","_withModifiers","_withCtx","RuiFormTextDetail","_createSlots"],"sources":["../../../../src/components/forms/text-field/RuiTextField.vue"],"sourcesContent":["<script lang=\"ts\" setup>\nimport type { ContextColorsType } from '@/consts/colors';\nimport type { RuiIcons } from '@/icons';\nimport RuiButton from '@/components/buttons/button/RuiButton.vue';\nimport RuiFieldLabel from '@/components/forms/field-label/RuiFieldLabel.vue';\nimport { textFieldStyles } from '@/components/forms/text-field/text-field-styles';\nimport RuiFormTextDetail from '@/components/helpers/RuiFormTextDetail.vue';\nimport RuiIcon from '@/components/icons/RuiIcon.vue';\nimport { type LabelPlacement, useLabelPlacement } from '@/composables/defaults/field';\nimport { useTimeoutManager } from '@/composables/timeout-manager';\nimport { type FormDetails, useFormDetails, useFormTextDetail } from '@/utils/form-text-detail';\nimport { getNonRootAttrs, getRootAttrs } from '@/utils/helpers';\n\nexport interface TextFieldProps {\n label?: string;\n placeholder?: string;\n disabled?: boolean;\n /** Where the label shows; falls back to the nearest `RuiFieldDefaults`, then the app default, then `top`. */\n labelPlacement?: LabelPlacement;\n color?: ContextColorsType;\n textColor?: ContextColorsType;\n dense?: boolean;\n hint?: string;\n errorMessages?: string | string[];\n successMessages?: string | string[];\n /** The line under the field for its hint and messages: `reserve`, `auto` or `hidden`; falls back to the nearest `RuiFieldDefaults`, then the app default, then `reserve`. */\n details?: FormDetails;\n /** @deprecated Use `details=\"hidden\"`. Removed in 4.0. */\n hideDetails?: boolean;\n prependIcon?: RuiIcons;\n appendIcon?: RuiIcons;\n readonly?: boolean;\n clearable?: boolean;\n required?: boolean;\n}\n\ndefineOptions({\n name: 'RuiTextField',\n inheritAttrs: false,\n});\n\nconst modelValue = defineModel<string>({ required: true });\n\nconst {\n label = '',\n placeholder = '',\n disabled = false,\n labelPlacement = undefined,\n color = undefined,\n textColor = undefined,\n dense = false,\n hint = '',\n errorMessages = [],\n successMessages = [],\n details = undefined,\n hideDetails = false,\n prependIcon = undefined,\n appendIcon = undefined,\n readonly = false,\n clearable = false,\n required = false,\n} = defineProps<TextFieldProps>();\n\nconst emit = defineEmits<{\n 'focus-input': [event: Event];\n 'blur': [event: Event];\n 'remove': [value: unknown];\n 'clear': [];\n}>();\n\ndefineSlots<{\n prepend?: () => any;\n append?: () => any;\n /** Rich hint content under the field, in place of the `hint` text; error and success messages still take precedence. */\n hint?: () => any;\n}>();\n\nconst isHovered = ref<boolean>(false);\nconst showClearButton = ref<boolean>(false);\n\nconst inputRef = useTemplateRef<HTMLInputElement>('inputRef');\n\nconst generatedId = useId();\nconst attrs = useAttrs();\n\nconst { focused } = useFocus(inputRef);\nconst placement = useLabelPlacement(() => labelPlacement);\nconst { clear: cancelClearHide, create: delayClearHide } = useTimeoutManager();\nconst { hasError, hasSuccess, hasMessages, validation } = useFormTextDetail(\n () => errorMessages,\n () => successMessages,\n);\nconst detailsMode = useFormDetails(() => details, () => hideDetails);\n\nconst inputId = computed<string>(() => {\n const id = attrs.id;\n return typeof id === 'string' && id ? id : generatedId;\n});\n\nconst showClearIcon = computed<boolean>(() => clearable && !!get(modelValue) && !disabled && !readonly);\n\nconst effectiveTextColor = computed<ContextColorsType | undefined>(() => {\n if (get(hasError))\n return 'error';\n if (get(hasSuccess))\n return 'success';\n if (textColor && !get(hasMessages))\n return textColor;\n return undefined;\n});\n\nconst effectiveColor = computed<ContextColorsType | undefined>(() => get(validation) ?? color);\n\nconst ui = computed<ReturnType<typeof textFieldStyles>>(() => textFieldStyles({\n dense,\n disabled,\n readonly,\n hovered: get(isHovered),\n focused: get(focused),\n color: get(effectiveColor),\n textColor: get(effectiveTextColor),\n validation: get(validation),\n}));\n\nfunction input(event: Event): void {\n const target = event.target;\n if (target instanceof HTMLInputElement) {\n set(modelValue, target.value);\n }\n}\n\nfunction clearIconClicked(): void {\n set(modelValue, '');\n emit('clear');\n}\n\nwatch(focused, (value) => {\n if (value) {\n // a refocus within the delay keeps the button\n cancelClearHide();\n set(showClearButton, true);\n }\n else {\n delayClearHide(() => set(showClearButton, false), 500);\n }\n});\n\ndefineExpose({\n setSelectionRange: (start: number, end: number) => {\n get(inputRef)?.setSelectionRange(start, end);\n },\n focus: () => get(inputRef)?.focus(),\n element: inputRef,\n});\n</script>\n\n<template>\n <div v-bind=\"getRootAttrs($attrs)\">\n <RuiFieldLabel\n v-if=\"label\"\n :text=\"label\"\n :for=\"inputId\"\n :hidden=\"placement === 'hidden'\"\n :required=\"required\"\n :disabled=\"disabled\"\n />\n <div\n :class=\"ui.wrapper()\"\n data-id=\"wrapper\"\n @mouseenter=\"isHovered = true\"\n @mouseleave=\"isHovered = false\"\n >\n <div\n v-if=\"$slots.prepend || prependIcon\"\n :class=\"ui.prepend()\"\n data-id=\"prepend\"\n >\n <slot\n v-if=\"$slots.prepend\"\n name=\"prepend\"\n />\n <div\n v-else-if=\"prependIcon\"\n :class=\"ui.icon()\"\n >\n <RuiIcon\n :name=\"prependIcon\"\n :size=\"18\"\n />\n </div>\n </div>\n <div\n :class=\"ui.inputWrapper()\"\n @click=\"emit('focus-input', $event)\"\n >\n <input\n :id=\"inputId\"\n ref=\"inputRef\"\n :value=\"modelValue\"\n :placeholder=\"placeholder || ' '\"\n :class=\"ui.input()\"\n :disabled=\"disabled\"\n :readonly=\"readonly\"\n :aria-invalid=\"hasError\"\n v-bind=\"getNonRootAttrs($attrs)\"\n @input=\"input($event)\"\n @blur=\"emit('blur', $event)\"\n @remove=\"emit('remove', $event)\"\n />\n <fieldset :class=\"ui.fieldset()\" />\n </div>\n <div\n v-if=\"$slots.append || appendIcon || showClearIcon\"\n :class=\"ui.append()\"\n data-id=\"append\"\n >\n <RuiButton\n v-if=\"showClearIcon\"\n :class=\"[ui.clearButton(), { hidden: !showClearButton }]\"\n variant=\"text\"\n type=\"button\"\n icon\n data-id=\"clear-btn\"\n aria-label=\"Clear\"\n tabindex=\"-1\"\n @click.stop=\"clearIconClicked()\"\n >\n <RuiIcon\n name=\"lu-x\"\n size=\"16\"\n />\n </RuiButton>\n <slot\n v-if=\"$slots.append\"\n name=\"append\"\n />\n <div\n v-else-if=\"appendIcon\"\n :class=\"ui.icon()\"\n >\n <RuiIcon\n :name=\"appendIcon\"\n :size=\"18\"\n />\n </div>\n </div>\n </div>\n <RuiFormTextDetail\n v-if=\"detailsMode !== 'hidden'\"\n :details=\"detailsMode\"\n :error-messages=\"errorMessages\"\n :success-messages=\"successMessages\"\n :hint=\"hint\"\n >\n <template\n v-if=\"$slots.hint\"\n #hint\n >\n <slot name=\"hint\" />\n </template>\n </RuiFormTextDetail>\n </div>\n</template>\n"],"mappings":"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"}
|
|
@@ -20,7 +20,6 @@ var textFieldStyles = tv({
|
|
|
20
20
|
prepend: "flex items-center gap-1 shrink-0 pl-3",
|
|
21
21
|
append: "flex items-center gap-1 shrink-0 pr-3",
|
|
22
22
|
icon: "text-rui-neutral-500 dark:text-rui-neutral-400",
|
|
23
|
-
details: "pt-1",
|
|
24
23
|
clearButton: ["!p-1 last:-mr-1", "text-rui-neutral-500 dark:text-rui-neutral-400 hover:text-rui-text dark:hover:text-rui-text"].join(" ")
|
|
25
24
|
},
|
|
26
25
|
variants: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"text-field-styles.js","names":[],"sources":["../../../../src/components/forms/text-field/text-field-styles.ts"],"sourcesContent":["import { textInputBase } from '@/components/forms/text-input-styles';\nimport { tv } from '@/utils/tv';\n\n/**\n * tv() styles for RuiTextField: a 36px box (32px dense) with 14px text and the bordered `fieldset`\n * from textInputBase. The label sits above in RuiFieldLabel, so the placeholder is always shown.\n *\n * `text-sm/6` rather than `text-sm`: the bare class sets a 20px line, which makes a 32px box.\n *\n * IMPORTANT: tv() extend does NOT deduplicate conflicting Tailwind classes between base and\n * extension, so the fieldset's classes live only in textInputBase.\n */\nexport const textFieldStyles = tv({\n extend: textInputBase,\n slots: {\n // Re-declare the base slot for type inference (extend merges classes at runtime)\n fieldset: '',\n wrapper: 'relative w-full flex items-center rounded-rui-control bg-white dark:bg-transparent',\n input: [\n 'peer w-full bg-transparent px-3 py-1.5 text-sm/6 text-rui-text outline-hidden',\n 'placeholder:text-rui-neutral-500 dark:placeholder:text-rui-neutral-400',\n ].join(' '),\n inputWrapper: 'flex flex-1',\n // matching insets: a glyph sits 12px from either edge\n prepend: 'flex items-center gap-1 shrink-0 pl-3',\n append: 'flex items-center gap-1 shrink-0 pr-3',\n icon: 'text-rui-neutral-500 dark:text-rui-neutral-400',\n
|
|
1
|
+
{"version":3,"file":"text-field-styles.js","names":[],"sources":["../../../../src/components/forms/text-field/text-field-styles.ts"],"sourcesContent":["import { textInputBase } from '@/components/forms/text-input-styles';\nimport { tv } from '@/utils/tv';\n\n/**\n * tv() styles for RuiTextField: a 36px box (32px dense) with 14px text and the bordered `fieldset`\n * from textInputBase. The label sits above in RuiFieldLabel, so the placeholder is always shown.\n *\n * `text-sm/6` rather than `text-sm`: the bare class sets a 20px line, which makes a 32px box.\n *\n * IMPORTANT: tv() extend does NOT deduplicate conflicting Tailwind classes between base and\n * extension, so the fieldset's classes live only in textInputBase.\n */\nexport const textFieldStyles = tv({\n extend: textInputBase,\n slots: {\n // Re-declare the base slot for type inference (extend merges classes at runtime)\n fieldset: '',\n wrapper: 'relative w-full flex items-center rounded-rui-control bg-white dark:bg-transparent',\n input: [\n 'peer w-full bg-transparent px-3 py-1.5 text-sm/6 text-rui-text outline-hidden',\n 'placeholder:text-rui-neutral-500 dark:placeholder:text-rui-neutral-400',\n ].join(' '),\n inputWrapper: 'flex flex-1',\n // matching insets: a glyph sits 12px from either edge\n prepend: 'flex items-center gap-1 shrink-0 pl-3',\n append: 'flex items-center gap-1 shrink-0 pr-3',\n icon: 'text-rui-neutral-500 dark:text-rui-neutral-400',\n // 24px round a 16px glyph; as the last control it pulls into the inset by its own padding\n clearButton: [\n '!p-1 last:-mr-1',\n 'text-rui-neutral-500 dark:text-rui-neutral-400 hover:text-rui-text dark:hover:text-rui-text',\n ].join(' '),\n },\n variants: {\n dense: {\n true: { input: 'py-1' },\n },\n // Icons keep the neutral tone; the color goes to the text around them\n textColor: {\n primary: { prepend: 'text-rui-primary', append: 'text-rui-primary', input: 'text-rui-primary' },\n secondary: { prepend: 'text-rui-secondary', append: 'text-rui-secondary', input: 'text-rui-secondary' },\n error: { prepend: 'text-rui-error', append: 'text-rui-error', input: 'text-rui-error' },\n warning: { prepend: 'text-rui-warning', append: 'text-rui-warning', input: 'text-rui-warning' },\n info: { prepend: 'text-rui-info', append: 'text-rui-info', input: 'text-rui-info' },\n success: { prepend: 'text-rui-success', append: 'text-rui-success', input: 'text-rui-success' },\n },\n disabled: {\n true: { wrapper: 'bg-rui-neutral-50 dark:bg-rui-neutral-900' },\n },\n // the activators' read-only fill, restated for dark over the base `dark:bg-transparent`\n readonly: {\n true: { wrapper: 'bg-rui-surface-muted dark:bg-rui-surface-muted' },\n },\n },\n});\n"],"mappings":";;;;;;;;;;;;AAYA,IAAa,kBAAkB,GAAG;CAChC,QAAQ;CACR,OAAO;EAEL,UAAU;EACV,SAAS;EACT,OAAO,CACL,iFACA,wEACF,CAAC,CAAC,KAAK,GAAG;EACV,cAAc;EAEd,SAAS;EACT,QAAQ;EACR,MAAM;EAEN,aAAa,CACX,mBACA,6FACF,CAAC,CAAC,KAAK,GAAG;CACZ;CACA,UAAU;EACR,OAAO,EACL,MAAM,EAAE,OAAO,OAAO,EACxB;EAEA,WAAW;GACT,SAAS;IAAE,SAAS;IAAoB,QAAQ;IAAoB,OAAO;GAAmB;GAC9F,WAAW;IAAE,SAAS;IAAsB,QAAQ;IAAsB,OAAO;GAAqB;GACtG,OAAO;IAAE,SAAS;IAAkB,QAAQ;IAAkB,OAAO;GAAiB;GACtF,SAAS;IAAE,SAAS;IAAoB,QAAQ;IAAoB,OAAO;GAAmB;GAC9F,MAAM;IAAE,SAAS;IAAiB,QAAQ;IAAiB,OAAO;GAAgB;GAClF,SAAS;IAAE,SAAS;IAAoB,QAAQ;IAAoB,OAAO;GAAmB;EAChG;EACA,UAAU,EACR,MAAM,EAAE,SAAS,4CAA4C,EAC/D;EAEA,UAAU,EACR,MAAM,EAAE,SAAS,iDAAiD,EACpE;CACF;AACF,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RuiFormTextDetail.js","names":["_sfc_main"],"sources":["../../../src/components/helpers/RuiFormTextDetail.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { useFormTextDetail } from '@/utils/form-text-detail';\n\nconst {\n errorMessages = [],\n successMessages = [],\n hint = '',\n} = defineProps<{\n errorMessages?: string | string[];\n successMessages?: string | string[];\n hint?: string;\n}>();\n\ndefineSlots<{\n /** Rich hint content, shown in place of the `hint` text while there is no error or success message. */\n hint?: () => any;\n}>();\n\nconst { formattedErrorMessages, formattedSuccessMessages, hasError, hasSuccess } =\n useFormTextDetail(\n () => errorMessages,\n () => successMessages,\n );\n
|
|
1
|
+
{"version":3,"file":"RuiFormTextDetail.js","names":["_sfc_main"],"sources":["../../../src/components/helpers/RuiFormTextDetail.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { type FormDetails, useFormTextDetail } from '@/utils/form-text-detail';\n\n/**\n * The line under a control. It is 20px: 4px of padding on the message above a 16px caption line,\n * so an `auto` line with nothing to show takes no space. The TransitionGroup has no `appear`: a\n * hint present on first render is in place when the field shows, and only a change of message\n * animates. The template keeps no comments, since a root-level one makes the component a fragment\n * in development and drops the class a parent passes.\n *\n * An error is clamped to two lines, since a backend error can run to several sentences and push a\n * dialog's footer down; the full text goes in `title` only while it is cut off. Screen readers get\n * all of it either way. Hints and success messages are not clamped: they are written for the field.\n */\nconst {\n errorMessages = [],\n successMessages = [],\n hint = '',\n details = 'reserve',\n} = defineProps<{\n errorMessages?: string | string[];\n successMessages?: string | string[];\n hint?: string;\n /** `reserve` keeps the line while empty; `auto` takes no space until there is a message. */\n details?: Exclude<FormDetails, 'hidden'>;\n}>();\n\ndefineSlots<{\n /** Rich hint content, shown in place of the `hint` text while there is no error or success message. */\n hint?: () => any;\n}>();\n\nconst { formattedErrorMessages, formattedSuccessMessages, hasError, hasSuccess } =\n useFormTextDetail(\n () => errorMessages,\n () => successMessages,\n );\n\nconst errorEl = useTemplateRef<HTMLDivElement>('error');\n\nconst errorClamped = ref<boolean>(false);\n\nconst errorMessage = computed<string | undefined>(() => get(formattedErrorMessages)[0]);\n\nfunction checkClamped(): void {\n const el = get(errorEl);\n set(errorClamped, !!el && el.scrollHeight > el.clientHeight + 1);\n}\n\nuseResizeObserver(errorEl, checkClamped);\n\nwatch(errorMessage, async () => {\n await nextTick();\n checkClamped();\n}, { flush: 'post' });\n</script>\n\n<template>\n <TransitionGroup\n tag=\"div\"\n class=\"details\"\n :class=\"{ 'min-h-5': details === 'reserve' }\"\n :data-details=\"details\"\n enter-from-class=\"opacity-0 -translate-y-2 h-0\"\n enter-active-class=\"transform transition\"\n enter-to-class=\"opacity-100 translate-y-0\"\n leave-from-class=\"opacity-100 -translate-y-2\"\n leave-active-class=\"transform transition\"\n leave-to-class=\"opacity-0 -translate-y-2 h-0\"\n >\n <div\n v-if=\"hasError\"\n key=\"error\"\n ref=\"error\"\n class=\"pt-1 text-xs/4 text-rui-error dark:text-rui-error-lighter line-clamp-2\"\n :title=\"errorClamped ? errorMessage : undefined\"\n data-id=\"details-error\"\n >\n {{ errorMessage }}\n </div>\n <div\n v-else-if=\"hasSuccess\"\n key=\"success\"\n class=\"pt-1 text-xs/4 text-rui-success\"\n >\n {{ formattedSuccessMessages[0] }}\n </div>\n <div\n v-else-if=\"$slots.hint || hint\"\n key=\"hint\"\n class=\"pt-1 text-xs/4 text-rui-text-secondary\"\n >\n <slot name=\"hint\">\n {{ hint }}\n </slot>\n </div>\n </TransitionGroup>\n</template>\n"],"mappings":""}
|
|
@@ -1,7 +1,21 @@
|
|
|
1
|
+
import { type FormDetails } from '../../utils/form-text-detail.js';
|
|
2
|
+
/**
|
|
3
|
+
* The line under a control. It is 20px: 4px of padding on the message above a 16px caption line,
|
|
4
|
+
* so an `auto` line with nothing to show takes no space. The TransitionGroup has no `appear`: a
|
|
5
|
+
* hint present on first render is in place when the field shows, and only a change of message
|
|
6
|
+
* animates. The template keeps no comments, since a root-level one makes the component a fragment
|
|
7
|
+
* in development and drops the class a parent passes.
|
|
8
|
+
*
|
|
9
|
+
* An error is clamped to two lines, since a backend error can run to several sentences and push a
|
|
10
|
+
* dialog's footer down; the full text goes in `title` only while it is cut off. Screen readers get
|
|
11
|
+
* all of it either way. Hints and success messages are not clamped: they are written for the field.
|
|
12
|
+
*/
|
|
1
13
|
type __VLS_Props = {
|
|
2
14
|
errorMessages?: string | string[];
|
|
3
15
|
successMessages?: string | string[];
|
|
4
16
|
hint?: string;
|
|
17
|
+
/** `reserve` keeps the line while empty; `auto` takes no space until there is a message. */
|
|
18
|
+
details?: Exclude<FormDetails, 'hidden'>;
|
|
5
19
|
};
|
|
6
20
|
type __VLS_Slots = {
|
|
7
21
|
/** Rich hint content, shown in place of the `hint` text while there is no error or success message. */
|
|
@@ -1,31 +1,55 @@
|
|
|
1
|
+
import { get, set } from "../../vendor/@vueuse/shared/dist/index.js";
|
|
2
|
+
import { useResizeObserver } from "../../vendor/@vueuse/core/dist/index.js";
|
|
1
3
|
import { useFormTextDetail } from "../../utils/form-text-detail.js";
|
|
2
|
-
import { TransitionGroup, createBlock, createCommentVNode, createElementBlock, createTextVNode, defineComponent, openBlock, renderSlot, toDisplayString, unref, withCtx } from "vue";
|
|
4
|
+
import { TransitionGroup, computed, createBlock, createCommentVNode, createElementBlock, createTextVNode, defineComponent, nextTick, normalizeClass, openBlock, ref, renderSlot, toDisplayString, unref, useTemplateRef, watch, withCtx } from "vue";
|
|
3
5
|
//#region src/components/helpers/RuiFormTextDetail.vue?vue&type=script&setup=true&lang.ts
|
|
4
|
-
var _hoisted_1 =
|
|
5
|
-
key: "error",
|
|
6
|
-
class: "text-rui-error dark:text-rui-error-lighter text-caption"
|
|
7
|
-
};
|
|
6
|
+
var _hoisted_1 = ["title"];
|
|
8
7
|
var _hoisted_2 = {
|
|
9
8
|
key: "success",
|
|
10
|
-
class: "text-rui-success
|
|
9
|
+
class: "pt-1 text-xs/4 text-rui-success"
|
|
11
10
|
};
|
|
12
11
|
var _hoisted_3 = {
|
|
13
12
|
key: "hint",
|
|
14
|
-
class: "text-rui-text-secondary
|
|
13
|
+
class: "pt-1 text-xs/4 text-rui-text-secondary"
|
|
15
14
|
};
|
|
15
|
+
/**
|
|
16
|
+
* The line under a control. It is 20px: 4px of padding on the message above a 16px caption line,
|
|
17
|
+
* so an `auto` line with nothing to show takes no space. The TransitionGroup has no `appear`: a
|
|
18
|
+
* hint present on first render is in place when the field shows, and only a change of message
|
|
19
|
+
* animates. The template keeps no comments, since a root-level one makes the component a fragment
|
|
20
|
+
* in development and drops the class a parent passes.
|
|
21
|
+
*
|
|
22
|
+
* An error is clamped to two lines, since a backend error can run to several sentences and push a
|
|
23
|
+
* dialog's footer down; the full text goes in `title` only while it is cut off. Screen readers get
|
|
24
|
+
* all of it either way. Hints and success messages are not clamped: they are written for the field.
|
|
25
|
+
*/
|
|
16
26
|
var RuiFormTextDetail_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineComponent({
|
|
17
27
|
__name: "RuiFormTextDetail",
|
|
18
28
|
props: {
|
|
19
29
|
errorMessages: { default: () => [] },
|
|
20
30
|
successMessages: { default: () => [] },
|
|
21
|
-
hint: { default: "" }
|
|
31
|
+
hint: { default: "" },
|
|
32
|
+
details: { default: "reserve" }
|
|
22
33
|
},
|
|
23
34
|
setup(__props) {
|
|
24
35
|
const { formattedErrorMessages, formattedSuccessMessages, hasError, hasSuccess } = useFormTextDetail(() => __props.errorMessages, () => __props.successMessages);
|
|
36
|
+
const errorEl = useTemplateRef("error");
|
|
37
|
+
const errorClamped = ref(false);
|
|
38
|
+
const errorMessage = computed(() => get(formattedErrorMessages)[0]);
|
|
39
|
+
function checkClamped() {
|
|
40
|
+
const el = get(errorEl);
|
|
41
|
+
set(errorClamped, !!el && el.scrollHeight > el.clientHeight + 1);
|
|
42
|
+
}
|
|
43
|
+
useResizeObserver(errorEl, checkClamped);
|
|
44
|
+
watch(errorMessage, async () => {
|
|
45
|
+
await nextTick();
|
|
46
|
+
checkClamped();
|
|
47
|
+
}, { flush: "post" });
|
|
25
48
|
return (_ctx, _cache) => {
|
|
26
49
|
return openBlock(), createBlock(TransitionGroup, {
|
|
27
50
|
tag: "div",
|
|
28
|
-
class: "details min-h-
|
|
51
|
+
class: normalizeClass(["details", { "min-h-5": __props.details === "reserve" }]),
|
|
52
|
+
"data-details": __props.details,
|
|
29
53
|
"enter-from-class": "opacity-0 -translate-y-2 h-0",
|
|
30
54
|
"enter-active-class": "transform transition",
|
|
31
55
|
"enter-to-class": "opacity-100 translate-y-0",
|
|
@@ -33,9 +57,15 @@ var RuiFormTextDetail_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*
|
|
|
33
57
|
"leave-active-class": "transform transition",
|
|
34
58
|
"leave-to-class": "opacity-0 -translate-y-2 h-0"
|
|
35
59
|
}, {
|
|
36
|
-
default: withCtx(() => [unref(hasError) ? (openBlock(), createElementBlock("div",
|
|
60
|
+
default: withCtx(() => [unref(hasError) ? (openBlock(), createElementBlock("div", {
|
|
61
|
+
key: "error",
|
|
62
|
+
ref: "error",
|
|
63
|
+
class: "pt-1 text-xs/4 text-rui-error dark:text-rui-error-lighter line-clamp-2",
|
|
64
|
+
title: unref(errorClamped) ? unref(errorMessage) : void 0,
|
|
65
|
+
"data-id": "details-error"
|
|
66
|
+
}, toDisplayString(unref(errorMessage)), 9, _hoisted_1)) : unref(hasSuccess) ? (openBlock(), createElementBlock("div", _hoisted_2, toDisplayString(unref(formattedSuccessMessages)[0]), 1)) : _ctx.$slots.hint || __props.hint ? (openBlock(), createElementBlock("div", _hoisted_3, [renderSlot(_ctx.$slots, "hint", {}, () => [createTextVNode(toDisplayString(__props.hint), 1)])])) : createCommentVNode("", true)]),
|
|
37
67
|
_: 3
|
|
38
|
-
});
|
|
68
|
+
}, 8, ["class", "data-details"]);
|
|
39
69
|
};
|
|
40
70
|
}
|
|
41
71
|
});
|
package/dist/components/helpers/RuiFormTextDetail.vue_vue_type_script_setup_true_lang.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RuiFormTextDetail.vue_vue_type_script_setup_true_lang.js","names":["$slots","_openBlock","_createBlock","_TransitionGroup","_withCtx","_unref","_createElementBlock","_toDisplayString","_renderSlot","_createTextVNode","_createCommentVNode"],"sources":["../../../src/components/helpers/RuiFormTextDetail.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { useFormTextDetail } from '@/utils/form-text-detail';\n\nconst {\n errorMessages = [],\n successMessages = [],\n hint = '',\n} = defineProps<{\n errorMessages?: string | string[];\n successMessages?: string | string[];\n hint?: string;\n}>();\n\ndefineSlots<{\n /** Rich hint content, shown in place of the `hint` text while there is no error or success message. */\n hint?: () => any;\n}>();\n\nconst { formattedErrorMessages, formattedSuccessMessages, hasError, hasSuccess } =\n useFormTextDetail(\n () => errorMessages,\n () => successMessages,\n );\n
|
|
1
|
+
{"version":3,"file":"RuiFormTextDetail.vue_vue_type_script_setup_true_lang.js","names":["$slots","_openBlock","_createBlock","_TransitionGroup","_normalizeClass","_withCtx","_unref","_createElementBlock","_toDisplayString","_renderSlot","_createTextVNode","_createCommentVNode"],"sources":["../../../src/components/helpers/RuiFormTextDetail.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { type FormDetails, useFormTextDetail } from '@/utils/form-text-detail';\n\n/**\n * The line under a control. It is 20px: 4px of padding on the message above a 16px caption line,\n * so an `auto` line with nothing to show takes no space. The TransitionGroup has no `appear`: a\n * hint present on first render is in place when the field shows, and only a change of message\n * animates. The template keeps no comments, since a root-level one makes the component a fragment\n * in development and drops the class a parent passes.\n *\n * An error is clamped to two lines, since a backend error can run to several sentences and push a\n * dialog's footer down; the full text goes in `title` only while it is cut off. Screen readers get\n * all of it either way. Hints and success messages are not clamped: they are written for the field.\n */\nconst {\n errorMessages = [],\n successMessages = [],\n hint = '',\n details = 'reserve',\n} = defineProps<{\n errorMessages?: string | string[];\n successMessages?: string | string[];\n hint?: string;\n /** `reserve` keeps the line while empty; `auto` takes no space until there is a message. */\n details?: Exclude<FormDetails, 'hidden'>;\n}>();\n\ndefineSlots<{\n /** Rich hint content, shown in place of the `hint` text while there is no error or success message. */\n hint?: () => any;\n}>();\n\nconst { formattedErrorMessages, formattedSuccessMessages, hasError, hasSuccess } =\n useFormTextDetail(\n () => errorMessages,\n () => successMessages,\n );\n\nconst errorEl = useTemplateRef<HTMLDivElement>('error');\n\nconst errorClamped = ref<boolean>(false);\n\nconst errorMessage = computed<string | undefined>(() => get(formattedErrorMessages)[0]);\n\nfunction checkClamped(): void {\n const el = get(errorEl);\n set(errorClamped, !!el && el.scrollHeight > el.clientHeight + 1);\n}\n\nuseResizeObserver(errorEl, checkClamped);\n\nwatch(errorMessage, async () => {\n await nextTick();\n checkClamped();\n}, { flush: 'post' });\n</script>\n\n<template>\n <TransitionGroup\n tag=\"div\"\n class=\"details\"\n :class=\"{ 'min-h-5': details === 'reserve' }\"\n :data-details=\"details\"\n enter-from-class=\"opacity-0 -translate-y-2 h-0\"\n enter-active-class=\"transform transition\"\n enter-to-class=\"opacity-100 translate-y-0\"\n leave-from-class=\"opacity-100 -translate-y-2\"\n leave-active-class=\"transform transition\"\n leave-to-class=\"opacity-0 -translate-y-2 h-0\"\n >\n <div\n v-if=\"hasError\"\n key=\"error\"\n ref=\"error\"\n class=\"pt-1 text-xs/4 text-rui-error dark:text-rui-error-lighter line-clamp-2\"\n :title=\"errorClamped ? errorMessage : undefined\"\n data-id=\"details-error\"\n >\n {{ errorMessage }}\n </div>\n <div\n v-else-if=\"hasSuccess\"\n key=\"success\"\n class=\"pt-1 text-xs/4 text-rui-success\"\n >\n {{ formattedSuccessMessages[0] }}\n </div>\n <div\n v-else-if=\"$slots.hint || hint\"\n key=\"hint\"\n class=\"pt-1 text-xs/4 text-rui-text-secondary\"\n >\n <slot name=\"hint\">\n {{ hint }}\n </slot>\n </div>\n </TransitionGroup>\n</template>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAgCA,MAAM,EAAE,wBAAwB,0BAA0B,UAAU,eAClE,wBACQ,QAAA,qBACA,QAAA,eACR;EAEF,MAAM,UAAU,eAA+B,OAAO;EAEtD,MAAM,eAAe,IAAa,KAAK;EAEvC,MAAM,eAAe,eAAmC,IAAI,sBAAsB,CAAC,CAAC,EAAE;EAEtF,SAAS,eAAqB;GAC5B,MAAM,KAAK,IAAI,OAAO;GACtB,IAAI,cAAc,CAAC,CAAC,MAAM,GAAG,eAAe,GAAG,eAAe,CAAC;EACjE;EAEA,kBAAkB,SAAS,YAAY;EAEvC,MAAM,cAAc,YAAY;GAC9B,MAAM,SAAS;GACf,aAAa;EACf,GAAG,EAAE,OAAO,OAAO,CAAC;;GAIlB,OAAAC,UAAA,GAAAC,YAsCkBC,iBAAA;IArChB,KAAI;IACJ,OAAKC,eAAA,CAAC,WAAS,EAAA,WACM,QAAA,YAAO,UAAA,CAAA,CAAA;IAC3B,gBAAc,QAAA;IACf,oBAAiB;IACjB,sBAAmB;IACnB,kBAAe;IACf,oBAAiB;IACjB,sBAAmB;IACnB,kBAAe;;IAEf,SAAAC,cASM,CAREC,MAAA,QAAA,KADRL,UAAA,GAAAM,mBASM,OAAA;KAPJ,KAAI;KACJ,KAAI;KACJ,OAAM;KACL,OAAOD,MAAA,YAAA,IAAeA,MAAA,YAAA,IAAe,KAAA;KACtC,WAAQ;IAEL,GAAAE,gBAAAF,MAAA,YAAA,CAAY,GAAA,GAAA,UAAA,KAGJA,MAAA,UAAA,KADbL,UAAA,GAAAM,mBAMM,OANN,YAMMC,gBADDF,MAAA,wBAAA,CAAwB,CAAA,EAAA,GAAA,CAAA,KAGhBN,KAAAA,OAAO,QAAQ,QAAA,QAD5BC,UAAA,GAAAM,mBAQM,OARN,YAQM,CAHJE,WAEO,KAAA,QAAA,QAAA,CAAA,SAAA,CADFC,gBAAAF,gBAAA,QAAA,IAAI,GAAA,CAAA,CAAA,CAAA,CAAA,CAAA,KAAA,mBAAA,IAAA,IAAA,CAAA,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RuiSkeletonBase.js","names":["_sfc_main"],"sources":["../../../src/components/loaders/RuiSkeletonBase.vue"],"sourcesContent":["<script lang=\"ts\" setup>\nimport { tv } from '@/utils/tv';\n\nexport interface Props {\n rounded?: 'none' | 'sm' | 'md' | 'lg' | 'full';\n}\n\ndefineOptions({\n name: 'RuiSkeletonLoaderBase',\n});\n\nconst { rounded } = defineProps<Props>();\n\nconst skeleton = tv({\n // zinc steps rather than translucent black and white, which in dark came out lighter than any surface\n base: 'animate-pulse bg-rui-neutral-200 dark:bg-rui-neutral-800',\n variants: {\n rounded: {\n none: 'rounded-none',\n sm: 'rounded-xs',\n md: 'rounded-rui-control',\n lg: 'rounded-rui-card',\n full: 'rounded-full',\n },\n },\n});\n\nconst ui = computed<string>(() => skeleton({ rounded }));\n</script>\n\n<template>\n
|
|
1
|
+
{"version":3,"file":"RuiSkeletonBase.js","names":["_sfc_main"],"sources":["../../../src/components/loaders/RuiSkeletonBase.vue"],"sourcesContent":["<script lang=\"ts\" setup>\nimport { tv } from '@/utils/tv';\n\nexport interface Props {\n rounded?: 'none' | 'sm' | 'md' | 'lg' | 'full';\n}\n\ndefineOptions({\n name: 'RuiSkeletonLoaderBase',\n});\n\nconst { rounded } = defineProps<Props>();\n\nconst skeleton = tv({\n // zinc steps rather than translucent black and white, which in dark came out lighter than any surface\n base: 'animate-pulse bg-rui-neutral-200 dark:bg-rui-neutral-800',\n variants: {\n rounded: {\n none: 'rounded-none',\n sm: 'rounded-xs',\n md: 'rounded-rui-control',\n lg: 'rounded-rui-card',\n full: 'rounded-full',\n },\n },\n});\n\n// the bar is decoration only, so it is aria-hidden: an alert role made a screen reader announce every bar\nconst ui = computed<string>(() => skeleton({ rounded }));\n</script>\n\n<template>\n <div\n :class=\"ui\"\n data-id=\"skeleton\"\n aria-hidden=\"true\"\n />\n</template>\n"],"mappings":""}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RuiSkeletonBase.vue_vue_type_script_setup_true_lang.js","names":["_openBlock","_createElementBlock","_normalizeClass","_unref"],"sources":["../../../src/components/loaders/RuiSkeletonBase.vue"],"sourcesContent":["<script lang=\"ts\" setup>\nimport { tv } from '@/utils/tv';\n\nexport interface Props {\n rounded?: 'none' | 'sm' | 'md' | 'lg' | 'full';\n}\n\ndefineOptions({\n name: 'RuiSkeletonLoaderBase',\n});\n\nconst { rounded } = defineProps<Props>();\n\nconst skeleton = tv({\n // zinc steps rather than translucent black and white, which in dark came out lighter than any surface\n base: 'animate-pulse bg-rui-neutral-200 dark:bg-rui-neutral-800',\n variants: {\n rounded: {\n none: 'rounded-none',\n sm: 'rounded-xs',\n md: 'rounded-rui-control',\n lg: 'rounded-rui-card',\n full: 'rounded-full',\n },\n },\n});\n\nconst ui = computed<string>(() => skeleton({ rounded }));\n</script>\n\n<template>\n
|
|
1
|
+
{"version":3,"file":"RuiSkeletonBase.vue_vue_type_script_setup_true_lang.js","names":["_openBlock","_createElementBlock","_normalizeClass","_unref"],"sources":["../../../src/components/loaders/RuiSkeletonBase.vue"],"sourcesContent":["<script lang=\"ts\" setup>\nimport { tv } from '@/utils/tv';\n\nexport interface Props {\n rounded?: 'none' | 'sm' | 'md' | 'lg' | 'full';\n}\n\ndefineOptions({\n name: 'RuiSkeletonLoaderBase',\n});\n\nconst { rounded } = defineProps<Props>();\n\nconst skeleton = tv({\n // zinc steps rather than translucent black and white, which in dark came out lighter than any surface\n base: 'animate-pulse bg-rui-neutral-200 dark:bg-rui-neutral-800',\n variants: {\n rounded: {\n none: 'rounded-none',\n sm: 'rounded-xs',\n md: 'rounded-rui-control',\n lg: 'rounded-rui-card',\n full: 'rounded-full',\n },\n },\n});\n\n// the bar is decoration only, so it is aria-hidden: an alert role made a screen reader announce every bar\nconst ui = computed<string>(() => skeleton({ rounded }));\n</script>\n\n<template>\n <div\n :class=\"ui\"\n data-id=\"skeleton\"\n aria-hidden=\"true\"\n />\n</template>\n"],"mappings":";;;;;;;;EAaA,MAAM,WAAW,GAAG;GAElB,MAAM;GACN,UAAU,EACR,SAAS;IACP,MAAM;IACN,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,MAAM;GACR,EACF;EACF,CAAC;EAGD,MAAM,KAAK,eAAuB,SAAS,EAAE,SAAM,QAAA,QAAE,CAAC,CAAC;;GAIrD,OAAAA,UAAA,GAAAC,mBAIE,OAAA;IAHC,OAAKC,eAAEC,MAAA,EAAA,CAAE;IACV,WAAQ;IACR,eAAY"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RuiMenu.js","names":["_sfc_main"],"sources":["../../../../src/components/overlays/menu/RuiMenu.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { VueClassValue } from '@/types/class-value';\nimport { ButtonGroupBoundary } from '@/components/buttons/button-group/button-group-context';\nimport RuiFormTextDetail from '@/components/helpers/RuiFormTextDetail.vue';\nimport { type FloatingOptions, useFloating } from '@/composables/floating';\nimport { useFormTextDetail } from '@/utils/form-text-detail';\nimport { cn, tv } from '@/utils/tv';\n\ninterface BaseMenuAttrs { onMouseover?: () => void; onMouseleave?: () => void }\n\n/**\n * What the activator slot binds onto its trigger. The ARIA keys belong on an\n * element that can carry them (a button, link or combobox), never on a plain\n * `div`; an activator that sets its own ARIA takes `activatorHandlers(attrs)`.\n */\ninterface MenuAttrs extends BaseMenuAttrs {\n 'onClick'?: () => void;\n 'aria-haspopup': RuiMenuRole | 'true';\n 'aria-expanded': boolean;\n 'aria-controls'?: string;\n}\n\nexport interface RuiMenuClassNames {\n root?: VueClassValue;\n wrapper?: VueClassValue;\n menu?: VueClassValue;\n /** Overrides applied to the popover content box (e.g. to drop its default padding). */\n content?: VueClassValue;\n /** Overrides applied to the hint and messages under the activator. */\n details?: VueClassValue;\n}\n\n/**\n * Roles a popover container may take. These are the values `aria-haspopup`\n * accepts, which keeps the activator and the popover in agreement.\n */\nexport type RuiMenuRole = 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog';\n\nexport interface MenuProps {\n openOnHover?: boolean;\n fullWidth?: boolean;\n disabled?: boolean;\n openDelay?: number;\n closeDelay?: number;\n options?: FloatingOptions;\n classNames?: RuiMenuClassNames;\n closeOnContentClick?: boolean;\n persistOnActivatorClick?: boolean;\n hint?: string;\n errorMessages?: string | string[];\n successMessages?: string | string[];\n showDetails?: boolean;\n dense?: boolean;\n /**\n * Keeps the menu open through the interactions that would otherwise dismiss\n * it: a click outside and an Escape press. A programmatic close (`v-model`\n * set to `false`) still applies.\n */\n persistent?: boolean;\n disableAutoFocus?: boolean;\n /**\n * Optional external element to anchor the menu to, instead of the\n * activator slot's wrapper. When set, the menu's position is computed\n * against this element.\n */\n anchorEl?: HTMLElement;\n /**\n * Role of the teleported popover. Defaults to `menu`, which is correct when\n * the content is made of `menuitem`s. Set it to `listbox` (or `tree`, `grid`,\n * `dialog`) when the content follows a different ARIA model, e.g. a selection\n * list opened by a `combobox`. The activator's `aria-haspopup` follows it.\n */\n role?: RuiMenuRole;\n /**\n * Accessible name of the teleported popover. A `dialog` popover needs one;\n * the other roles take it from their activator or options.\n */\n menuLabel?: string;\n}\n\ndefineOptions({\n name: 'RuiMenu',\n});\n\nconst modelValue = defineModel<boolean>({ default: false });\n\nconst {\n openOnHover = false,\n disabled = false,\n fullWidth = false,\n openDelay = 0,\n closeDelay = 0,\n options,\n classNames,\n closeOnContentClick = false,\n persistOnActivatorClick = false,\n hint,\n errorMessages = [],\n successMessages = [],\n showDetails = false,\n dense = false,\n persistent = false,\n disableAutoFocus = false,\n anchorEl,\n role = 'menu',\n menuLabel,\n} = defineProps<MenuProps>();\n\ndefineSlots<{\n activator?: (props: {\n // An inline type literal, so callers can pass it on as `Record<string, unknown>`\n attrs: {\n 'onMouseover'?: () => void;\n 'onMouseleave'?: () => void;\n 'onClick'?: () => void;\n 'aria-haspopup': RuiMenuRole | 'true';\n 'aria-expanded': boolean;\n 'aria-controls'?: string;\n };\n open: boolean;\n disabled: boolean;\n hasError: boolean;\n hasSuccess: boolean;\n }) => any;\n default?: (props: { width: number }) => any;\n /** Rendered above the activator, outside the element the menu positions against. */\n label?: () => any;\n}>();\n\nconst click = ref<boolean>(false);\nconst menuContent = useTemplateRef<HTMLElement>('menuContent');\nconst menuId = useId();\n\nconst FOCUSABLE_ELEMENTS_SELECTOR =\n 'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])';\n\nconst {\n reference: activator,\n popover: menu,\n open,\n visible,\n currentPlacement,\n leavePending,\n onLeavePending,\n onOpen,\n onClose,\n onLeaveComplete,\n updatePosition,\n} = useFloating(\n () => options ?? {},\n () => disabled,\n () => openDelay,\n () => closeDelay,\n () => anchorEl,\n);\n\nconst { width } = useElementSize(activator);\n\nconst { hasError, hasSuccess } = useFormTextDetail(\n () => errorMessages,\n () => successMessages,\n);\n\nconst menuStyles = tv({\n slots: {\n wrapper: 'relative inline-flex max-w-full',\n popover: 'w-max z-rui-menu',\n // a hairline edge keeps the popup apart from a surface of nearly the same shade in dark mode\n content: 'rounded-rui-panel overflow-hidden shadow-rui-menu bg-rui-menu border border-rui-divider text-rui-text focus:outline-hidden p-1',\n details: 'pt-1',\n },\n variants: {\n fullWidth: {\n true: { wrapper: 'w-full' },\n },\n dense: {\n true: { details: 'px-2' },\n false: { details: 'px-4' },\n },\n },\n defaultVariants: { fullWidth: false, dense: false },\n});\n\nconst ui = computed<ReturnType<typeof menuStyles>>(() => menuStyles({\n fullWidth,\n dense,\n}));\n\n// `aria-haspopup=\"true\"` means `\"menu\"`, and the default keeps emitting `true` for existing callers\nconst ariaHasPopup = computed<RuiMenuRole | 'true'>(() => role === 'menu' ? 'true' : role);\n\n/**\n * The activator attributes.\n *\n * This and the computed below it must return the same object shape from every\n * branch. Otherwise vue-tsc infers the slot's `attrs` type as a discriminated\n * union where each key is either all defined or all undefined, which breaks\n * consumer code typing its own `attrs` handler with individually optional\n * keys.\n */\nconst baseMenuAttrs = computed<BaseMenuAttrs>(() => {\n const clickVal = get(click);\n return {\n onMouseover: disabled\n ? undefined\n : () => {\n if (openOnHover)\n onOpen();\n },\n onMouseleave: disabled\n ? undefined\n : () => {\n if (openOnHover && !clickVal)\n onClose();\n },\n };\n});\n\nconst menuAttrs = computed<MenuAttrs>(() => ({\n ...get(baseMenuAttrs),\n 'onClick': disabled ? undefined : checkClick,\n 'aria-haspopup': get(ariaHasPopup),\n 'aria-expanded': get(open),\n // Only while the popover is in the DOM, so the reference never points at a missing element\n 'aria-controls': get(visible) ? menuId : undefined,\n}));\n\n/**\n * Moves focus into the open menu without scrolling the page. A menu taller than the space below its\n * activator would otherwise make the browser scroll the whole document to reveal the focused\n * container, sliding the page out from under the activator.\n */\nfunction focusOnContent() {\n const content = get(menuContent);\n if (!content)\n return;\n\n content.focus({ preventScroll: true });\n}\n\nfunction focusMenu(): void {\n if (disableAutoFocus)\n return;\n\n nextTick(() => focusOnContent());\n}\n\n/** Hands focus back to the activator on close, likewise without scrolling to it. */\nfunction focusOnActivator() {\n const activatorEl = get(activator);\n if (!activatorEl) {\n return;\n }\n const focusableEl = activatorEl.querySelector<HTMLElement>(FOCUSABLE_ELEMENTS_SELECTOR);\n if (focusableEl) {\n focusableEl.focus({ preventScroll: true });\n }\n}\n\nfunction onLeave(event?: KeyboardEvent): void {\n if (!get(open))\n return;\n onClose();\n set(click, false);\n event?.stopPropagation();\n\n // Return focus to activator when menu closes\n if (disableAutoFocus) {\n return;\n }\n\n nextTick(() => focusOnActivator());\n}\n\n/**\n * `persistent` blocks the outside interactions that would close the menu, and\n * Escape is one of them, which is how `RuiDialog` and `RuiBottomSheet` already\n * read it. The guard sits here rather than in `onLeave`, because `onLeave`\n * also runs for a programmatic close through `v-model`, which must keep\n * working.\n *\n * A persistent menu leaves the key alone instead of swallowing it, so an inner\n * popover can close on Escape while the menu stays open, and an ancestor still\n * sees a key nothing consumed.\n */\nfunction onEscape(event: KeyboardEvent): void {\n if (persistent)\n return;\n\n onLeave(event);\n}\n\nfunction checkClick(): void {\n if (get(open) && get(click)) {\n if (!persistOnActivatorClick)\n onLeave();\n }\n else {\n onOpen();\n set(click, true);\n }\n}\n\nwatch(modelValue, (value) => {\n if (get(open) === value)\n return;\n\n if (value) {\n onOpen();\n set(click, true);\n }\n else {\n onLeave();\n }\n});\n\nwatch(open, (open) => {\n set(modelValue, open);\n if (open) {\n focusMenu();\n }\n});\n\nonClickOutside(menu, () => {\n if (get(open) && !persistent)\n onLeave();\n}, { ignore: [activator] });\n</script>\n\n<template>\n <!--\n Escape is swallowed only while the menu is open and not persistent\n (`onLeave` stops the event itself); a closed or persistent menu must let it\n through, or a consumer that closes its own editor on Escape never sees the\n key.\n -->\n <div\n :class=\"classNames?.root\"\n @keydown.esc=\"onEscape($event)\"\n >\n <slot name=\"label\" />\n <div\n ref=\"activator\"\n :class=\"ui.wrapper({ class: cn(classNames?.wrapper) })\"\n :data-menu-disabled=\"disabled\"\n >\n <slot\n name=\"activator\"\n v-bind=\"{ attrs: menuAttrs, open, disabled, hasError, hasSuccess }\"\n />\n </div>\n <Teleport\n v-if=\"!disabled\"\n to=\"body\"\n >\n <div\n v-if=\"visible\"\n :id=\"menuId\"\n ref=\"menu\"\n :class=\"ui.popover({ class: cn(classNames?.menu) })\"\n :role=\"role\"\n :aria-label=\"menuLabel\"\n :data-placement=\"currentPlacement\"\n @click=\"closeOnContentClick ? onLeave() : undefined\"\n @keydown.esc=\"onEscape($event)\"\n >\n <TransitionGroup\n enter-active-class=\"transition ease-out duration-200\"\n enter-from-class=\"opacity-0 translate-y-1\"\n enter-to-class=\"opacity-100 translate-y-0\"\n leave-active-class=\"transition ease-in duration-150\"\n leave-from-class=\"opacity-100 translate-y-0\"\n leave-to-class=\"opacity-0 translate-y-1\"\n @before-enter=\"updatePosition()\"\n @after-leave=\"leavePending ? onLeaveComplete() : undefined\"\n @before-leave=\"onLeavePending()\"\n >\n <div\n v-if=\"open\"\n ref=\"menuContent\"\n key=\"menu\"\n data-id=\"content\"\n :class=\"ui.content({ class: cn(classNames?.content) })\"\n tabindex=\"-1\"\n v-bind=\"baseMenuAttrs\"\n >\n <ButtonGroupBoundary>\n <slot v-bind=\"{ width }\" />\n </ButtonGroupBoundary>\n </div>\n </TransitionGroup>\n </div>\n </Teleport>\n <RuiFormTextDetail\n v-if=\"showDetails\"\n :class=\"ui.details({ class: cn(classNames?.details) })\"\n :error-messages=\"errorMessages\"\n :success-messages=\"successMessages\"\n :hint=\"hint\"\n />\n </div>\n</template>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"RuiMenu.js","names":["_sfc_main"],"sources":["../../../../src/components/overlays/menu/RuiMenu.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { VueClassValue } from '@/types/class-value';\nimport { ButtonGroupBoundary } from '@/components/buttons/button-group/button-group-context';\nimport RuiFormTextDetail from '@/components/helpers/RuiFormTextDetail.vue';\nimport { type FloatingOptions, useFloating } from '@/composables/floating';\nimport { type FormDetails, useFormTextDetail } from '@/utils/form-text-detail';\nimport { cn, tv } from '@/utils/tv';\n\ninterface BaseMenuAttrs { onMouseover?: () => void; onMouseleave?: () => void }\n\n/**\n * What the activator slot binds onto its trigger. The ARIA keys belong on an\n * element that can carry them (a button, link or combobox), never on a plain\n * `div`; an activator that sets its own ARIA takes `activatorHandlers(attrs)`.\n */\ninterface MenuAttrs extends BaseMenuAttrs {\n 'onClick'?: () => void;\n 'aria-haspopup': RuiMenuRole | 'true';\n 'aria-expanded': boolean;\n 'aria-controls'?: string;\n}\n\nexport interface RuiMenuClassNames {\n root?: VueClassValue;\n wrapper?: VueClassValue;\n menu?: VueClassValue;\n /** Overrides applied to the popover content box (e.g. to drop its default padding). */\n content?: VueClassValue;\n /** Overrides applied to the hint and messages under the activator. */\n details?: VueClassValue;\n}\n\n/**\n * Roles a popover container may take. These are the values `aria-haspopup`\n * accepts, which keeps the activator and the popover in agreement.\n */\nexport type RuiMenuRole = 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog';\n\nexport interface MenuProps {\n openOnHover?: boolean;\n fullWidth?: boolean;\n disabled?: boolean;\n openDelay?: number;\n closeDelay?: number;\n options?: FloatingOptions;\n classNames?: RuiMenuClassNames;\n closeOnContentClick?: boolean;\n persistOnActivatorClick?: boolean;\n hint?: string;\n errorMessages?: string | string[];\n successMessages?: string | string[];\n showDetails?: boolean;\n /** With `showDetails`: `reserve` keeps the line while empty (default), `auto` takes no space until there is a message. */\n details?: Exclude<FormDetails, 'hidden'>;\n dense?: boolean;\n /**\n * Keeps the menu open through the interactions that would otherwise dismiss\n * it: a click outside and an Escape press. A programmatic close (`v-model`\n * set to `false`) still applies.\n */\n persistent?: boolean;\n disableAutoFocus?: boolean;\n /**\n * Optional external element to anchor the menu to, instead of the\n * activator slot's wrapper. When set, the menu's position is computed\n * against this element.\n */\n anchorEl?: HTMLElement;\n /**\n * Role of the teleported popover. Defaults to `menu`, which is correct when\n * the content is made of `menuitem`s. Set it to `listbox` (or `tree`, `grid`,\n * `dialog`) when the content follows a different ARIA model, e.g. a selection\n * list opened by a `combobox`. The activator's `aria-haspopup` follows it.\n */\n role?: RuiMenuRole;\n /**\n * Accessible name of the teleported popover. A `dialog` popover needs one;\n * the other roles take it from their activator or options.\n */\n menuLabel?: string;\n}\n\ndefineOptions({\n name: 'RuiMenu',\n});\n\nconst modelValue = defineModel<boolean>({ default: false });\n\nconst {\n openOnHover = false,\n disabled = false,\n fullWidth = false,\n openDelay = 0,\n closeDelay = 0,\n options,\n classNames,\n closeOnContentClick = false,\n persistOnActivatorClick = false,\n hint,\n errorMessages = [],\n successMessages = [],\n showDetails = false,\n details = 'reserve',\n dense = false,\n persistent = false,\n disableAutoFocus = false,\n anchorEl,\n role = 'menu',\n menuLabel,\n} = defineProps<MenuProps>();\n\ndefineSlots<{\n activator?: (props: {\n // An inline type literal, so callers can pass it on as `Record<string, unknown>`\n attrs: {\n 'onMouseover'?: () => void;\n 'onMouseleave'?: () => void;\n 'onClick'?: () => void;\n 'aria-haspopup': RuiMenuRole | 'true';\n 'aria-expanded': boolean;\n 'aria-controls'?: string;\n };\n open: boolean;\n disabled: boolean;\n hasError: boolean;\n hasSuccess: boolean;\n }) => any;\n default?: (props: { width: number }) => any;\n /** Rendered above the activator, outside the element the menu positions against. */\n label?: () => any;\n}>();\n\nconst click = ref<boolean>(false);\nconst menuContent = useTemplateRef<HTMLElement>('menuContent');\nconst menuId = useId();\n\nconst FOCUSABLE_ELEMENTS_SELECTOR =\n 'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])';\n\nconst {\n reference: activator,\n popover: menu,\n open,\n visible,\n currentPlacement,\n leavePending,\n onLeavePending,\n onOpen,\n onClose,\n onLeaveComplete,\n updatePosition,\n} = useFloating(\n () => options ?? {},\n () => disabled,\n () => openDelay,\n () => closeDelay,\n () => anchorEl,\n);\n\nconst { width } = useElementSize(activator);\n\nconst { hasError, hasSuccess } = useFormTextDetail(\n () => errorMessages,\n () => successMessages,\n);\n\nconst menuStyles = tv({\n slots: {\n wrapper: 'relative inline-flex max-w-full',\n popover: 'w-max z-rui-menu',\n // a hairline edge keeps the popup apart from a surface of nearly the same shade in dark mode\n content: 'rounded-rui-panel overflow-hidden shadow-rui-menu bg-rui-menu border border-rui-divider text-rui-text focus:outline-hidden p-1',\n details: '',\n },\n variants: {\n fullWidth: {\n true: { wrapper: 'w-full' },\n },\n dense: {\n true: { details: 'px-2' },\n false: { details: 'px-4' },\n },\n },\n defaultVariants: { fullWidth: false, dense: false },\n});\n\nconst ui = computed<ReturnType<typeof menuStyles>>(() => menuStyles({\n fullWidth,\n dense,\n}));\n\n// `aria-haspopup=\"true\"` means `\"menu\"`, and the default keeps emitting `true` for existing callers\nconst ariaHasPopup = computed<RuiMenuRole | 'true'>(() => role === 'menu' ? 'true' : role);\n\n/**\n * The activator attributes.\n *\n * This and the computed below it must return the same object shape from every\n * branch. Otherwise vue-tsc infers the slot's `attrs` type as a discriminated\n * union where each key is either all defined or all undefined, which breaks\n * consumer code typing its own `attrs` handler with individually optional\n * keys.\n */\nconst baseMenuAttrs = computed<BaseMenuAttrs>(() => {\n const clickVal = get(click);\n return {\n onMouseover: disabled\n ? undefined\n : () => {\n if (openOnHover)\n onOpen();\n },\n onMouseleave: disabled\n ? undefined\n : () => {\n if (openOnHover && !clickVal)\n onClose();\n },\n };\n});\n\nconst menuAttrs = computed<MenuAttrs>(() => ({\n ...get(baseMenuAttrs),\n 'onClick': disabled ? undefined : checkClick,\n 'aria-haspopup': get(ariaHasPopup),\n 'aria-expanded': get(open),\n // Only while the popover is in the DOM, so the reference never points at a missing element\n 'aria-controls': get(visible) ? menuId : undefined,\n}));\n\n/**\n * Moves focus into the open menu without scrolling the page. A menu taller than the space below its\n * activator would otherwise make the browser scroll the whole document to reveal the focused\n * container, sliding the page out from under the activator.\n */\nfunction focusOnContent() {\n const content = get(menuContent);\n if (!content)\n return;\n\n content.focus({ preventScroll: true });\n}\n\nfunction focusMenu(): void {\n if (disableAutoFocus)\n return;\n\n nextTick(() => focusOnContent());\n}\n\n/** Hands focus back to the activator on close, likewise without scrolling to it. */\nfunction focusOnActivator() {\n const activatorEl = get(activator);\n if (!activatorEl) {\n return;\n }\n const focusableEl = activatorEl.querySelector<HTMLElement>(FOCUSABLE_ELEMENTS_SELECTOR);\n if (focusableEl) {\n focusableEl.focus({ preventScroll: true });\n }\n}\n\nfunction onLeave(event?: KeyboardEvent): void {\n if (!get(open))\n return;\n onClose();\n set(click, false);\n event?.stopPropagation();\n\n // Return focus to activator when menu closes\n if (disableAutoFocus) {\n return;\n }\n\n nextTick(() => focusOnActivator());\n}\n\n/**\n * `persistent` blocks the outside interactions that would close the menu, and\n * Escape is one of them, which is how `RuiDialog` and `RuiBottomSheet` already\n * read it. The guard sits here rather than in `onLeave`, because `onLeave`\n * also runs for a programmatic close through `v-model`, which must keep\n * working.\n *\n * A persistent menu leaves the key alone instead of swallowing it, so an inner\n * popover can close on Escape while the menu stays open, and an ancestor still\n * sees a key nothing consumed.\n *\n * Escape is swallowed only while the menu is open and not persistent\n * (`onLeave` stops the event itself); a closed or persistent menu must let it\n * through, or a consumer that closes its own editor on Escape never sees the\n * key. The root element listens for it.\n */\nfunction onEscape(event: KeyboardEvent): void {\n if (persistent)\n return;\n\n onLeave(event);\n}\n\nfunction checkClick(): void {\n if (get(open) && get(click)) {\n if (!persistOnActivatorClick)\n onLeave();\n }\n else {\n onOpen();\n set(click, true);\n }\n}\n\nwatch(modelValue, (value) => {\n if (get(open) === value)\n return;\n\n if (value) {\n onOpen();\n set(click, true);\n }\n else {\n onLeave();\n }\n});\n\nwatch(open, (open) => {\n set(modelValue, open);\n if (open) {\n focusMenu();\n }\n});\n\nonClickOutside(menu, () => {\n if (get(open) && !persistent)\n onLeave();\n}, { ignore: [activator] });\n</script>\n\n<template>\n <div\n :class=\"classNames?.root\"\n @keydown.esc=\"onEscape($event)\"\n >\n <slot name=\"label\" />\n <div\n ref=\"activator\"\n :class=\"ui.wrapper({ class: cn(classNames?.wrapper) })\"\n :data-menu-disabled=\"disabled\"\n >\n <slot\n name=\"activator\"\n v-bind=\"{ attrs: menuAttrs, open, disabled, hasError, hasSuccess }\"\n />\n </div>\n <Teleport\n v-if=\"!disabled\"\n to=\"body\"\n >\n <div\n v-if=\"visible\"\n :id=\"menuId\"\n ref=\"menu\"\n :class=\"ui.popover({ class: cn(classNames?.menu) })\"\n :role=\"role\"\n :aria-label=\"menuLabel\"\n :data-placement=\"currentPlacement\"\n @click=\"closeOnContentClick ? onLeave() : undefined\"\n @keydown.esc=\"onEscape($event)\"\n >\n <TransitionGroup\n enter-active-class=\"transition ease-out duration-200\"\n enter-from-class=\"opacity-0 translate-y-1\"\n enter-to-class=\"opacity-100 translate-y-0\"\n leave-active-class=\"transition ease-in duration-150\"\n leave-from-class=\"opacity-100 translate-y-0\"\n leave-to-class=\"opacity-0 translate-y-1\"\n @before-enter=\"updatePosition()\"\n @after-leave=\"leavePending ? onLeaveComplete() : undefined\"\n @before-leave=\"onLeavePending()\"\n >\n <div\n v-if=\"open\"\n ref=\"menuContent\"\n key=\"menu\"\n data-id=\"content\"\n :class=\"ui.content({ class: cn(classNames?.content) })\"\n tabindex=\"-1\"\n v-bind=\"baseMenuAttrs\"\n >\n <ButtonGroupBoundary>\n <slot v-bind=\"{ width }\" />\n </ButtonGroupBoundary>\n </div>\n </TransitionGroup>\n </div>\n </Teleport>\n <RuiFormTextDetail\n v-if=\"showDetails\"\n :details=\"details\"\n :class=\"ui.details({ class: cn(classNames?.details) })\"\n :error-messages=\"errorMessages\"\n :success-messages=\"successMessages\"\n :hint=\"hint\"\n />\n </div>\n</template>\n"],"mappings":""}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { VueClassValue } from '../../../types/class-value.js';
|
|
2
2
|
import { type FloatingOptions } from '../../../composables/floating.js';
|
|
3
|
+
import { type FormDetails } from '../../../utils/form-text-detail.js';
|
|
3
4
|
export interface RuiMenuClassNames {
|
|
4
5
|
root?: VueClassValue;
|
|
5
6
|
wrapper?: VueClassValue;
|
|
@@ -28,6 +29,8 @@ export interface MenuProps {
|
|
|
28
29
|
errorMessages?: string | string[];
|
|
29
30
|
successMessages?: string | string[];
|
|
30
31
|
showDetails?: boolean;
|
|
32
|
+
/** With `showDetails`: `reserve` keeps the line while empty (default), `auto` takes no space until there is a message. */
|
|
33
|
+
details?: Exclude<FormDetails, 'hidden'>;
|
|
31
34
|
dense?: boolean;
|
|
32
35
|
/**
|
|
33
36
|
* Keeps the menu open through the interactions that would otherwise dismiss
|
|
@@ -50,6 +50,7 @@ var RuiMenu_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCo
|
|
|
50
50
|
type: Boolean,
|
|
51
51
|
default: false
|
|
52
52
|
},
|
|
53
|
+
details: { default: "reserve" },
|
|
53
54
|
dense: {
|
|
54
55
|
type: Boolean,
|
|
55
56
|
default: false
|
|
@@ -86,7 +87,7 @@ var RuiMenu_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCo
|
|
|
86
87
|
wrapper: "relative inline-flex max-w-full",
|
|
87
88
|
popover: "w-max z-rui-menu",
|
|
88
89
|
content: "rounded-rui-panel overflow-hidden shadow-rui-menu bg-rui-menu border border-rui-divider text-rui-text focus:outline-hidden p-1",
|
|
89
|
-
details: "
|
|
90
|
+
details: ""
|
|
90
91
|
},
|
|
91
92
|
variants: {
|
|
92
93
|
fullWidth: { true: { wrapper: "w-full" } },
|
|
@@ -171,6 +172,11 @@ var RuiMenu_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCo
|
|
|
171
172
|
* A persistent menu leaves the key alone instead of swallowing it, so an inner
|
|
172
173
|
* popover can close on Escape while the menu stays open, and an ancestor still
|
|
173
174
|
* sees a key nothing consumed.
|
|
175
|
+
*
|
|
176
|
+
* Escape is swallowed only while the menu is open and not persistent
|
|
177
|
+
* (`onLeave` stops the event itself); a closed or persistent menu must let it
|
|
178
|
+
* through, or a consumer that closes its own editor on Escape never sees the
|
|
179
|
+
* key. The root element listens for it.
|
|
174
180
|
*/
|
|
175
181
|
function onEscape(event) {
|
|
176
182
|
if (__props.persistent) return;
|
|
@@ -256,11 +262,13 @@ var RuiMenu_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCo
|
|
|
256
262
|
})], 42, _hoisted_2)) : createCommentVNode("", true)])) : createCommentVNode("", true),
|
|
257
263
|
__props.showDetails ? (openBlock(), createBlock(RuiFormTextDetail_default, {
|
|
258
264
|
key: 1,
|
|
265
|
+
details: __props.details,
|
|
259
266
|
class: normalizeClass(unref(ui).details({ class: unref(cn)(__props.classNames?.details) })),
|
|
260
267
|
"error-messages": __props.errorMessages,
|
|
261
268
|
"success-messages": __props.successMessages,
|
|
262
269
|
hint: __props.hint
|
|
263
270
|
}, null, 8, [
|
|
271
|
+
"details",
|
|
264
272
|
"class",
|
|
265
273
|
"error-messages",
|
|
266
274
|
"success-messages",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RuiMenu.vue_vue_type_script_setup_true_lang.js","names":["_useModel","_openBlock","_createElementBlock","_normalizeClass","_withKeys","_renderSlot","_createElementVNode","_unref","_normalizeProps","_guardReactiveProps","_createBlock","_Teleport","_createVNode","_TransitionGroup","_withCtx","_mergeProps","_createCommentVNode","RuiFormTextDetail"],"sources":["../../../../src/components/overlays/menu/RuiMenu.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { VueClassValue } from '@/types/class-value';\nimport { ButtonGroupBoundary } from '@/components/buttons/button-group/button-group-context';\nimport RuiFormTextDetail from '@/components/helpers/RuiFormTextDetail.vue';\nimport { type FloatingOptions, useFloating } from '@/composables/floating';\nimport { useFormTextDetail } from '@/utils/form-text-detail';\nimport { cn, tv } from '@/utils/tv';\n\ninterface BaseMenuAttrs { onMouseover?: () => void; onMouseleave?: () => void }\n\n/**\n * What the activator slot binds onto its trigger. The ARIA keys belong on an\n * element that can carry them (a button, link or combobox), never on a plain\n * `div`; an activator that sets its own ARIA takes `activatorHandlers(attrs)`.\n */\ninterface MenuAttrs extends BaseMenuAttrs {\n 'onClick'?: () => void;\n 'aria-haspopup': RuiMenuRole | 'true';\n 'aria-expanded': boolean;\n 'aria-controls'?: string;\n}\n\nexport interface RuiMenuClassNames {\n root?: VueClassValue;\n wrapper?: VueClassValue;\n menu?: VueClassValue;\n /** Overrides applied to the popover content box (e.g. to drop its default padding). */\n content?: VueClassValue;\n /** Overrides applied to the hint and messages under the activator. */\n details?: VueClassValue;\n}\n\n/**\n * Roles a popover container may take. These are the values `aria-haspopup`\n * accepts, which keeps the activator and the popover in agreement.\n */\nexport type RuiMenuRole = 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog';\n\nexport interface MenuProps {\n openOnHover?: boolean;\n fullWidth?: boolean;\n disabled?: boolean;\n openDelay?: number;\n closeDelay?: number;\n options?: FloatingOptions;\n classNames?: RuiMenuClassNames;\n closeOnContentClick?: boolean;\n persistOnActivatorClick?: boolean;\n hint?: string;\n errorMessages?: string | string[];\n successMessages?: string | string[];\n showDetails?: boolean;\n dense?: boolean;\n /**\n * Keeps the menu open through the interactions that would otherwise dismiss\n * it: a click outside and an Escape press. A programmatic close (`v-model`\n * set to `false`) still applies.\n */\n persistent?: boolean;\n disableAutoFocus?: boolean;\n /**\n * Optional external element to anchor the menu to, instead of the\n * activator slot's wrapper. When set, the menu's position is computed\n * against this element.\n */\n anchorEl?: HTMLElement;\n /**\n * Role of the teleported popover. Defaults to `menu`, which is correct when\n * the content is made of `menuitem`s. Set it to `listbox` (or `tree`, `grid`,\n * `dialog`) when the content follows a different ARIA model, e.g. a selection\n * list opened by a `combobox`. The activator's `aria-haspopup` follows it.\n */\n role?: RuiMenuRole;\n /**\n * Accessible name of the teleported popover. A `dialog` popover needs one;\n * the other roles take it from their activator or options.\n */\n menuLabel?: string;\n}\n\ndefineOptions({\n name: 'RuiMenu',\n});\n\nconst modelValue = defineModel<boolean>({ default: false });\n\nconst {\n openOnHover = false,\n disabled = false,\n fullWidth = false,\n openDelay = 0,\n closeDelay = 0,\n options,\n classNames,\n closeOnContentClick = false,\n persistOnActivatorClick = false,\n hint,\n errorMessages = [],\n successMessages = [],\n showDetails = false,\n dense = false,\n persistent = false,\n disableAutoFocus = false,\n anchorEl,\n role = 'menu',\n menuLabel,\n} = defineProps<MenuProps>();\n\ndefineSlots<{\n activator?: (props: {\n // An inline type literal, so callers can pass it on as `Record<string, unknown>`\n attrs: {\n 'onMouseover'?: () => void;\n 'onMouseleave'?: () => void;\n 'onClick'?: () => void;\n 'aria-haspopup': RuiMenuRole | 'true';\n 'aria-expanded': boolean;\n 'aria-controls'?: string;\n };\n open: boolean;\n disabled: boolean;\n hasError: boolean;\n hasSuccess: boolean;\n }) => any;\n default?: (props: { width: number }) => any;\n /** Rendered above the activator, outside the element the menu positions against. */\n label?: () => any;\n}>();\n\nconst click = ref<boolean>(false);\nconst menuContent = useTemplateRef<HTMLElement>('menuContent');\nconst menuId = useId();\n\nconst FOCUSABLE_ELEMENTS_SELECTOR =\n 'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])';\n\nconst {\n reference: activator,\n popover: menu,\n open,\n visible,\n currentPlacement,\n leavePending,\n onLeavePending,\n onOpen,\n onClose,\n onLeaveComplete,\n updatePosition,\n} = useFloating(\n () => options ?? {},\n () => disabled,\n () => openDelay,\n () => closeDelay,\n () => anchorEl,\n);\n\nconst { width } = useElementSize(activator);\n\nconst { hasError, hasSuccess } = useFormTextDetail(\n () => errorMessages,\n () => successMessages,\n);\n\nconst menuStyles = tv({\n slots: {\n wrapper: 'relative inline-flex max-w-full',\n popover: 'w-max z-rui-menu',\n // a hairline edge keeps the popup apart from a surface of nearly the same shade in dark mode\n content: 'rounded-rui-panel overflow-hidden shadow-rui-menu bg-rui-menu border border-rui-divider text-rui-text focus:outline-hidden p-1',\n details: 'pt-1',\n },\n variants: {\n fullWidth: {\n true: { wrapper: 'w-full' },\n },\n dense: {\n true: { details: 'px-2' },\n false: { details: 'px-4' },\n },\n },\n defaultVariants: { fullWidth: false, dense: false },\n});\n\nconst ui = computed<ReturnType<typeof menuStyles>>(() => menuStyles({\n fullWidth,\n dense,\n}));\n\n// `aria-haspopup=\"true\"` means `\"menu\"`, and the default keeps emitting `true` for existing callers\nconst ariaHasPopup = computed<RuiMenuRole | 'true'>(() => role === 'menu' ? 'true' : role);\n\n/**\n * The activator attributes.\n *\n * This and the computed below it must return the same object shape from every\n * branch. Otherwise vue-tsc infers the slot's `attrs` type as a discriminated\n * union where each key is either all defined or all undefined, which breaks\n * consumer code typing its own `attrs` handler with individually optional\n * keys.\n */\nconst baseMenuAttrs = computed<BaseMenuAttrs>(() => {\n const clickVal = get(click);\n return {\n onMouseover: disabled\n ? undefined\n : () => {\n if (openOnHover)\n onOpen();\n },\n onMouseleave: disabled\n ? undefined\n : () => {\n if (openOnHover && !clickVal)\n onClose();\n },\n };\n});\n\nconst menuAttrs = computed<MenuAttrs>(() => ({\n ...get(baseMenuAttrs),\n 'onClick': disabled ? undefined : checkClick,\n 'aria-haspopup': get(ariaHasPopup),\n 'aria-expanded': get(open),\n // Only while the popover is in the DOM, so the reference never points at a missing element\n 'aria-controls': get(visible) ? menuId : undefined,\n}));\n\n/**\n * Moves focus into the open menu without scrolling the page. A menu taller than the space below its\n * activator would otherwise make the browser scroll the whole document to reveal the focused\n * container, sliding the page out from under the activator.\n */\nfunction focusOnContent() {\n const content = get(menuContent);\n if (!content)\n return;\n\n content.focus({ preventScroll: true });\n}\n\nfunction focusMenu(): void {\n if (disableAutoFocus)\n return;\n\n nextTick(() => focusOnContent());\n}\n\n/** Hands focus back to the activator on close, likewise without scrolling to it. */\nfunction focusOnActivator() {\n const activatorEl = get(activator);\n if (!activatorEl) {\n return;\n }\n const focusableEl = activatorEl.querySelector<HTMLElement>(FOCUSABLE_ELEMENTS_SELECTOR);\n if (focusableEl) {\n focusableEl.focus({ preventScroll: true });\n }\n}\n\nfunction onLeave(event?: KeyboardEvent): void {\n if (!get(open))\n return;\n onClose();\n set(click, false);\n event?.stopPropagation();\n\n // Return focus to activator when menu closes\n if (disableAutoFocus) {\n return;\n }\n\n nextTick(() => focusOnActivator());\n}\n\n/**\n * `persistent` blocks the outside interactions that would close the menu, and\n * Escape is one of them, which is how `RuiDialog` and `RuiBottomSheet` already\n * read it. The guard sits here rather than in `onLeave`, because `onLeave`\n * also runs for a programmatic close through `v-model`, which must keep\n * working.\n *\n * A persistent menu leaves the key alone instead of swallowing it, so an inner\n * popover can close on Escape while the menu stays open, and an ancestor still\n * sees a key nothing consumed.\n */\nfunction onEscape(event: KeyboardEvent): void {\n if (persistent)\n return;\n\n onLeave(event);\n}\n\nfunction checkClick(): void {\n if (get(open) && get(click)) {\n if (!persistOnActivatorClick)\n onLeave();\n }\n else {\n onOpen();\n set(click, true);\n }\n}\n\nwatch(modelValue, (value) => {\n if (get(open) === value)\n return;\n\n if (value) {\n onOpen();\n set(click, true);\n }\n else {\n onLeave();\n }\n});\n\nwatch(open, (open) => {\n set(modelValue, open);\n if (open) {\n focusMenu();\n }\n});\n\nonClickOutside(menu, () => {\n if (get(open) && !persistent)\n onLeave();\n}, { ignore: [activator] });\n</script>\n\n<template>\n <!--\n Escape is swallowed only while the menu is open and not persistent\n (`onLeave` stops the event itself); a closed or persistent menu must let it\n through, or a consumer that closes its own editor on Escape never sees the\n key.\n -->\n <div\n :class=\"classNames?.root\"\n @keydown.esc=\"onEscape($event)\"\n >\n <slot name=\"label\" />\n <div\n ref=\"activator\"\n :class=\"ui.wrapper({ class: cn(classNames?.wrapper) })\"\n :data-menu-disabled=\"disabled\"\n >\n <slot\n name=\"activator\"\n v-bind=\"{ attrs: menuAttrs, open, disabled, hasError, hasSuccess }\"\n />\n </div>\n <Teleport\n v-if=\"!disabled\"\n to=\"body\"\n >\n <div\n v-if=\"visible\"\n :id=\"menuId\"\n ref=\"menu\"\n :class=\"ui.popover({ class: cn(classNames?.menu) })\"\n :role=\"role\"\n :aria-label=\"menuLabel\"\n :data-placement=\"currentPlacement\"\n @click=\"closeOnContentClick ? onLeave() : undefined\"\n @keydown.esc=\"onEscape($event)\"\n >\n <TransitionGroup\n enter-active-class=\"transition ease-out duration-200\"\n enter-from-class=\"opacity-0 translate-y-1\"\n enter-to-class=\"opacity-100 translate-y-0\"\n leave-active-class=\"transition ease-in duration-150\"\n leave-from-class=\"opacity-100 translate-y-0\"\n leave-to-class=\"opacity-0 translate-y-1\"\n @before-enter=\"updatePosition()\"\n @after-leave=\"leavePending ? onLeaveComplete() : undefined\"\n @before-leave=\"onLeavePending()\"\n >\n <div\n v-if=\"open\"\n ref=\"menuContent\"\n key=\"menu\"\n data-id=\"content\"\n :class=\"ui.content({ class: cn(classNames?.content) })\"\n tabindex=\"-1\"\n v-bind=\"baseMenuAttrs\"\n >\n <ButtonGroupBoundary>\n <slot v-bind=\"{ width }\" />\n </ButtonGroupBoundary>\n </div>\n </TransitionGroup>\n </div>\n </Teleport>\n <RuiFormTextDetail\n v-if=\"showDetails\"\n :class=\"ui.details({ class: cn(classNames?.details) })\"\n :error-messages=\"errorMessages\"\n :success-messages=\"successMessages\"\n :hint=\"hint\"\n />\n </div>\n</template>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"RuiMenu.vue_vue_type_script_setup_true_lang.js","names":["_useModel","_openBlock","_createElementBlock","_normalizeClass","_withKeys","_renderSlot","_createElementVNode","_unref","_normalizeProps","_guardReactiveProps","_createBlock","_Teleport","_createVNode","_TransitionGroup","_withCtx","_mergeProps","_createCommentVNode","RuiFormTextDetail"],"sources":["../../../../src/components/overlays/menu/RuiMenu.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { VueClassValue } from '@/types/class-value';\nimport { ButtonGroupBoundary } from '@/components/buttons/button-group/button-group-context';\nimport RuiFormTextDetail from '@/components/helpers/RuiFormTextDetail.vue';\nimport { type FloatingOptions, useFloating } from '@/composables/floating';\nimport { type FormDetails, useFormTextDetail } from '@/utils/form-text-detail';\nimport { cn, tv } from '@/utils/tv';\n\ninterface BaseMenuAttrs { onMouseover?: () => void; onMouseleave?: () => void }\n\n/**\n * What the activator slot binds onto its trigger. The ARIA keys belong on an\n * element that can carry them (a button, link or combobox), never on a plain\n * `div`; an activator that sets its own ARIA takes `activatorHandlers(attrs)`.\n */\ninterface MenuAttrs extends BaseMenuAttrs {\n 'onClick'?: () => void;\n 'aria-haspopup': RuiMenuRole | 'true';\n 'aria-expanded': boolean;\n 'aria-controls'?: string;\n}\n\nexport interface RuiMenuClassNames {\n root?: VueClassValue;\n wrapper?: VueClassValue;\n menu?: VueClassValue;\n /** Overrides applied to the popover content box (e.g. to drop its default padding). */\n content?: VueClassValue;\n /** Overrides applied to the hint and messages under the activator. */\n details?: VueClassValue;\n}\n\n/**\n * Roles a popover container may take. These are the values `aria-haspopup`\n * accepts, which keeps the activator and the popover in agreement.\n */\nexport type RuiMenuRole = 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog';\n\nexport interface MenuProps {\n openOnHover?: boolean;\n fullWidth?: boolean;\n disabled?: boolean;\n openDelay?: number;\n closeDelay?: number;\n options?: FloatingOptions;\n classNames?: RuiMenuClassNames;\n closeOnContentClick?: boolean;\n persistOnActivatorClick?: boolean;\n hint?: string;\n errorMessages?: string | string[];\n successMessages?: string | string[];\n showDetails?: boolean;\n /** With `showDetails`: `reserve` keeps the line while empty (default), `auto` takes no space until there is a message. */\n details?: Exclude<FormDetails, 'hidden'>;\n dense?: boolean;\n /**\n * Keeps the menu open through the interactions that would otherwise dismiss\n * it: a click outside and an Escape press. A programmatic close (`v-model`\n * set to `false`) still applies.\n */\n persistent?: boolean;\n disableAutoFocus?: boolean;\n /**\n * Optional external element to anchor the menu to, instead of the\n * activator slot's wrapper. When set, the menu's position is computed\n * against this element.\n */\n anchorEl?: HTMLElement;\n /**\n * Role of the teleported popover. Defaults to `menu`, which is correct when\n * the content is made of `menuitem`s. Set it to `listbox` (or `tree`, `grid`,\n * `dialog`) when the content follows a different ARIA model, e.g. a selection\n * list opened by a `combobox`. The activator's `aria-haspopup` follows it.\n */\n role?: RuiMenuRole;\n /**\n * Accessible name of the teleported popover. A `dialog` popover needs one;\n * the other roles take it from their activator or options.\n */\n menuLabel?: string;\n}\n\ndefineOptions({\n name: 'RuiMenu',\n});\n\nconst modelValue = defineModel<boolean>({ default: false });\n\nconst {\n openOnHover = false,\n disabled = false,\n fullWidth = false,\n openDelay = 0,\n closeDelay = 0,\n options,\n classNames,\n closeOnContentClick = false,\n persistOnActivatorClick = false,\n hint,\n errorMessages = [],\n successMessages = [],\n showDetails = false,\n details = 'reserve',\n dense = false,\n persistent = false,\n disableAutoFocus = false,\n anchorEl,\n role = 'menu',\n menuLabel,\n} = defineProps<MenuProps>();\n\ndefineSlots<{\n activator?: (props: {\n // An inline type literal, so callers can pass it on as `Record<string, unknown>`\n attrs: {\n 'onMouseover'?: () => void;\n 'onMouseleave'?: () => void;\n 'onClick'?: () => void;\n 'aria-haspopup': RuiMenuRole | 'true';\n 'aria-expanded': boolean;\n 'aria-controls'?: string;\n };\n open: boolean;\n disabled: boolean;\n hasError: boolean;\n hasSuccess: boolean;\n }) => any;\n default?: (props: { width: number }) => any;\n /** Rendered above the activator, outside the element the menu positions against. */\n label?: () => any;\n}>();\n\nconst click = ref<boolean>(false);\nconst menuContent = useTemplateRef<HTMLElement>('menuContent');\nconst menuId = useId();\n\nconst FOCUSABLE_ELEMENTS_SELECTOR =\n 'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])';\n\nconst {\n reference: activator,\n popover: menu,\n open,\n visible,\n currentPlacement,\n leavePending,\n onLeavePending,\n onOpen,\n onClose,\n onLeaveComplete,\n updatePosition,\n} = useFloating(\n () => options ?? {},\n () => disabled,\n () => openDelay,\n () => closeDelay,\n () => anchorEl,\n);\n\nconst { width } = useElementSize(activator);\n\nconst { hasError, hasSuccess } = useFormTextDetail(\n () => errorMessages,\n () => successMessages,\n);\n\nconst menuStyles = tv({\n slots: {\n wrapper: 'relative inline-flex max-w-full',\n popover: 'w-max z-rui-menu',\n // a hairline edge keeps the popup apart from a surface of nearly the same shade in dark mode\n content: 'rounded-rui-panel overflow-hidden shadow-rui-menu bg-rui-menu border border-rui-divider text-rui-text focus:outline-hidden p-1',\n details: '',\n },\n variants: {\n fullWidth: {\n true: { wrapper: 'w-full' },\n },\n dense: {\n true: { details: 'px-2' },\n false: { details: 'px-4' },\n },\n },\n defaultVariants: { fullWidth: false, dense: false },\n});\n\nconst ui = computed<ReturnType<typeof menuStyles>>(() => menuStyles({\n fullWidth,\n dense,\n}));\n\n// `aria-haspopup=\"true\"` means `\"menu\"`, and the default keeps emitting `true` for existing callers\nconst ariaHasPopup = computed<RuiMenuRole | 'true'>(() => role === 'menu' ? 'true' : role);\n\n/**\n * The activator attributes.\n *\n * This and the computed below it must return the same object shape from every\n * branch. Otherwise vue-tsc infers the slot's `attrs` type as a discriminated\n * union where each key is either all defined or all undefined, which breaks\n * consumer code typing its own `attrs` handler with individually optional\n * keys.\n */\nconst baseMenuAttrs = computed<BaseMenuAttrs>(() => {\n const clickVal = get(click);\n return {\n onMouseover: disabled\n ? undefined\n : () => {\n if (openOnHover)\n onOpen();\n },\n onMouseleave: disabled\n ? undefined\n : () => {\n if (openOnHover && !clickVal)\n onClose();\n },\n };\n});\n\nconst menuAttrs = computed<MenuAttrs>(() => ({\n ...get(baseMenuAttrs),\n 'onClick': disabled ? undefined : checkClick,\n 'aria-haspopup': get(ariaHasPopup),\n 'aria-expanded': get(open),\n // Only while the popover is in the DOM, so the reference never points at a missing element\n 'aria-controls': get(visible) ? menuId : undefined,\n}));\n\n/**\n * Moves focus into the open menu without scrolling the page. A menu taller than the space below its\n * activator would otherwise make the browser scroll the whole document to reveal the focused\n * container, sliding the page out from under the activator.\n */\nfunction focusOnContent() {\n const content = get(menuContent);\n if (!content)\n return;\n\n content.focus({ preventScroll: true });\n}\n\nfunction focusMenu(): void {\n if (disableAutoFocus)\n return;\n\n nextTick(() => focusOnContent());\n}\n\n/** Hands focus back to the activator on close, likewise without scrolling to it. */\nfunction focusOnActivator() {\n const activatorEl = get(activator);\n if (!activatorEl) {\n return;\n }\n const focusableEl = activatorEl.querySelector<HTMLElement>(FOCUSABLE_ELEMENTS_SELECTOR);\n if (focusableEl) {\n focusableEl.focus({ preventScroll: true });\n }\n}\n\nfunction onLeave(event?: KeyboardEvent): void {\n if (!get(open))\n return;\n onClose();\n set(click, false);\n event?.stopPropagation();\n\n // Return focus to activator when menu closes\n if (disableAutoFocus) {\n return;\n }\n\n nextTick(() => focusOnActivator());\n}\n\n/**\n * `persistent` blocks the outside interactions that would close the menu, and\n * Escape is one of them, which is how `RuiDialog` and `RuiBottomSheet` already\n * read it. The guard sits here rather than in `onLeave`, because `onLeave`\n * also runs for a programmatic close through `v-model`, which must keep\n * working.\n *\n * A persistent menu leaves the key alone instead of swallowing it, so an inner\n * popover can close on Escape while the menu stays open, and an ancestor still\n * sees a key nothing consumed.\n *\n * Escape is swallowed only while the menu is open and not persistent\n * (`onLeave` stops the event itself); a closed or persistent menu must let it\n * through, or a consumer that closes its own editor on Escape never sees the\n * key. The root element listens for it.\n */\nfunction onEscape(event: KeyboardEvent): void {\n if (persistent)\n return;\n\n onLeave(event);\n}\n\nfunction checkClick(): void {\n if (get(open) && get(click)) {\n if (!persistOnActivatorClick)\n onLeave();\n }\n else {\n onOpen();\n set(click, true);\n }\n}\n\nwatch(modelValue, (value) => {\n if (get(open) === value)\n return;\n\n if (value) {\n onOpen();\n set(click, true);\n }\n else {\n onLeave();\n }\n});\n\nwatch(open, (open) => {\n set(modelValue, open);\n if (open) {\n focusMenu();\n }\n});\n\nonClickOutside(menu, () => {\n if (get(open) && !persistent)\n onLeave();\n}, { ignore: [activator] });\n</script>\n\n<template>\n <div\n :class=\"classNames?.root\"\n @keydown.esc=\"onEscape($event)\"\n >\n <slot name=\"label\" />\n <div\n ref=\"activator\"\n :class=\"ui.wrapper({ class: cn(classNames?.wrapper) })\"\n :data-menu-disabled=\"disabled\"\n >\n <slot\n name=\"activator\"\n v-bind=\"{ attrs: menuAttrs, open, disabled, hasError, hasSuccess }\"\n />\n </div>\n <Teleport\n v-if=\"!disabled\"\n to=\"body\"\n >\n <div\n v-if=\"visible\"\n :id=\"menuId\"\n ref=\"menu\"\n :class=\"ui.popover({ class: cn(classNames?.menu) })\"\n :role=\"role\"\n :aria-label=\"menuLabel\"\n :data-placement=\"currentPlacement\"\n @click=\"closeOnContentClick ? onLeave() : undefined\"\n @keydown.esc=\"onEscape($event)\"\n >\n <TransitionGroup\n enter-active-class=\"transition ease-out duration-200\"\n enter-from-class=\"opacity-0 translate-y-1\"\n enter-to-class=\"opacity-100 translate-y-0\"\n leave-active-class=\"transition ease-in duration-150\"\n leave-from-class=\"opacity-100 translate-y-0\"\n leave-to-class=\"opacity-0 translate-y-1\"\n @before-enter=\"updatePosition()\"\n @after-leave=\"leavePending ? onLeaveComplete() : undefined\"\n @before-leave=\"onLeavePending()\"\n >\n <div\n v-if=\"open\"\n ref=\"menuContent\"\n key=\"menu\"\n data-id=\"content\"\n :class=\"ui.content({ class: cn(classNames?.content) })\"\n tabindex=\"-1\"\n v-bind=\"baseMenuAttrs\"\n >\n <ButtonGroupBoundary>\n <slot v-bind=\"{ width }\" />\n </ButtonGroupBoundary>\n </div>\n </TransitionGroup>\n </div>\n </Teleport>\n <RuiFormTextDetail\n v-if=\"showDetails\"\n :details=\"details\"\n :class=\"ui.details({ class: cn(classNames?.details) })\"\n :error-messages=\"errorMessages\"\n :success-messages=\"successMessages\"\n :hint=\"hint\"\n />\n </div>\n</template>\n"],"mappings":"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"}
|