@adyen/bento-mcp 0.3.0 → 0.5.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/CHANGELOG.md +27 -0
- package/dist/assets/components/alert/alert.types.ts +1 -1
- package/dist/assets/components/button/button.types.ts +1 -1
- package/dist/assets/components/button/components/button-actions/button-actions.types.ts +1 -1
- package/dist/assets/components/card/card.types.ts +1 -1
- package/dist/assets/components/checkbox/checkbox.vue +1 -1
- package/dist/assets/components/checkbox/components/checkbox-group/checkbox-group.types.ts +1 -1
- package/dist/assets/components/checkbox/components/checkbox-group/checkbox-group.vue +1 -1
- package/dist/assets/components/code-snippet/code-snippet.types.ts +1 -1
- package/dist/assets/components/country/country.types.ts +1 -1
- package/dist/assets/components/data-grid/components/data-grid-cell-actions/data-grid-cell-actions.types.ts +1 -1
- package/dist/assets/components/data-grid/components/data-grid-cell-actions/data-grid-cell-actions.vue +1 -1
- package/dist/assets/components/data-grid/components/data-grid-row/data-grid-row.vue +1 -1
- package/dist/assets/components/data-grid/data-grid.docs.mdx +1 -1
- package/dist/assets/components/data-grid/data-grid.vue +1 -1
- package/dist/assets/components/date-picker/date-picker.types.ts +1 -1
- package/dist/assets/components/date-picker/date-picker.vue +1 -1
- package/dist/assets/components/date-range-picker/components/date-range-picker-calendar/date-range-picker-calendar.vue +1 -1
- package/dist/assets/components/date-range-picker/date-range-picker.types.ts +1 -1
- package/dist/assets/components/draggable/components/draggable-handle/draggable-handle.stories.ts +1 -0
- package/dist/assets/components/draggable/components/draggable-handle/draggable-handle.types.ts +1 -0
- package/dist/assets/components/draggable/components/draggable-handle/draggable-handle.vue +1 -0
- package/dist/assets/components/draggable/draggable.docs.mdx +178 -0
- package/dist/assets/components/draggable/draggable.stories.ts +1 -0
- package/dist/assets/components/draggable/draggable.types.ts +1 -0
- package/dist/assets/components/draggable/draggable.vue +1 -0
- package/dist/assets/components/dropdown/components/variants/dropdown-avatar/dropdown-avatar.vue +1 -1
- package/dist/assets/components/dropdown/components/variants/dropdown-country/dropdown-country.vue +1 -1
- package/dist/assets/components/dropdown/components/variants/dropdown-currency/dropdown-currency.vue +1 -1
- package/dist/assets/components/dropdown/components/variants/dropdown-payment-method/dropdown-payment-method.vue +1 -1
- package/dist/assets/components/dropdown/components/variants/dropdown-tag/dropdown-tag.vue +1 -1
- package/dist/assets/components/dropdown/composables/use-keyboard-navigation.types.ts +1 -1
- package/dist/assets/components/dropdown/dropdown.stories.ts +1 -1
- package/dist/assets/components/dropdown/dropdown.types.ts +1 -1
- package/dist/assets/components/dropdown/dropdown.vue +1 -1
- package/dist/assets/components/file-uploader/file-uploader.vue +1 -1
- package/dist/assets/components/filter-bar/components/boolean-filter/boolean-filter.vue +1 -1
- package/dist/assets/components/filter-bar/components/date-filter/date-filter.vue +1 -1
- package/dist/assets/components/filter-bar/components/input-filter/input-filter.types.ts +1 -1
- package/dist/assets/components/filter-bar/filter-bar.docs.mdx +1 -0
- package/dist/assets/components/filter-bar/filter-bar.stories.ts +1 -1
- package/dist/assets/components/filter-bar/filter-bar.types.ts +1 -1
- package/dist/assets/components/filter-bar/filter-bar.vue +1 -1
- package/dist/assets/components/form-layout/components/form-layout-group/form-layout-group.vue +1 -1
- package/dist/assets/components/form-layout/form-layout.docs.mdx +33 -20
- package/dist/assets/components/form-layout/form-layout.stories.ts +1 -1
- package/dist/assets/components/header/header.types.ts +1 -1
- package/dist/assets/components/input-field/input-field.types.ts +1 -1
- package/dist/assets/components/input-field/input-field.vue +1 -1
- package/dist/assets/components/input-field-password/input-field-password.types.ts +1 -1
- package/dist/assets/components/input-field-password/input-field-password.vue +1 -1
- package/dist/assets/components/input-field-phone-number/input-field-phone-number.types.ts +1 -1
- package/dist/assets/components/input-field-phone-number/input-field-phone-number.vue +1 -1
- package/dist/assets/components/input-time/input-time.vue +1 -1
- package/dist/assets/components/internal/calendar/calendar.types.ts +1 -1
- package/dist/assets/components/internal/calendar/composables/use-range-pane.types.ts +1 -0
- package/dist/assets/components/internal/listbox/components/listbox-multi-select/listbox-multi-select.vue +1 -1
- package/dist/assets/components/internal/listbox/components/listbox-single-select/listbox-single-select.vue +1 -1
- package/dist/assets/components/internal/listbox/listbox.vue +1 -1
- package/dist/assets/components/internal/teleport/teleport.vue +1 -1
- package/dist/assets/components/link/link.types.ts +1 -1
- package/dist/assets/components/menu/menu.types.ts +1 -1
- package/dist/assets/components/menu/menu.vue +1 -1
- package/dist/assets/components/modal/modal.types.ts +1 -1
- package/dist/assets/components/modal/modal.vue +1 -1
- package/dist/assets/components/pagination/pagination.types.ts +1 -1
- package/dist/assets/components/payment-method/payment-method.types.ts +1 -1
- package/dist/assets/components/popover/popover.types.ts +1 -1
- package/dist/assets/components/popper-container/popper-container.types.ts +1 -1
- package/dist/assets/components/promo-banner/promo-banner.types.ts +1 -1
- package/dist/assets/components/radio-group/radio-group.types.ts +1 -1
- package/dist/assets/components/segmented-control/segmented-control.types.ts +1 -1
- package/dist/assets/components/status/status.types.ts +1 -1
- package/dist/assets/components/summary-grid/components/summary-grid-item/summary-grid-item.types.ts +1 -1
- package/dist/assets/components/summary-grid/summary-grid.types.ts +1 -1
- package/dist/assets/components/tag/tag.types.ts +1 -1
- package/dist/assets/components/textarea/textarea.types.ts +1 -1
- package/dist/assets/components/textarea/textarea.vue +1 -1
- package/dist/assets/components/timeline/timeline.types.ts +1 -1
- package/dist/assets/components/toggle/toggle.types.ts +1 -1
- package/dist/assets/components/toggle/toggle.vue +1 -1
- package/dist/assets/components/typography/typography.types.ts +1 -1
- package/dist/assets/components.json +101 -100
- package/dist/assets/composables/use-bento-theme/install.types.ts +1 -1
- package/dist/assets/composables/use-form-validate/use-form.types.ts +1 -1
- package/dist/assets/deprecations.md +196 -196
- package/dist/assets/icons.json +18 -0
- package/dist/assets/index.ts +1 -1
- package/dist/assets/tailwind/README.md +45 -5
- package/dist/assets/usage.json +103 -0
- package/dist/main.js +53 -3
- package/package.json +2 -3
- package/dist/assets/components/draggable-handle/draggable-handle.stories.ts +0 -1
- package/dist/assets/components/draggable-handle/draggable-handle.types.ts +0 -1
- package/dist/assets/components/draggable-handle/draggable-handle.vue +0 -1
- /package/dist/assets/components/{draggable-handle → draggable/components/draggable-handle}/draggable-handle.docs.mdx +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.0 (2026-04-22)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- added `find_component_usage` to find all usages of a component in the codebase ([5a859f624](https://github.com/Adyen/bento/commit/5a859f624))
|
|
8
|
+
|
|
9
|
+
### ❤️ Thank You
|
|
10
|
+
|
|
11
|
+
- daver
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## 0.4.0 (2026-04-15)
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
|
|
18
|
+
- fixed `bento-mcp` components registry ([1656acaf8](https://github.com/Adyen/bento/commit/1656acaf8))
|
|
19
|
+
|
|
20
|
+
### Miscellaneous Chores
|
|
21
|
+
|
|
22
|
+
- removed `@swc/cli` from `mcp` package to fix `CVE-2026-31808` and `CVE-2026-32630` ([3c2114a97](https://github.com/Adyen/bento/commit/3c2114a97))
|
|
23
|
+
|
|
24
|
+
### ❤️ Thank You
|
|
25
|
+
|
|
26
|
+
- daver
|
|
27
|
+
- erice
|
|
28
|
+
|
|
29
|
+
|
|
3
30
|
## 0.3.0 (2026-04-08)
|
|
4
31
|
|
|
5
32
|
### Features
|
|
@@ -1 +1 @@
|
|
|
1
|
-
/** * @deprecated
|
|
1
|
+
/** * @deprecated Since v2.0.0. Use string literal instead. */ export enum BentoAlertVariant { DEFAULT = 'default', TIP = 'tip', } /** * @deprecated Since v2.0.0. Use string literal instead. */ export enum BentoAlertTypeOption { WARNING = 'warning', CRITICAL = 'critical', HIGHLIGHT = 'highlight', SUCCESS = 'success', } /** * @deprecated Since v2.0.0. Use `@close-alert` instead. */ export enum BentoAlertEvent { CLOSE_ALERT = 'close-alert', }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
/** * @deprecated
|
|
1
|
+
/** * @deprecated Since v2.0.0. Use string literal instead. */ export enum BentoButtonVariant { PRIMARY = 'primary', SECONDARY = 'secondary', TERTIARY = 'tertiary', TERTIARY_WITH_BACKGROUND = 'tertiary-with-background', } export enum BentoButtonPropClass { CONDENSED = 'condensed', DESTRUCTIVE = 'critical', DISABLED = 'disabled', ICON_ONLY = 'icon-only', INVERSE = 'inverse', } export interface BentoButtonProps { condensed?: boolean; critical?: boolean; disabled?: boolean; inverse?: boolean; variant?: BentoButtonVariant | `${BentoButtonVariant}`; type?: string; link?: object; }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import type { BentoButtonProps } from '../../button.types'; import type { BentoMenuProps } from '@/components/menu/menu.types'; import type { BentoLoadingButtonState } from '../loading-button/loading-button.types'; import type { VueNodeElement } from '@/types/elements'; import type { OmitStrict } from '@/types/omit-strict'; import type { VNodeRef } from 'vue/types/vnode'; export type BentoButtonActionObject = BentoActionButton; // TODO: Update to Array<BentoButtonActionButton | BentoButtonActionMenuObject> in v2.0.0 export type BentoButtonActionsList = Array<BentoActionButton & BentoActionMenu>; export enum BentoButtonActionsLayoutBasic { BUTTONS_END = 'buttons-end', FILL_CONTAINER = 'fill-container', SPACE_BETWEEN = 'space-between', VERTICAL_STACK = 'vertical-stack', } export enum BentoButtonActionsLayoutExtended { BUTTONS_START = 'buttons-start', } /** * @deprecated
|
|
1
|
+
import type { BentoButtonProps } from '../../button.types'; import type { BentoMenuProps } from '@/components/menu/menu.types'; import type { BentoLoadingButtonState } from '../loading-button/loading-button.types'; import type { VueNodeElement } from '@/types/elements'; import type { OmitStrict } from '@/types/omit-strict'; import type { VNodeRef } from 'vue/types/vnode'; export type BentoButtonActionObject = BentoActionButton; // TODO: Update to Array<BentoButtonActionButton | BentoButtonActionMenuObject> in v2.0.0 export type BentoButtonActionsList = Array<BentoActionButton & BentoActionMenu>; export enum BentoButtonActionsLayoutBasic { BUTTONS_END = 'buttons-end', FILL_CONTAINER = 'fill-container', SPACE_BETWEEN = 'space-between', VERTICAL_STACK = 'vertical-stack', } export enum BentoButtonActionsLayoutExtended { BUTTONS_START = 'buttons-start', } /** * @deprecated Since v2.0.0. Use string literal instead. */ export const BentoButtonActionsLayout = { ...BentoButtonActionsLayoutBasic, ...BentoButtonActionsLayoutExtended }; export type BentoButtonActionsLayout = BentoButtonActionsLayoutBasic | BentoButtonActionsLayoutExtended; export interface BentoButtonActionsProps { /** * List of actions that will be used to render the buttons. * First element in the list will be rendered as "primary". * All elements after the first will be rendered as "secondary". * * Each object in the list should have a `title`, an `event` and * (optional) a Vue `icon` from the library `@adyen/ui-assets-icons-16`. * * `title`: Button's text. * * `event`: Event triggered when clicking the Button. * * `icon` (optional): Vue Icon from the library `@adyen/ui-assets-icons-16`. * * @see BentoButton for a list of all the other props supported by each button action. * * ``` * [{ * title: 'Primary', * event: () => { ... }, * icon: FourPeopleIcon, * link: { * variant: 'default', * external: true, * to: 'https://www.adyen.com/' * }, * }, { * title: 'Secondary', * event: () => { ... } * disabled: true, * variant: 'secondary', * critical: true * }] * ``` */ actions: BentoButtonActionsList; /** * Sets the Button Actions to color scheme inverse */ inverse?: boolean; /** * Defines the layout in which the button actions is presented. * It can be one of: * * @values BUTTONS_START * @values BUTTONS_END * @values FILL_CONTAINER * @values SPACE_BETWEEN * @values VERTICAL_STACK */ layout?: string; } /** * Private */ /** * Base interface for the Button and Menu objects */ export interface BentoButtonActionBase { title: string; icon?: VueNodeElement; iconLeft?: VueNodeElement; iconRight?: VueNodeElement; /** * Provides a template ref to the underlying component instance. */ elementRef?: Exclude<VNodeRef, string>; } /** * Type for the Buttons passed as objects to Button Actions */ export interface BentoActionButton extends BentoButtonActionBase, BentoButtonProps { event?: (event: Event) => void; state?: BentoLoadingButtonState | `${BentoLoadingButtonState}`; iconOnly?: boolean; } /** * Type for the Menus passed as objects to Button Actions */ export type BentoActionMenu = BentoButtonActionBase & OmitStrict<BentoMenuProps, 'button' | 'data'> & Partial<Pick<BentoMenuProps, 'data'>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
/** * @deprecated
|
|
1
|
+
/** * @deprecated Since v2.0.0. Use string literal instead. */ export enum BentoCardBackground { PRIMARY = 'primary', SECONDARY = 'secondary', } /** * @deprecated Since v2.0.0. Use string literal instead. */ export enum BentoCardTitleSize { DEFAULT = 'default', SMALL = 'small', }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<template> <label class="b-checkbox" :class="conditionalClass" v-bind="styleAttrs" :aria-hidden="getAriaHidden($attrs)"> <checkbox-input v-bind="$attrs" ref="input" :aria-label="computedAriaLabel" :aria-describedby="ariaDescribedBy" :aria-required="required" :disabled="disabled" :has-error="shouldShowError" :indeterminate="isIndeterminate" :value="value" :model-value="modelValue" :readonly="readonly" @update:model-value="onChange" /> <span v-if="hasSlot('default') || shouldShowErrorMessage" class="b-checkbox__text"> <bento-typography v-if="hasSlot('default')" class="b-checkbox__label" el="span" variant="body"> <span><slot /></span> <bento-typography v-if="optional" class="b-checkbox__label-requirement" el="span" >({{ t('optional') }})</bento-typography > <bento-typography v-else-if="required" class="b-checkbox__label-requirement" el="span" >*</bento-typography > <bento-tag v-if="tag" class="b-checkbox__label-tag" v-bind="tag" /> </bento-typography> <bento-typography v-if="hasSlot('default') && hasSlot('description')" :id="descriptionId" class="b-checkbox__description" el="span" variant="body" > <slot name="description"></slot> </bento-typography> <error-message v-if="shouldShowErrorMessage" :id="errorId" :error-message="errorMessage" /> <bento-typography v-if="contentSlotAvailable" class="b-checkbox__content" el="span" variant="body"> <slot name="content"></slot> </bento-typography> </span> </label> </template> <script lang="ts"> import { computed, defineComponent, type HTMLAttributes, onMounted, type PropType, ref, toRefs, useAttrs, } from 'vue'; import { BentoCheckboxEvent, BentoCheckboxState, type BentoCheckboxTag, type BentoCheckboxType, type BentoMultiCheckboxType, } from './checkbox.types'; import { BentoTypography } from '@/components/typography'; import { CheckboxInput } from '@/components/internal/checkbox-input'; import { ErrorMessage } from '@/components/internal/error-message'; import { useDefaultSlotValidator, useHasSlot } from '@/composables'; import { generateUid } from '@/core/utils/ts'; import { getSlotText } from '@/utils/ts/get-slot-text'; import { useI18n } from '@/utils/ts/i18n'; import { useAriaLabel } from '@/utils/ts/aria-label'; import { BentoTag } from '@/components/tag'; import { type BentoCheckboxGroupItems } from './components/checkbox-group/checkbox-group.types'; import messages from './messages.json'; type MessageSchema = (typeof messages)['en-US']; /** * Checkboxes are used to make a binary choice about an option. * The label tag area is also clickable. * * @usage * import { BentoCheckbox } from '@adyen/bento-vue2'; * * export default { * components: { BentoCheckbox }, * template: ` * <bento-checkbox> * Label text * <template #description>Sub label text</template> * </bento-checkbox> * `, * } */ export default defineComponent({ name: 'bento-checkbox', i18n: { messages }, components: { BentoTag, BentoTypography, CheckboxInput, ErrorMessage, }, inheritAttrs: false, model: { prop: 'modelValue' }, props: { /** * @deprecated
|
|
1
|
+
<template> <label class="b-checkbox" :class="conditionalClass" v-bind="styleAttrs" :aria-hidden="getAriaHidden($attrs)"> <checkbox-input v-bind="$attrs" ref="input" :aria-label="computedAriaLabel" :aria-describedby="ariaDescribedBy" :aria-required="required" :disabled="disabled" :has-error="shouldShowError" :indeterminate="isIndeterminate" :value="value" :model-value="modelValue" :readonly="readonly" @update:model-value="onChange" /> <span v-if="hasSlot('default') || shouldShowErrorMessage" class="b-checkbox__text"> <bento-typography v-if="hasSlot('default')" class="b-checkbox__label" el="span" variant="body"> <span><slot /></span> <bento-typography v-if="optional" class="b-checkbox__label-requirement" el="span" >({{ t('optional') }})</bento-typography > <bento-typography v-else-if="required" class="b-checkbox__label-requirement" el="span" >*</bento-typography > <bento-tag v-if="tag" class="b-checkbox__label-tag" v-bind="tag" /> </bento-typography> <bento-typography v-if="hasSlot('default') && hasSlot('description')" :id="descriptionId" class="b-checkbox__description" el="span" variant="body" > <slot name="description"></slot> </bento-typography> <error-message v-if="shouldShowErrorMessage" :id="errorId" :error-message="errorMessage" /> <bento-typography v-if="contentSlotAvailable" class="b-checkbox__content" el="span" variant="body"> <slot name="content"></slot> </bento-typography> </span> </label> </template> <script lang="ts"> import { computed, defineComponent, type HTMLAttributes, onMounted, type PropType, ref, toRefs, useAttrs, } from 'vue'; import { BentoCheckboxEvent, BentoCheckboxState, type BentoCheckboxTag, type BentoCheckboxType, type BentoMultiCheckboxType, } from './checkbox.types'; import { BentoTypography } from '@/components/typography'; import { CheckboxInput } from '@/components/internal/checkbox-input'; import { ErrorMessage } from '@/components/internal/error-message'; import { useDefaultSlotValidator, useHasSlot } from '@/composables'; import { generateUid } from '@/core/utils/ts'; import { getSlotText } from '@/utils/ts/get-slot-text'; import { useI18n } from '@/utils/ts/i18n'; import { useAriaLabel } from '@/utils/ts/aria-label'; import { BentoTag } from '@/components/tag'; import { type BentoCheckboxGroupItems } from './components/checkbox-group/checkbox-group.types'; import messages from './messages.json'; type MessageSchema = (typeof messages)['en-US']; /** * Checkboxes are used to make a binary choice about an option. * The label tag area is also clickable. * * @usage * import { BentoCheckbox } from '@adyen/bento-vue2'; * * export default { * components: { BentoCheckbox }, * template: ` * <bento-checkbox> * Label text * <template #description>Sub label text</template> * </bento-checkbox> * `, * } */ export default defineComponent({ name: 'bento-checkbox', i18n: { messages }, components: { BentoTag, BentoTypography, CheckboxInput, ErrorMessage, }, inheritAttrs: false, model: { prop: 'modelValue' }, props: { /** * @deprecated Since v2.0.0. Use `aria-label` instead. * Accessible name for the radio button component. It is strongly recommended to pas this value in case this component is used without a label. */ ariaLabel: { type: String, default: null }, /** * Setting this property to true will disable the checkbox in your form. */ disabled: { type: Boolean, default: false }, /** * Renders the checkbox in an error state with an error message. */ errorMessage: { type: String, default: null }, /** * Setting this property to true will show the checkbox in error state. */ hasError: { type: Boolean, default: false }, /** * The :indeterminate CSS pseudo-class represents any form element * whose state is indeterminate, such as checkboxes which have their HTML indeterminate attribute * set to true, radio buttons which are members of a group in which all radio buttons are unchecked, * and indeterminate <progress> elements. https://developer.mozilla.org/en-US/docs/Web/CSS/:indeterminate * This property lets you control the input.indeterminate native property. */ indeterminate: { type: Boolean, default: false }, /** * When handling a single checkbox with a singular state, use a boolean. * * When handling multiple checkboxes, use an array of keys. * These keys exist as a set "value" prop for each checkbox. */ modelValue: { type: [Boolean, Array] as PropType<BentoCheckboxType | BentoCheckboxGroupItems>, default: false, }, /** * Highlights the checkbox is optional. */ optional: { type: Boolean, default: false }, /** * Setting this property to true will set the checkbox to read only state. */ readonly: { type: Boolean, default: false }, /** * Highlights the checkbox is required. */ required: { type: Boolean, default: false }, /** * Sets the properties of the BentoTag component that is placed next to the label. * {@see BentoTag} */ tag: { type: Object as PropType<BentoCheckboxTag>, default: undefined }, /** * The value of the component. The DOM API casts this to a string. * The browser uses "on" as the default value. * * In the case of multiple checkboxes this value is used as a unique key. */ value: { type: [String, Number], default: null }, }, emits: [BentoCheckboxEvent.UPDATE, BentoCheckboxEvent.INPUT], setup(props, { attrs, emit, slots }) { const { t } = useI18n<{ message: MessageSchema }>({ messages }); const descriptionId = generateUid('description'); const errorId = generateUid('error'); const hasSlot = useHasSlot(slots); const { disabled, indeterminate, modelValue, readonly, value } = toRefs(props); const defaultSlotValue = getSlotText(slots)('default'); const descriptionSlotValue = getSlotText(slots)('description'); useDefaultSlotValidator('bento-checkbox', slots.default, slots.description); const isCheckboxGroup = computed(() => Array.isArray(modelValue.value)); const isChecked = computed(() => { if (!isCheckboxGroup.value) { return modelValue.value as boolean; } return (modelValue.value as BentoMultiCheckboxType).some(item => item === value.value); }); const isIndeterminate = computed(() => indeterminate.value); const shouldShowError = computed( () => !props.disabled && !props.readonly && (props.hasError || !!props.errorMessage) ); const shouldShowErrorMessage = computed(() => !props.disabled && !props.readonly && !!props.errorMessage); // Content slot logic const hasContentSlot = computed(() => hasSlot('content')); const isCheckedOrIntermediate = computed(() => isIndeterminate?.value || isChecked?.value); const contentSlotAvailable = computed( () => hasContentSlot?.value && isCheckboxGroup?.value && isCheckedOrIntermediate?.value ); // ARIA fields const ariaDescribedBy = computed<HTMLAttributes['aria-describedby']>( () => `${shouldShowErrorMessage.value ? errorId : ''} ${ defaultSlotValue && descriptionSlotValue ? descriptionId : '' }`.trim() || null ); const ariaLabelAttribute = ref(attrs['aria-label']); const { ariaLabel } = toRefs(props); const checkboxTranslation = ref(); // Translations used in the script tag need to be inside onMounted otherwise they fail in Vue 3 onMounted(() => { checkboxTranslation.value = t('checkbox'); }); // Only add aria-label if no label slot is present const computedAriaLabel = computed( () => useAriaLabel({ ariaLabel: ariaLabelAttribute.value as string, // TODO: take this line away when the aria-label props is removed label: ariaLabel, defaultFallback: hasSlot('default') ? (getSlotText(slots)('default') as string) : checkboxTranslation.value, }).value ); const getAriaHidden = (attributes: HTMLAttributes) => attributes?.['aria-hidden']; const styleAttrs = computed(() => { const { style, class: cls } = useAttrs(); return { class: cls as HTMLAttributes['class'], style: style as HTMLAttributes['style'], }; }); const conditionalClass = computed(() => ({ [`b-checkbox--${BentoCheckboxState.DISABLED}`]: disabled.value, [`b-checkbox--${BentoCheckboxState.READONLY}`]: readonly.value, })); const onChange = (newValue: BentoCheckboxType | BentoCheckboxGroupItems) => { if (disabled.value) { return; } emit(BentoCheckboxEvent.UPDATE, newValue); emit(BentoCheckboxEvent.INPUT, newValue); }; return { // Values descriptionId, errorId, ariaDescribedBy, computedAriaLabel, conditionalClass, defaultSlotValue, descriptionSlotValue, isIndeterminate, shouldShowError, shouldShowErrorMessage, contentSlotAvailable, hasContentSlot, styleAttrs, // Methods hasSlot, onChange, getAriaHidden, t, }; }, }); </script> <style lang="scss" scoped src="./checkbox.scss" />
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import { type FormFieldEmits } from '../../../../types/form-field'; import type { BentoCheckboxTag } from '../../checkbox.types'; /** * @deprecated
|
|
1
|
+
import { type FormFieldEmits } from '../../../../types/form-field'; import type { BentoCheckboxTag } from '../../checkbox.types'; /** * @deprecated Since v2.0.0. Use string literal instead. */ export enum BentoCheckboxGroupVariant { HORIZONTAL = 'horizontal', VERTICAL = 'vertical', } export interface BentoCheckboxGroupItem { value: string | number; label: string; description?: string; disabled?: boolean; hasError?: boolean; errorMessage?: string; readonly?: boolean; tag?: BentoCheckboxTag; } export type BentoCheckboxGroupItems = Array<BentoCheckboxGroupItem>; export type BentoCheckboxGroupEmits = FormFieldEmits<any>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<template> <div class="b-checkbox-group"> <controls-group class="b-checkbox-group__checkboxes" :aria-describedby="shouldShowGroupErrorMessage ? errorId : ''" :aria-required="required" component-name="bento-checkbox-group" :class="conditionalClasses" :description="description" :disabled="disabled" :form="formId" :hide-label="hideLabel" :label="label" :optional="optional" :required="required" :tooltip-text="tooltipText" > <bento-checkbox v-for="item in items" :key="item.value" :model-value="inputValue" :value="item.value" :disabled="disabled || item.disabled" :has-error="shouldShowGroupError || item.hasError" :error-message="shouldShowGroupError ? null : item.errorMessage" :readonly="isReadOnly || item.readonly" :tag="item.tag" @update:model-value="onModelValueUpdate" > <template #default>{{ item.label }}</template> <template v-if="item.description" #description>{{ item.description }}</template> <template v-if="hasContentSlot('content')" #content> <slot name="content" v-bind="item"></slot> </template> </bento-checkbox> </controls-group> <error-message v-if="shouldShowGroupErrorMessage" :id="errorId" :error-message="errorMessage" class="b-checkbox-group__error-message" /> </div> </template> <script setup lang="ts"> import { computed, onMounted, type PropType, ref, toRef, useSlots, watch } from 'vue'; import { ControlsGroup } from '@/components/internal/controls-group'; import { ErrorMessage } from '@/components/internal/error-message'; import { type BentoCheckboxGroupEmits, type BentoCheckboxGroupItems, BentoCheckboxGroupVariant, } from './checkbox-group.types'; import BentoCheckbox from '@/components/checkbox/checkbox.vue'; import { generateUid } from '@/core/utils/ts'; import { deprecate } from '@/utils/ts/deprecate'; import { useFormLayoutFieldLoading } from '@/composables/use-form-layout-loading/use-form-layout-loading'; import { useFormFieldEmits } from '@/utils/ts/form-field/form-field'; const props = defineProps({ /** * Descriptive supporting text visbile underneath the label. */ description: { type: String, default: null }, /** * If set to true, renders all checkboxes in the group in a disabled state. */ disabled: { type: Boolean, default: false }, /** * Renders all checkboxes in an error state with an error message. */ errorMessage: { type: String, default: null }, /** * The form id the checkbox group is associated with. Defaults to null, as a checkbox group can also be used standalone. */ formId: { type: String, default: null }, /** * If set to true, renders all checkboxes in the group in an error state. * * @deprecated Use
|
|
1
|
+
<template> <div class="b-checkbox-group"> <controls-group class="b-checkbox-group__checkboxes" :aria-describedby="shouldShowGroupErrorMessage ? errorId : ''" :aria-required="required" component-name="bento-checkbox-group" :class="conditionalClasses" :description="description" :disabled="disabled" :form="formId" :hide-label="hideLabel" :label="label" :optional="optional" :required="required" :tooltip-text="tooltipText" > <bento-checkbox v-for="item in items" :key="item.value" :model-value="inputValue" :value="item.value" :disabled="disabled || item.disabled" :has-error="shouldShowGroupError || item.hasError" :error-message="shouldShowGroupError ? null : item.errorMessage" :readonly="isReadOnly || item.readonly" :tag="item.tag" @update:model-value="onModelValueUpdate" > <template #default>{{ item.label }}</template> <template v-if="item.description" #description>{{ item.description }}</template> <template v-if="hasContentSlot('content')" #content> <slot name="content" v-bind="item"></slot> </template> </bento-checkbox> </controls-group> <error-message v-if="shouldShowGroupErrorMessage" :id="errorId" :error-message="errorMessage" class="b-checkbox-group__error-message" /> </div> </template> <script setup lang="ts"> import { computed, onMounted, type PropType, ref, toRef, useSlots, watch } from 'vue'; import { ControlsGroup } from '@/components/internal/controls-group'; import { ErrorMessage } from '@/components/internal/error-message'; import { type BentoCheckboxGroupEmits, type BentoCheckboxGroupItems, BentoCheckboxGroupVariant, } from './checkbox-group.types'; import BentoCheckbox from '@/components/checkbox/checkbox.vue'; import { generateUid } from '@/core/utils/ts'; import { deprecate } from '@/utils/ts/deprecate'; import { useFormLayoutFieldLoading } from '@/composables/use-form-layout-loading/use-form-layout-loading'; import { useFormFieldEmits } from '@/utils/ts/form-field/form-field'; const props = defineProps({ /** * Descriptive supporting text visbile underneath the label. */ description: { type: String, default: null }, /** * If set to true, renders all checkboxes in the group in a disabled state. */ disabled: { type: Boolean, default: false }, /** * Renders all checkboxes in an error state with an error message. */ errorMessage: { type: String, default: null }, /** * The form id the checkbox group is associated with. Defaults to null, as a checkbox group can also be used standalone. */ formId: { type: String, default: null }, /** * If set to true, renders all checkboxes in the group in an error state. * * @deprecated Since v2.0.0. Use `errorMessage` instead. */ hasError: { type: Boolean, default: false }, /** * Shows/hides the group label. */ hideLabel: { type: Boolean, default: null }, /** * The checkbox group's items. */ items: { type: Array as PropType<BentoCheckboxGroupItems>, required: true }, /** * The name associated with the group. Required for accessibility purposes. */ label: { type: String, required: true }, /** * Determines the initially selected value of the group. Corresponds to the BentoCheckboxButtonItem's value. * Defaults to no value selected. */ modelValue: { type: Array as PropType<Array<number | string | null>>, default: () => [] }, /** * Indicates the field is optional. */ optional: { type: Boolean, default: false }, /** * If set to true, renders all checkboxes in the group in a readonly state. */ readonly: { type: Boolean, default: false }, /** * Indicates the field is required. */ required: { type: Boolean, default: false }, /** * Tooltip message. */ tooltipText: { type: String, default: null }, /** * The checkbox group's layout. It can be vertical or horizontal. Defaults to vertical. */ variant: { type: String as PropType<BentoCheckboxGroupVariant | `${BentoCheckboxGroupVariant}`>, default: BentoCheckboxGroupVariant.VERTICAL, validator: (value: BentoCheckboxGroupVariant) => Object.values(BentoCheckboxGroupVariant).includes(value), }, }); const emit = defineEmits<BentoCheckboxGroupEmits>(); const slots = useSlots(); const errorId = generateUid('error'); const { isReadOnly } = useFormLayoutFieldLoading(toRef(props, 'readonly')); const { emitValue } = useFormFieldEmits(emit); const shouldShowGroupError = computed( () => !props.disabled && !isReadOnly.value && (!!props.errorMessage || props.hasError) ); const shouldShowGroupErrorMessage = computed(() => !props.disabled && !isReadOnly.value && !!props.errorMessage); const conditionalClasses = computed(() => ({ 'b-checkbox-group__checkboxes--horizontal': props.variant === BentoCheckboxGroupVariant.HORIZONTAL, })); // Slots logic // Temporary, use-has-slots composable doesn't currently work for // vue3 named scoped slots const hasContentSlot = (name: string) => !!slots[name]; const shouldShowSlotsError = computed(() => hasContentSlot('content') && props.variant !== 'vertical'); watch( shouldShowSlotsError, newValue => { if (newValue) { throw new Error('The content slot is only available for the vertical checkbox group layout'); } }, { immediate: true } ); const inputValue = ref(props.modelValue); watch( () => props.modelValue, () => { inputValue.value = props.modelValue; } ); const onModelValueUpdate = (value: any) => { inputValue.value = value; emitValue(value); }; onMounted(() => { if (props.hasError) { deprecate( 'BentoCheckboxGroup "hasError" property', `Use the BentoCheckboxGroup "errorMessage" property instead to indicate that there is a modifier error. Having the field as "undefined" will hide/remove the error. <bento-checkbox-group errorMessage="Error message text" />`, '2.0.0' ); } }); </script> <script lang="ts"> const COMPONENT_NAME = 'bento-checkbox-group'; /** * A checkbox group component should be used whenever it is necessary to group a set of checkboxes that are related together * This component conforms to the official WAI-ARIA guidelines for grouping controls: https://www.w3.org/WAI/tutorials/forms/grouping/ * * import { BentoCheckboxGroup } from '@adyen/bento-vue2/'; * * export default { * components: { BentoCheckboxGroup }, * template: ` * <bento-checkbox-group * label="Your group label" * :items="[ * { value: 1, label: 'Your label' }, * { value: 2, label: 'Another label', description: 'Your sub label' }, * ]" * /> * ` * }; */ export default { name: COMPONENT_NAME, model: { prop: 'modelValue' }, }; </script> <style lang="scss" scoped src="./checkbox-group.scss" />
|
|
@@ -1 +1 @@
|
|
|
1
|
-
/** * @deprecated
|
|
1
|
+
/** * @deprecated Since v2.0.0. Use string literal instead. */ export enum BentoCodeSnippetVariant { INLINE = 'inline', SINGLELINE = 'singleline', MULTILINE = 'multiline', } /** * @deprecated Since v2.0.0. Use string literal instead. */ export enum BentoCodeSnippetSupportedLanguage { JSON = 'json', XML = 'xml', JAVA = 'java', JAVASCRIPT = 'javascript', PYTHON = 'python', CSV = 'csv', } export enum BentoCodeSnippetEvent { ACTION = 'action', CHANGE = 'change', } export interface BentoCodeSnippetProps { /** * Displays a button that when clicked will emit an action event * Leaves to the parent component the logic of what to do with it */ actionable?: boolean; /** * Displays a button that when clicked will emit a run event * Leaves to the parent component the logic of what to do with it */ actionableText?: string; /** * The code content */ content?: string; /** * Displays a button that can expand the codeblock into a modal to easily * view large blocks of code. */ expandable?: boolean; /** * Allows editing of the code-snippet content by the user */ editable?: boolean; /** * Language name for syntax highlighting rules */ lang?: BentoCodeSnippetSupportedLanguage | `${BentoCodeSnippetSupportedLanguage}`; /** * Label to show in the action-bar, describing the language */ langLabel?: string; /** * Toggles the loading indicator within the action bar */ loading?: boolean; /** * Defines the variant of code-snippet */ variant?: BentoCodeSnippetVariant | `${BentoCodeSnippetVariant}`; }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
/** * @deprecated
|
|
1
|
+
/** * @deprecated Since v2.0.0. Use string literal instead. */ export enum BentoCountryVariant { ISO = 'iso', NAME = 'name', PHONE_CODE = 'phone_code', CAPITAL = 'capital', /** deprecated Use any of the other BentoCountry variants instead OR BentoTypography component for custom text */ CUSTOM = 'custom', } export enum BentoCountryCode { BD = 'BD', BE = 'BE', BF = 'BF', BG = 'BG', BA = 'BA', BB = 'BB', WF = 'WF', BL = 'BL', BM = 'BM', BN = 'BN', BO = 'BO', BH = 'BH', BI = 'BI', BJ = 'BJ', BT = 'BT', JM = 'JM', BV = 'BV', BW = 'BW', WS = 'WS', BQ = 'BQ', BR = 'BR', BS = 'BS', JE = 'JE', BY = 'BY', BZ = 'BZ', RU = 'RU', RW = 'RW', RS = 'RS', TL = 'TL', RE = 'RE', TM = 'TM', TJ = 'TJ', RO = 'RO', TK = 'TK', GW = 'GW', GU = 'GU', GT = 'GT', GS = 'GS', GR = 'GR', GQ = 'GQ', GP = 'GP', JP = 'JP', GY = 'GY', GG = 'GG', GF = 'GF', GE = 'GE', GD = 'GD', GB = 'GB', GA = 'GA', SV = 'SV', GN = 'GN', GM = 'GM', GL = 'GL', GI = 'GI', GH = 'GH', OM = 'OM', TN = 'TN', JO = 'JO', HR = 'HR', HT = 'HT', HU = 'HU', HK = 'HK', HN = 'HN', HM = 'HM', VE = 'VE', PR = 'PR', PS = 'PS', PW = 'PW', PT = 'PT', SJ = 'SJ', PY = 'PY', IQ = 'IQ', PA = 'PA', PF = 'PF', PG = 'PG', PE = 'PE', PK = 'PK', PH = 'PH', PN = 'PN', PL = 'PL', PM = 'PM', ZM = 'ZM', EH = 'EH', EE = 'EE', EG = 'EG', ZA = 'ZA', EC = 'EC', IT = 'IT', VN = 'VN', SB = 'SB', ET = 'ET', SO = 'SO', ZW = 'ZW', SA = 'SA', ES = 'ES', ER = 'ER', ME = 'ME', MD = 'MD', MG = 'MG', MF = 'MF', MA = 'MA', MC = 'MC', UZ = 'UZ', MM = 'MM', ML = 'ML', MO = 'MO', MN = 'MN', MH = 'MH', MK = 'MK', MU = 'MU', MT = 'MT', MW = 'MW', MV = 'MV', MQ = 'MQ', MP = 'MP', MS = 'MS', MR = 'MR', IM = 'IM', UG = 'UG', TZ = 'TZ', MY = 'MY', MX = 'MX', IL = 'IL', FR = 'FR', IO = 'IO', SH = 'SH', FI = 'FI', FJ = 'FJ', FK = 'FK', FM = 'FM', FO = 'FO', NI = 'NI', NL = 'NL', NO = 'NO', NA = 'NA', VU = 'VU', NC = 'NC', NE = 'NE', NF = 'NF', NG = 'NG', NZ = 'NZ', NP = 'NP', NR = 'NR', NU = 'NU', CK = 'CK', XK = 'XK', CI = 'CI', CH = 'CH', CO = 'CO', CN = 'CN', CM = 'CM', CL = 'CL', CC = 'CC', CA = 'CA', CG = 'CG', CF = 'CF', CD = 'CD', CZ = 'CZ', CY = 'CY', CX = 'CX', CR = 'CR', CW = 'CW', CV = 'CV', CU = 'CU', SZ = 'SZ', SY = 'SY', SX = 'SX', KG = 'KG', KE = 'KE', SS = 'SS', SR = 'SR', KI = 'KI', KH = 'KH', KN = 'KN', KM = 'KM', ST = 'ST', SK = 'SK', KR = 'KR', SI = 'SI', KP = 'KP', KW = 'KW', SN = 'SN', SM = 'SM', SL = 'SL', SC = 'SC', KZ = 'KZ', KY = 'KY', SG = 'SG', SE = 'SE', SD = 'SD', DO = 'DO', DM = 'DM', DJ = 'DJ', DK = 'DK', VG = 'VG', DE = 'DE', YE = 'YE', DZ = 'DZ', US = 'US', UY = 'UY', YT = 'YT', UM = 'UM', LB = 'LB', LC = 'LC', LA = 'LA', TV = 'TV', TW = 'TW', TT = 'TT', TR = 'TR', LK = 'LK', LI = 'LI', LV = 'LV', TO = 'TO', LT = 'LT', LU = 'LU', LR = 'LR', LS = 'LS', TH = 'TH', TF = 'TF', TG = 'TG', TD = 'TD', TC = 'TC', LY = 'LY', VA = 'VA', VC = 'VC', AE = 'AE', AD = 'AD', AG = 'AG', AF = 'AF', AI = 'AI', VI = 'VI', IS = 'IS', IR = 'IR', AM = 'AM', AL = 'AL', AO = 'AO', AQ = 'AQ', AS = 'AS', AR = 'AR', AU = 'AU', AT = 'AT', AW = 'AW', IN = 'IN', AX = 'AX', AZ = 'AZ', IE = 'IE', ID = 'ID', UA = 'UA', QA = 'QA', MZ = 'MZ', UNKNOWN = 'ZZ', } export interface BentoCountryProps { /** * Two character country code ([ISO 3166-1 alpha-2 format](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2)). */ code?: BentoCountryCode | `${BentoCountryCode}`; /** * Custom label text. */ label?: string; /** * Which label variant to display. * @values iso, name, phone_code, capital */ variant?: BentoCountryVariant | `${BentoCountryVariant}`; }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import type { BentoButtonActionObject } from '@/components/button/components/button-actions/button-actions.types'; import type { BentoMenuItem } from '@/components/menu/components/menu-item/menu-item.types'; import type { BentoDatagridDataItem } from '@/components/data-grid/components/data-grid-row/data-grid-row.types'; import type { OmitStrict } from '@/types/omit-strict'; import { type BentoTooltipPosition } from '@/directives/tooltip/tooltip.types'; export type BentoColumnCellActionVariant = 'copy' | 'edit' | 'open' | 'more'; export type BentoColumnCellActionEvent = (rowDataItem: BentoDatagridDataItem, columnKey: string) => void; export type BentoColumnCellActionMenuItem = { /** * @deprecated
|
|
1
|
+
import type { BentoButtonActionObject } from '@/components/button/components/button-actions/button-actions.types'; import type { BentoMenuItem } from '@/components/menu/components/menu-item/menu-item.types'; import type { BentoDatagridDataItem } from '@/components/data-grid/components/data-grid-row/data-grid-row.types'; import type { OmitStrict } from '@/types/omit-strict'; import { type BentoTooltipPosition } from '@/directives/tooltip/tooltip.types'; export type BentoColumnCellActionVariant = 'copy' | 'edit' | 'open' | 'more'; export type BentoColumnCellActionEvent = (rowDataItem: BentoDatagridDataItem, columnKey: string) => void; export type BentoColumnCellActionMenuItem = { /** * @deprecated Since v2.0.0. Use `event` instead. */ handler?: BentoMenuItem['handler']; event?: BentoColumnCellActionEvent; } & OmitStrict<BentoMenuItem, 'handler'>; export type BentoColumnCellAction = OmitStrict< BentoButtonActionObject, 'variant' | 'event' | 'title' | 'icon' | 'state' > & { title?: string; event?: BentoColumnCellActionEvent; tooltipText?: string; tooltipPosition?: BentoTooltipPosition | `${BentoTooltipPosition}`; // Variant button properties isShort?: true; variant?: BentoColumnCellActionVariant; // Menu button properties menuData?: Array<BentoColumnCellActionMenuItem>; // Copy button properties getCopyData?: (rowDataItem: BentoDatagridDataItem, columnKey: string) => string; }; export type BentoColumnCellActions = Array<BentoColumnCellAction>; export type BentoColumnCellActionsByCellDataFn = ( rowDataItem: BentoDatagridDataItem, columnKey: string ) => BentoColumnCellActions; export type BentoColumnCellActionsProp = BentoColumnCellActions | BentoColumnCellActionsByCellDataFn; export type BentoDataGridRowActionsFn = (rowDataItem: BentoDatagridDataItem) => BentoColumnCellActions; export type BentoDataGridRowActionsProp = BentoColumnCellActions | BentoDataGridRowActionsFn;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<template> <Transition name="b-data-grid-cell-actions__animation" appear> <div class="b-data-grid-cell-actions"> <div v-if="showGradientElement && !isTouchDevice" class="b-data-grid-cell-actions__background-gradient" :class="conditionalCellActionsBackgroundGradientClasses" ></div> <div class="b-data-grid-cell-actions__actions" :class="conditionalCellActionsClasses" role="group"> <div v-for="(button, index) in computedCellActions" :key="index" class="b-data-grid-cell-actions__action" > <bento-menu v-if="button.menuData" v-bento-tooltip-directive:[button.tooltipPosition]="button.tooltipText" :data="transformMenuEvents(button.menuData)" :button="{ condensed: true, variant: 'secondary', disabled: button.disabled, }" :disable-arrow-down-opening-menu="disableArrowDownOpeningMenu" :teleport="teleportMenu" @open="emit('menu-open')" @close="emit('menu-close')" @menu-item-click="emit('menu-item-click')" > <template v-if="shouldShowMenuIconLeft(button)" #iconLeft> <options-vertical-icon v-if="button?.variant === 'more'" :svg-title="getButtonTitle(button, true)" /> <component :is="button.iconLeft" v-else :svg-title="getButtonTitle(button, true)" /> </template> <template v-if="!button?.isShort" #default> {{ getButtonTitle(button) }} </template> </bento-menu> <copy v-else-if="button.variant === 'copy'" :text="getCopyText(button)" :button-text="getButtonTitle(button)" :button="{ condensed: true, variant: 'secondary', }" /> <bento-button v-else v-bento-tooltip-directive:[button.tooltipPosition]="button.tooltipText" condensed variant="secondary" :critical="button.critical" :disabled="button.disabled" :link="button.link" v-on="generateButtonEvents(button)" > <template v-if="button.iconLeft || button.variant === 'edit'" #iconLeft> <edit1-icon v-if="button?.variant === 'edit'" :svg-title="getButtonTitle(button, true)" /> <component :is="button.iconLeft" v-else :svg-title="button.title" /> </template> <template v-if="!button?.isShort" #default> {{ getButtonTitle(button) }} </template> <template v-if="button.iconRight || button.variant === 'open'" #iconRight> <external-link-icon v-if="button?.variant === 'open'" :svg-title="getButtonTitle(button, true)" /> <component :is="button.iconRight" v-else :svg-title="button.title" /> </template> </bento-button> </div> </div> </div> </Transition> </template> <script setup lang="ts"> import { computed, inject, type PropType, ref, type Ref } from 'vue'; import { BentoButton } from '@/components/button'; import { BentoMenu, type BentoMenuItem } from '@/components/menu'; import { Copy } from '@/internal/copy'; import { BentoTooltipDirective as vBentoTooltipDirective } from '@/directives/tooltip'; import { printDevelopmentWarning } from '@/utils/ts/print-development-warning'; import { type BentoDatagridDataItem } from '@/components/data-grid/data-grid.types'; import { DATA_GRID_TOUCH_DEVICE } from '@/components/data-grid/data-grid.keys'; import Edit1Icon from '@adyen/ui-assets-icons-16/vue/edit-1'; import ExternalLinkIcon from '@adyen/ui-assets-icons-16/vue/external-link'; import OptionsVerticalIcon from '@adyen/ui-assets-icons-16/vue/options-vertical'; // Types import type { BentoColumnCellAction, BentoColumnCellActionMenuItem, BentoColumnCellActions, BentoColumnCellActionsByCellDataFn, BentoColumnCellActionsProp, BentoDataGridRowActionsProp, } from './data-grid-cell-actions.types'; // Translations import messages from './messages.json'; import { useI18n } from '@/utils/ts/i18n'; type MessageSchema = (typeof messages)['en-US']; const { t } = useI18n<{ message: MessageSchema }>({ messages }); const props = defineProps({ /** * Column definitions using 'key' to relate to data object. */ cellActions: { type: [Array, Function] as PropType<BentoColumnCellActionsProp | BentoDataGridRowActionsProp>, required: true, validator: (value: BentoColumnCellActions) => value?.length <= 2, }, /** * The column field key. */ columnKey: { type: String, default: undefined, }, /** * Disables any opening of the cell actions menus using the down arrow key. */ disableArrowDownOpeningMenu: { type: Boolean, default: false, }, /** * Set to true if the datagrid has been scrolled and the right side has overflowing space. */ hasRightOverflow: { type: Boolean, default: false, }, /** * Set to true if the row has a darker background when zebra rows is enabled. */ hasZebraBackground: { type: Boolean, default: false, }, /** * Set to true if the row is selected. */ isSelected: { type: Boolean, default: false, }, /** * Set to true if the row is highlighted. */ isHighlighted: { type: Boolean, default: false, }, /** * Set if set to true will teleport the action menu component */ teleportMenu: { type: Boolean, default: false, }, /** * The row data item object. */ rowDataItem: { type: Object as PropType<BentoDatagridDataItem>, required: true, }, }); const emit = defineEmits<{ /** * Emits when a cell actions menu is opened */ (e: 'menu-open'): void; /** * Emits when a cell actions menu is closed */ (e: 'menu-close'): void; /** * Emits when a cell actions menu button closes due to a menu item click */ (e: 'menu-item-click'): void; }>(); const isTouchDevice: Ref<boolean> = inject(DATA_GRID_TOUCH_DEVICE, ref(false)); // Helper to get text for menu items, always returning a string const getMenuItemText = (action: BentoColumnCellAction | BentoColumnCellActionMenuItem) => { // If it's already a menu item with a text property, use it if ('text' in action && action.text !== undefined) { return action.text; } // Otherwise, derive from variant or title switch ((action as BentoColumnCellAction).variant) { case 'copy': return t('copy'); case 'edit': return t('edit'); case 'open': return t('open'); case 'more': return t('more'); default: return (action as BentoColumnCellAction).title; } }; const getMenuItemIcon = (action: BentoColumnCellAction) => { switch (action.variant) { case 'copy': return Copy; case 'edit': return Edit1Icon; case 'open': return ExternalLinkIcon; default: return undefined; } }; const isRowActions = computed(() => props.columnKey === undefined); const conditionalCellActionsBackgroundGradientClasses = computed(() => ({ 'b-data-grid-cell-actions__background-gradient--with-row-actions-and-overflow': isRowActions.value && props.hasRightOverflow, 'b-data-grid-cell-actions__background-gradient--zebra': props.hasZebraBackground, 'b-data-grid-cell-actions__background-gradient--highlighted': props.isHighlighted, 'b-data-grid-cell-actions__background-gradient--selected': props.isSelected, })); const conditionalCellActionsClasses = computed(() => ({ 'b-data-grid-cell-actions__actions--with-row-actions': isRowActions.value, 'b-data-grid-cell-actions__actions--zebra': props.hasZebraBackground, 'b-data-grid-cell-actions__actions--highlighted': props.isHighlighted, 'b-data-grid-cell-actions__actions--selected': props.isSelected, })); const showGradientElement = computed(() => !isRowActions.value || (isRowActions.value && props.hasRightOverflow)); /** * Checks if the passed object is a cell actions function that returns an cell actions array. * @param cellActions - a cell actions array or function */ function isBentoColumnCellActionsByCellValueDataFn( cellActions: BentoColumnCellActionsProp ): cellActions is BentoColumnCellActionsByCellDataFn { return typeof cellActions === 'function'; } const computedCellActions = computed(() => { const cellActions = isBentoColumnCellActionsByCellValueDataFn(props.cellActions) ? props.cellActions?.(props.rowDataItem, props.columnKey) : props.cellActions; if (isTouchDevice.value && !isRowActions.value && cellActions) { const flattenedMenuItems: Array<BentoColumnCellActionMenuItem> = []; cellActions.forEach(action => { if (action.menuData) { action.menuData.forEach(subMenuItem => { flattenedMenuItems.push(subMenuItem); }); } else { flattenedMenuItems.push({ text: getMenuItemText(action), event: action.event, critical: action.critical, disabled: action.disabled, icon: getMenuItemIcon(action), }); } }); return [ { variant: 'more', isShort: true, iconLeft: OptionsVerticalIcon, menuData: transformMenuEvents(flattenedMenuItems), }, ] as BentoColumnCellActions; } // Do not allow users to provide more than 2 cell action buttons if (!isTouchDevice.value && cellActions?.length > 2) { printDevelopmentWarning( 'WARNING: BentoDataGrid Cell actions can only have a maximum of 2 actions, if you need more consider using the menuData property instead.' ); } return cellActions?.slice(0, 2); }); const getButtonTitle = computed(() => (button: BentoColumnCellAction, isForSvgTitle?: boolean) => { if ((!isForSvgTitle && button.isShort) || (isForSvgTitle && !button.isShort)) { return undefined; } switch (button.variant) { case 'copy': return t('copy'); case 'edit': return t('edit'); case 'open': return t('open'); case 'more': return t('more'); default: return button.title; } }); const generateButtonEvents = (button: BentoColumnCellAction) => button?.event !== undefined ? { click: (event: MouseEvent) => { event.stopPropagation(); button.event(props.rowDataItem, props.columnKey); }, } : {}; const generateMenuEvents = (menuDataItem: BentoColumnCellActionMenuItem): BentoMenuItem => ({ ...menuDataItem, handler: () => { if (menuDataItem?.handler && typeof menuDataItem.handler === 'function') { menuDataItem?.handler(); } if (menuDataItem?.event && typeof menuDataItem.event === 'function') { menuDataItem?.event(props.rowDataItem, props.columnKey); } }, }); const transformMenuEvents = (menuData: Array<BentoColumnCellActionMenuItem>) => menuData.map(menuDataItem => { const transformedMenuData = menuDataItem; if (transformedMenuData?.items) { transformedMenuData.items = transformMenuEvents(transformedMenuData.items); } return { ...generateMenuEvents(transformedMenuData) }; }); const getCopyText = (button: BentoColumnCellAction) => button?.getCopyData !== undefined ? button.getCopyData(props.rowDataItem, props.columnKey) : props.columnKey ? (props.rowDataItem?.[props.columnKey]?.toString() as string) : ''; const shouldShowMenuIconLeft = (button: BentoColumnCellAction) => button.iconLeft || button?.variant === 'more'; </script> <script lang="ts"> /** * Internal component to display the data-grid actions. * There are 4 different pre-defined buttons that can be used by using the `variant` property. * * The component renders buttons or a menu depending on the actions provided. * * @example * <data-grid-cell-actions * :cell-actions="[ * { variant: 'edit', event: () => {} }, * { * variant: 'more', * isShort: true, * menuData: [ * { * text: 'Cancel payment', * handler: () => {}), * critical: true, * }, * { * text: 'Transfer payment', * handler: () => {}, * }, * { * text: 'Delete payment', * handler: () => {}, * disabled: true, * }, * ], * }, * ]" * column-key="payment" * :row-data-item="rowData" * /> */ export default { i18n: { messages }, name: 'data-grid-cell-actions', }; </script> <style lang="scss" scoped src="./data-grid-cell-actions.scss" />
|
|
1
|
+
<template> <Transition name="b-data-grid-cell-actions__animation" appear> <div class="b-data-grid-cell-actions"> <div v-if="showGradientElement && !isTouchDevice" class="b-data-grid-cell-actions__background-gradient" :class="conditionalCellActionsBackgroundGradientClasses" ></div> <div class="b-data-grid-cell-actions__actions" :class="conditionalCellActionsClasses" role="group"> <div v-for="(button, index) in computedCellActions" :key="index" class="b-data-grid-cell-actions__action" > <bento-menu v-if="button.menuData" v-bento-tooltip-directive:[button.tooltipPosition]="button.tooltipText" :data="transformMenuEvents(button.menuData)" :button="{ condensed: true, variant: 'secondary', disabled: button.disabled, }" :disable-arrow-down-opening-menu="disableArrowDownOpeningMenu" :teleport="teleportMenu" @open="emit('menu-open')" @close="emit('menu-close')" @menu-item-click="emit('menu-item-click')" > <template v-if="shouldShowMenuIconLeft(button)" #iconLeft> <options-vertical-icon v-if="button?.variant === 'more'" :svg-title="getButtonTitle(button, true)" /> <component :is="button.iconLeft" v-else :svg-title="getButtonTitle(button, true)" /> </template> <template v-if="!button?.isShort" #default> {{ getButtonTitle(button) }} </template> </bento-menu> <copy v-else-if="button.variant === 'copy'" :text="getCopyText(button)" :button-text="getButtonTitle(button)" :button="{ condensed: true, variant: 'secondary', }" /> <bento-button v-else v-bento-tooltip-directive:[button.tooltipPosition]="button.tooltipText" condensed variant="secondary" :critical="button.critical" :disabled="button.disabled" :link="button.link" v-on="generateButtonEvents(button)" > <template v-if="button.iconLeft || button.variant === 'edit'" #iconLeft> <edit1-icon v-if="button?.variant === 'edit'" :svg-title="getButtonTitle(button, true)" /> <component :is="button.iconLeft" v-else :svg-title="button.title" /> </template> <template v-if="!button?.isShort" #default> {{ getButtonTitle(button) }} </template> <template v-if="button.iconRight || button.variant === 'open'" #iconRight> <external-link-icon v-if="button?.variant === 'open'" :svg-title="getButtonTitle(button, true)" /> <component :is="button.iconRight" v-else :svg-title="button.title" /> </template> </bento-button> </div> </div> </div> </Transition> </template> <script setup lang="ts"> import { computed, inject, type PropType, ref, type Ref } from 'vue'; import { BentoButton } from '@/components/button'; import { BentoMenu, type BentoMenuItem } from '@/components/menu'; import { Copy } from '@/internal/copy'; import { BentoTooltipDirective as vBentoTooltipDirective } from '@/directives/tooltip'; import { printDevelopmentWarning } from '@/utils/ts/print-development-warning'; import { type BentoDatagridDataItem } from '@/components/data-grid/data-grid.types'; import { DATA_GRID_TOUCH_DEVICE } from '@/components/data-grid/data-grid.keys'; import Edit1Icon from '@adyen/ui-assets-icons-16/vue/edit-1'; import ExternalLinkIcon from '@adyen/ui-assets-icons-16/vue/external-link'; import OptionsVerticalIcon from '@adyen/ui-assets-icons-16/vue/options-vertical'; // Types import type { BentoColumnCellAction, BentoColumnCellActionMenuItem, BentoColumnCellActions, BentoColumnCellActionsByCellDataFn, BentoColumnCellActionsProp, BentoDataGridRowActionsProp, } from './data-grid-cell-actions.types'; // Translations import messages from './messages.json'; import { useI18n } from '@/utils/ts/i18n'; type MessageSchema = (typeof messages)['en-US']; const { t } = useI18n<{ message: MessageSchema }>({ messages }); const props = defineProps({ /** * Column definitions using 'key' to relate to data object. */ cellActions: { type: [Array, Function] as PropType<BentoColumnCellActionsProp | BentoDataGridRowActionsProp>, required: true, validator: (value: BentoColumnCellActions) => value?.length <= 2, }, /** * The column field key. */ columnKey: { type: String, default: undefined, }, /** * Disables any opening of the cell actions menus using the down arrow key. */ disableArrowDownOpeningMenu: { type: Boolean, default: false, }, /** * Set to true if the datagrid has been scrolled and the right side has overflowing space. */ hasRightOverflow: { type: Boolean, default: false, }, /** * Set to true if the row has a darker background when zebra rows is enabled. */ hasZebraBackground: { type: Boolean, default: false, }, /** * Set to true if the row is selected. */ isSelected: { type: Boolean, default: false, }, /** * Set to true if the row is highlighted. */ isHighlighted: { type: Boolean, default: false, }, /** * Set if set to true will teleport the action menu component */ teleportMenu: { type: Boolean, default: false, }, /** * The row data item object. */ rowDataItem: { type: Object as PropType<BentoDatagridDataItem>, required: true, }, }); const emit = defineEmits<{ /** * Emits when a cell actions menu is opened */ (e: 'menu-open'): void; /** * Emits when a cell actions menu is closed */ (e: 'menu-close'): void; /** * Emits when a cell actions menu button closes due to a menu item click */ (e: 'menu-item-click'): void; }>(); const isTouchDevice: Ref<boolean> = inject(DATA_GRID_TOUCH_DEVICE, ref(false)); // Helper to get text for menu items, always returning a string const getMenuItemText = (action: BentoColumnCellAction | BentoColumnCellActionMenuItem) => { // If it's already a menu item with a text property, use it if ('text' in action && action.text !== undefined) { return action.text; } // Otherwise, derive from variant or title switch ((action as BentoColumnCellAction).variant) { case 'copy': return t('copy'); case 'edit': return t('edit'); case 'open': return t('open'); case 'more': return t('more'); default: return (action as BentoColumnCellAction).title; } }; const getMenuItemIcon = (action: BentoColumnCellAction) => { switch (action.variant) { case 'copy': return Copy; case 'edit': return Edit1Icon; case 'open': return ExternalLinkIcon; default: return undefined; } }; const isRowActions = computed(() => props.columnKey === undefined); const conditionalCellActionsBackgroundGradientClasses = computed(() => ({ 'b-data-grid-cell-actions__background-gradient--with-row-actions-and-overflow': isRowActions.value && props.hasRightOverflow, 'b-data-grid-cell-actions__background-gradient--zebra': props.hasZebraBackground, 'b-data-grid-cell-actions__background-gradient--highlighted': props.isHighlighted, 'b-data-grid-cell-actions__background-gradient--selected': props.isSelected, })); const conditionalCellActionsClasses = computed(() => ({ 'b-data-grid-cell-actions__actions--with-row-actions': isRowActions.value, 'b-data-grid-cell-actions__actions--zebra': props.hasZebraBackground, 'b-data-grid-cell-actions__actions--highlighted': props.isHighlighted, 'b-data-grid-cell-actions__actions--selected': props.isSelected, 'b-data-grid-cell-actions__actions--zebra-hover': !isRowActions.value && props.hasZebraBackground, 'b-data-grid-cell-actions__actions--highlighted-hover': !isRowActions.value && props.isHighlighted, 'b-data-grid-cell-actions__actions--selected-hover': !isRowActions.value && props.isSelected, })); const showGradientElement = computed(() => !isRowActions.value || (isRowActions.value && props.hasRightOverflow)); /** * Checks if the passed object is a cell actions function that returns an cell actions array. * @param cellActions - a cell actions array or function */ function isBentoColumnCellActionsByCellValueDataFn( cellActions: BentoColumnCellActionsProp ): cellActions is BentoColumnCellActionsByCellDataFn { return typeof cellActions === 'function'; } const computedCellActions = computed(() => { const cellActions = isBentoColumnCellActionsByCellValueDataFn(props.cellActions) ? props.cellActions?.(props.rowDataItem, props.columnKey) : props.cellActions; if (isTouchDevice.value && !isRowActions.value && cellActions) { const flattenedMenuItems: Array<BentoColumnCellActionMenuItem> = []; cellActions.forEach(action => { if (action.menuData) { action.menuData.forEach(subMenuItem => { flattenedMenuItems.push(subMenuItem); }); } else { flattenedMenuItems.push({ text: getMenuItemText(action), event: action.event, critical: action.critical, disabled: action.disabled, icon: getMenuItemIcon(action), }); } }); return [ { variant: 'more', isShort: true, iconLeft: OptionsVerticalIcon, menuData: transformMenuEvents(flattenedMenuItems), }, ] as BentoColumnCellActions; } // Do not allow users to provide more than 2 cell action buttons if (!isTouchDevice.value && cellActions?.length > 2) { printDevelopmentWarning( 'WARNING: BentoDataGrid Cell actions can only have a maximum of 2 actions, if you need more consider using the menuData property instead.' ); } return cellActions?.slice(0, 2); }); const getButtonTitle = computed(() => (button: BentoColumnCellAction, isForSvgTitle?: boolean) => { if ((!isForSvgTitle && button.isShort) || (isForSvgTitle && !button.isShort)) { return undefined; } switch (button.variant) { case 'copy': return t('copy'); case 'edit': return t('edit'); case 'open': return t('open'); case 'more': return t('more'); default: return button.title; } }); const generateButtonEvents = (button: BentoColumnCellAction) => button?.event !== undefined ? { click: (event: MouseEvent) => { event.stopPropagation(); button.event(props.rowDataItem, props.columnKey); }, } : {}; const generateMenuEvents = (menuDataItem: BentoColumnCellActionMenuItem): BentoMenuItem => ({ ...menuDataItem, handler: () => { if (menuDataItem?.handler && typeof menuDataItem.handler === 'function') { menuDataItem?.handler(); } if (menuDataItem?.event && typeof menuDataItem.event === 'function') { menuDataItem?.event(props.rowDataItem, props.columnKey); } }, }); const transformMenuEvents = (menuData: Array<BentoColumnCellActionMenuItem>) => menuData.map(menuDataItem => { const transformedMenuData = menuDataItem; if (transformedMenuData?.items) { transformedMenuData.items = transformMenuEvents(transformedMenuData.items); } return { ...generateMenuEvents(transformedMenuData) }; }); const getCopyText = (button: BentoColumnCellAction) => button?.getCopyData !== undefined ? button.getCopyData(props.rowDataItem, props.columnKey) : props.columnKey ? (props.rowDataItem?.[props.columnKey]?.toString() as string) : ''; const shouldShowMenuIconLeft = (button: BentoColumnCellAction) => button.iconLeft || button?.variant === 'more'; </script> <script lang="ts"> /** * Internal component to display the data-grid actions. * There are 4 different pre-defined buttons that can be used by using the `variant` property. * * The component renders buttons or a menu depending on the actions provided. * * @example * <data-grid-cell-actions * :cell-actions="[ * { variant: 'edit', event: () => {} }, * { * variant: 'more', * isShort: true, * menuData: [ * { * text: 'Cancel payment', * handler: () => {}), * critical: true, * }, * { * text: 'Transfer payment', * handler: () => {}, * }, * { * text: 'Delete payment', * handler: () => {}, * disabled: true, * }, * ], * }, * ]" * column-key="payment" * :row-data-item="rowData" * /> */ export default { i18n: { messages }, name: 'data-grid-cell-actions', }; </script> <style lang="scss" scoped src="./data-grid-cell-actions.scss" />
|
|
@@ -1 +1 @@
|
|
|
1
|
-
<template> <div class="b-data-grid-row-container" role="presentation"> <div v-bind="ariaRowAttributes" :id="rowId?.toString()" :key="rowIndex" class="b-data-grid-row-container__row" :class="conditionalRowClasses(rowData, rowIndex)" role="row" :aria-selected="getRowAriaSelectedValue(rowData)" @click="onRowClick" @keyup.enter="onRowClick" @keyup.space="onRowClick" > <template v-for="({ columnSet, id, isFrozenColumns }, columnSetIndex) of columnSets"> <div v-if="columnSet?.length" :key="`data-grid-rows-${id}`" class="b-data-grid-row-container__cells" :class="conditionalRowSetClasses(isFrozenColumns, isSmallContainer)" :data-testid="`data-grid-${id}-rows`" > <data-grid-cell v-for="(column, colIndex) in columnSet" :key="`cell-${column.field}`" :ref="el => registerRowCellRef(el, column.field)" class="b-data-grid-row-container__cell" :column="column" :column-set-index="columnSetIndex" :col-index="colIndex" :column-sets="columnSets" :condensed="condensed" :dragging-columns="draggingColumns" :dragging-events-for-row-cell="draggingEventsForRowCell" :get-data-item-id="getDataItemId" :has-zebra-background="isRowEven" :is-highlighted="isHighlightedRow(rowData)" :is-selected="isSelectedRow(rowData)" :hovered-over-column-state="hoveredOverColumnState" :is-data-item-disabled="isDataItemDisabled" :is-measuring-column-widths="isMeasuringColumnWidths" :is-nested-content-expanded="isNestedContentShown" :nested-content="shouldShowNestedContentToggle" :nested-rows-element-ids="allNestedRowIds" :row-data="rowData" :row-index="rowIndex" :selection-is-indeterminate="hasUnSelectedNestedRows" :selectable="selectable" :selected-items="selectedItems" @cell-hovered="emit('cell-hovered', $event)" @cell-focus="emit('cell-focus', $event)" @on-frozen-columns-key-press="emit('on-frozen-columns-key-press', $event)" @select-row="onRowSelect" @nested-content-toggled="nestedContentToggled" > <template v-for="(_, innerSlot) of slots" #[innerSlot]="scope"> <slot :name="innerSlot" v-bind="scope" /> </template> </data-grid-cell> </div> <div v-if="!isFrozenColumns && rowActions" :key="`data-grid-row-actions-${id}`" ref="rowActionsRef" :aria-colindex="sumColumnAmountsInColumnSets().toString()" class="b-data-grid-row-container__row-actions-container" :class="conditionalRowActionsContainerClasses" :style="computedRowActionsStyles" :data-cellindex="getRowActionsCellIndex()" tabindex="-1" role="gridcell" @focus="onRowActionsCellFocus" @focusOut="onRowActionsCellFocusOut" @keydown="onRowActionsCellOnKeyDown" > <data-grid-cell-actions class="b-data-grid-row-container__row-actions" disable-arrow-down-opening-menu :cell-actions="rowActions" :has-right-overflow="hasRightOverflow" :row-data-item="rowData" teleport-menu @menu-open="onRowActionMenuOpen" @menu-close="onRowActionMenuClose" /> </div> </template> </div> <Transition v-if=" (hasNestedContent && hasSlot('nested-slot') && shouldShowNestedContent(rowData)) || (hasNestedRows && shouldShowNestedRowsBooleanResult) " :key="`${rowIndex}-nest-content`" name="b-data-grid-row-container__animation--nested-content" > <div v-if="isNestedContentShown && shouldShowRenderNestedRows" :class="conditionalNestedRowClasses" data-testid="nested-content-container" :style="nestedContentStyles" role="presentation" > <div ref="nestedContentRef"> <div v-if="isLoadingNestedRows" class="b-data-grid-row-container__row-nested-rows-loading-container" > <bento-loading-indicator /> </div> <data-grid-row v-for="(childRowData, childRowIndex) in rowData.children" v-bind="props" :id="getRowId(childRowIndex)" :key="`child-${childRowIndex}`" :ref="el => registerNestedRowRef(el, childRowIndex)" :row-data="childRowData" :row-index="childRowIndex" :row-set-size="rowData.children.length" is-nested-row @cell-hovered="emit('cell-hovered', $event)" @cell-focus="emit('cell-focus', $event)" @row-click="emitRowClick" @on-frozen-columns-key-press="emit('on-frozen-columns-key-press', $event)" @select-row="onNestedRowSelect(childRowData, $event)" > <template v-for="(_, innerSlot) of slots" #[innerSlot]="scope"> <slot :name="innerSlot" v-bind="scope" /> </template> </data-grid-row> </div> </div> <div v-else-if="isNestedContentShown" v-bind="nestedContentContainerDivAriaAttributes" :class="conditionalNestedRowClasses" data-testid="nested-content-container" :style="nestedContentStyles" role="row" tabindex="-1" > <div ref="nestedContentRef" role="gridcell"> <div v-if="isLoadingNestedRows" class="b-data-grid-row-container__row-nested-rows-loading-container" > <bento-loading-indicator /> </div> <div v-else-if="hasSlot('nested-slot') && shouldShowNestedContent(rowData)"> <slot :item="rowData" name="nested-slot" /> </div> </div> </div> </Transition> </div> </template> <script setup lang="ts"> import { type ComponentPublicInstance, computed, type ComputedRef, inject, onMounted, type PropType, ref, useSlots, watch, } from 'vue'; import { BentoLoadingIndicator } from '@/components/loading-indicator'; import { focusOnInteractiveChild, makeAllInteractiveChildrenFocusable, makeAllInteractiveChildrenUnFocusable, } from '@/components/data-grid/utils'; import DataGridRow from '@/components/data-grid/components/data-grid-row/data-grid-row.vue'; import DataGridCell from '@/components/data-grid/components/data-grid-cell/data-grid-cell.vue'; import DataGridCellActions from '@/components/data-grid/components/data-grid-cell-actions/data-grid-cell-actions.vue'; // Types and enums import { type BentoColumn, type BentoDatagridDataItem, type BentoDatagridDataItemId, type BentoDatagridGetDataItemIdFn, type BentoDatagridIsDataItemDisabledFn, type BentoDatagridShouldShowNestedContentFn, type BentoDatagridShouldShowNestedRowsFn, BentoDatagridStyleState, } from '@/components/data-grid/data-grid.types'; import type { DataGridCellColumnSet, DataGridCellDragEventsForColumn, } from '../data-grid-cell/data-grid-cell.types'; import type { BentoDatagridDraggingColumnState } from '../data-grid-columns/data-grid-columns.types'; import type { DatagridColumnHoveringState } from '@/components/data-grid/composables/use-column-hovering'; // Translations import { type Booleanish } from '@/types/prop-types'; import { useHasSlot } from '@/composables/use-has-slot/use-has-slot'; import { useExpandableContentHeight } from '@/composables/use-expandable-content-height/use-expandable-content-height'; import { observeSizeOfElement } from '@/utils/ts/resize'; import { DATAGRID_ACTIONS_COLUMN_FIELD_NAME, DATAGRID_CELL_OFFSET_PADDING, } from '@/components/data-grid/composables/use-calculate-column-width/use-calculate-column-width'; import { generateUid } from '@/core/utils/ts'; import { type BentoDataGridRowActionsProp } from '@/components/data-grid/components/data-grid-cell-actions/data-grid-cell-actions.types'; import type { HTMLAttributes } from 'vue/types/jsx'; import { EventKey } from '@/types/utils-events'; import { DATA_GRID_SMALL_CONTAINER } from '@/components/data-grid/data-grid.keys'; const slots = useSlots(); const hasSlot = useHasSlot(slots); const props = defineProps({ /** * Row actions property that generates which button to show in row actions area */ rowActions: { type: [Function, Array] as PropType<BentoDataGridRowActionsProp>, default: undefined, }, /** * When true, will emit a 'rowClick' event when a row is clicked. Will add cursor: pointer to rows. */ allowRowClicks: { type: Boolean, default: false }, /** * The columns sets including */ columnSets: { type: Array as PropType<Array<DataGridCellColumnSet>>, required: true, }, /** * Sets the row cells to the condensed padding state. */ condensed: { type: Boolean, default: false, }, /** * Columns being dragged, contains dragging column and a target column */ draggingColumns: { type: Object as PropType<BentoDatagridDraggingColumnState>, default: undefined, }, /** * Dragging event handlers for each row cell */ draggingEventsForRowCell: { type: Function as PropType<DataGridCellDragEventsForColumn>, required: true, }, /** * A customisable method to retrieve a unique identifier for a data item in the array of data supplied. * This is used selecting and performance reasons and must exist in each data item. */ getDataItemId: { type: Function as PropType<BentoDatagridGetDataItemIdFn>, default: (item: BentoDatagridDataItem) => item.id, }, /** * Set to true if the datagrid has been scrolled and the left side has overflowing space. */ hasLeftOverflow: { type: Boolean, default: false, }, /** * Enables the nested content slot functionality. */ hasNestedContent: { type: Boolean, default: false, }, /** * Enables the nested content slot functionality. */ hasNestedRows: { type: Boolean, default: false, }, /** * Set to true if the datagrid has been scrolled and the right side has overflowing space. */ hasRightOverflow: { type: Boolean, default: false, }, /** * Highlighted row IDs. */ highlightedRows: { type: Array as PropType<Array<BentoDatagridDataItemId>>, default: () => [], }, /** * The row index of a currently hovered row. */ hoveredRowIndex: { type: Number, default: -1, }, /** * The column hovered over state and whether the hovered over column should show a border right or left. */ hoveredOverColumnState: { type: Object as PropType<DatagridColumnHoveringState>, required: true, }, /** * A customisable method to determine if a row is not selectable. * The default is that every row is selectable. */ isDataItemDisabled: { type: Function as PropType<BentoDatagridIsDataItemDisabledFn>, default: () => false, }, /** * If the row is of an even number. * Rows are 0th based index e.g. index 0 is an odd-numbered row. */ isRowEven: { type: Boolean, default: false, }, /** * If the row is the last child in it's parent container. */ isRowLastChild: { type: Boolean, default: false, }, /** * If columns are being measured for the autoWidth functionality. */ isMeasuringColumnWidths: { type: Boolean, default: false, }, /** * Determines if this row is nested inside another row. */ isNestedRow: { type: Boolean, default: false, }, /** * The maximum width of the row actions in all rows */ rowActionsMaximumWidth: { type: Number, default: 0, }, /** * The row data object */ rowData: { type: Object as PropType<BentoDatagridDataItem>, required: true, }, /** * The row index. */ rowIndex: { type: Number, required: true, }, /** * The row ID to be using for the ID attribute. */ rowId: { type: Number, default: undefined, }, /** * The number of rows that this row is within. */ rowSetSize: { type: Number, default: 0, }, /** * Determines whether to render frozen seperator on the row. */ showFrozenColumnsSeparator: { type: Boolean, default: false, }, /** * Determines if the row selection is enabled. */ selectable: { type: Boolean, default: false, }, /** * The array of IDs for the selected rows. */ selectedItems: { type: Array as PropType<Array<BentoDatagridDataItemId>>, required: true, }, /** * Enables / Disables the nested-content slot */ shouldShowNestedContent: { type: Function as PropType<BentoDatagridShouldShowNestedContentFn>, default: (_item: BentoDatagridDataItem) => true, }, /** * Returns the nested rows for a given BentoDatagridDataItem. */ shouldShowNestedRows: { type: Function as PropType<BentoDatagridShouldShowNestedRowsFn>, default: (_item: BentoDatagridDataItem) => true, }, }); const emit = defineEmits<{ /** * Emitted when a cell has been hovered over. This is used to keep the row in a hovered state when hovering over a menu. */ (e: 'cell-hovered', value: number); /** * Emitted when an a frozen column cell fires a key up or down event. */ (e: 'on-frozen-columns-key-press', event: Event); /** * Emitted when a row has been selected. */ (e: 'select-row', value: Array<BentoDatagridDataItemId>, rowData: BentoDatagridDataItem); /** * Emitted when a nested row has been selected. */ ( e: 'select-nested-row', value: Array<BentoDatagridDataItemId>, parentRowData: BentoDatagridDataItem, rowData: BentoDatagridDataItem ); /** * Emitted when a row has been clicked. */ (e: 'row-click', rowData: BentoDatagridDataItem, event: MouseEvent | KeyboardEvent); /** * Emitted when a row with nested content is toggled */ (e: 'nested-content-toggled', rowData: BentoDatagridDataItem); /** * Emitted when a row cell is focused. */ ( e: 'cell-focus', value: { rowData: BentoDatagridDataItem; column: BentoColumn; rowIndex: number; colIndex: number } ); }>(); /** * Checks if the relatedTarget element appears after the reference element in DOM order. * Used to detect backward navigation (Shift+Tab). */ const isRelatedTargetAfterInDOM = (relatedTarget: HTMLElement | null, referenceEl: HTMLElement): boolean => { if (!relatedTarget || !referenceEl) { return false; } return !!(referenceEl.compareDocumentPosition(relatedTarget) & Node.DOCUMENT_POSITION_FOLLOWING); }; // Refs const gridcellRefs = ref<{ [key: string]: HTMLDivElement }>({}); const isNestedContentShown = ref(false); const nestedContentRef = ref(); const rowActionsRef = ref(); const nestedRowRefs = ref<Array<InstanceType<typeof DataGridRow>>>([]); const nestedContentSlotId = generateUid('nested-content-slot'); const isRowActionMenuOpen = ref(false); const isExitingRowActionInteraction = ref(false); const focusableItems = ref<Array<{ node: HTMLElement }>>(); /** * Returns the row-action container element, handling the case where Vue 2 * may wrap refs inside a v-for as an array. */ const getRowActionsElement = (): HTMLElement | undefined => { const val = rowActionsRef.value; return Array.isArray(val) ? val[0] : val; }; onMounted(() => { const container = getRowActionsElement(); if (container) { focusableItems.value = makeAllInteractiveChildrenUnFocusable(container); } }); const shouldShowNestedRowsBooleanResult = computed(() => props.shouldShowNestedRows(props.rowData)); const shouldShowRenderNestedRows = computed( () => !props.isNestedRow && props.hasNestedRows && shouldShowNestedRowsBooleanResult.value && props.rowData?.children?.length > 0 ); const isLoadingNestedRows = computed( () => !props.isNestedRow && props.hasNestedRows && shouldShowNestedRowsBooleanResult.value && (props.rowData?.children === undefined || props?.rowData?.children.length === 0) ); const shouldShowNestedContentToggle = computed( () => (props.hasNestedContent && props.shouldShowNestedContent(props.rowData)) || (props.hasNestedRows && shouldShowNestedRowsBooleanResult.value && !props.isNestedRow) ); const ariaRowAttributes = computed<HTMLAttributes>(() => { if (props.hasNestedContent || props.hasNestedRows) { const isNestedContentShownBooleanishVal = isNestedContentShown.value.toString() as Booleanish; return { 'aria-expanded': shouldShowNestedContentToggle.value ? isNestedContentShownBooleanishVal : undefined, 'aria-level': props.isNestedRow ? '2' : '1', 'aria-posinset': props.rowIndex + 1, 'aria-setsize': props.rowSetSize, }; } return { 'aria-rowindex': props.rowIndex + 1 }; }); const nestedContentContainerDivAriaAttributes = computed<HTMLAttributes>(() => { if (isLoadingNestedRows.value) { return { 'aria-busy': 'true', 'aria-live': 'polite', }; } if ( shouldShowRenderNestedRows.value || !props.hasNestedContent || !props.shouldShowNestedContent(props.rowData) ) { return undefined; } return { 'aria-level': props.isNestedRow ? '3' : '2', 'aria-posinset': '1', 'aria-setsize': '1', id: nestedContentSlotId, role: 'row', tabindex: '-1', }; }); const nestedRowIds = computed(() => { if (shouldShowRenderNestedRows.value) { return props.rowData?.children?.reduce((acc, _val, index) => { acc[index] = generateUid('nested-row'); return acc; }, {}); } return []; }); const allNestedRowIds = computed(() => { if (!isNestedContentShown.value) { return undefined; } if (props.hasNestedContent && props.shouldShowNestedContent(props.rowData)) { return [nestedContentSlotId]; } if (shouldShowRenderNestedRows.value && Object.keys(nestedRowIds.value)?.length > 0) { const generatedRowIds = Object.values(nestedRowIds.value); if (generatedRowIds?.length > 0) { return generatedRowIds; } } return []; }); const hasUnSelectedNestedRows = computed(() => { const childrendDataItemIds = props.rowData?.children?.map(nestedItem => props.getDataItemId(nestedItem)); if (childrendDataItemIds?.length > 0) { const everyChildIsSelected = childrendDataItemIds.every(childId => props?.selectedItems?.includes(childId)); const someItemsAreSelected = childrendDataItemIds.some(childId => props?.selectedItems?.includes(childId)); return !everyChildIsSelected && someItemsAreSelected; } return false; }); // Methods const registerRowCellRef = (el: Element | ComponentPublicInstance, field: string) => { // We always know this will be used on a DataGridCell component so we can cast it const gridCellComponent = el as InstanceType<typeof DataGridCell>; if (el !== null && gridCellComponent?.gridcellRef) { gridcellRefs.value[field] = gridCellComponent.gridcellRef as HTMLDivElement; } }; const registerNestedRowRef = (el: Element | ComponentPublicInstance, childRowIndex: number) => { if (el !== null) { nestedRowRefs.value[childRowIndex] = el as InstanceType<typeof DataGridRow>; } }; const getRowId = (childRowIndex: number) => { return nestedRowIds.value[childRowIndex]; }; const getRowAriaSelectedValue = (row: BentoDatagridDataItem): Booleanish => { if (!props.selectable) { return null; } const id = props.getDataItemId(row); return props.selectedItems.includes(id).toString() as Booleanish; }; const emitRowClick = (rowData: BentoDatagridDataItem, event: MouseEvent | KeyboardEvent) => { emit('row-click', rowData, event); }; /** * Handles row clicks when the `allowRowClicks` prop is true. * * This method also prevents the `row-click` event from firing if the click * originated from an interactive element (like a button, input, or link) * within the row. This is to avoid unintended behavior when a user * interacts with controls inside a row. * * @param event The mouse or keyboard event from the click or key press. */ const onRowClick = (event: MouseEvent | KeyboardEvent) => { // Only proceed if allowRowClicks is true if (!props.allowRowClicks) { return; } let isEventTargetInteractiveElement = false; const dataGridRowInteractiveTypes = ['button', 'input', 'a']; for (let i = 0; i < dataGridRowInteractiveTypes.length; i++) { if ((event?.target as HTMLElement)?.closest(dataGridRowInteractiveTypes[i])) { isEventTargetInteractiveElement = true; break; } } // Only trigger if element is NOT interactive if (!isEventTargetInteractiveElement) { emitRowClick(props.rowData, event); } }; const onRowSelect = (value: Array<BentoDatagridDataItemId>) => { emit('select-row', value, props.rowData); }; const onNestedRowSelect = (rowData: BentoDatagridDataItem, value: Array<BentoDatagridDataItemId>) => { emit('select-nested-row', value, props.rowData, rowData); }; /** * Sets the state to indicate that a row action menu is open. This is used to apply a higher * z-index to the row's action container, ensuring the menu displays correctly above * subsequent rows. */ const onRowActionMenuOpen = () => { isRowActionMenuOpen.value = true; }; /** * Resets the state when a row action menu is closed, removing the elevated z-index from the * row's action container. */ const onRowActionMenuClose = () => { isRowActionMenuOpen.value = false; }; /** * When the row actions cell is focused, determine navigation direction and focus * the appropriate interactive child. Backward navigation (Shift+Tab) focuses the * last child; forward navigation focuses the first. */ const onRowActionsCellFocus = (e: FocusEvent) => { if (isExitingRowActionInteraction.value) { isExitingRowActionInteraction.value = false; return; } const container = getRowActionsElement(); if (!container || !focusableItems.value?.length) { return; } const isBackwardNavigation = isRelatedTargetAfterInDOM(e.relatedTarget as HTMLElement, container); const lastIndex = focusableItems.value.length - 1; const targetItem = isBackwardNavigation ? focusableItems.value[lastIndex] : focusableItems.value[0]; makeAllInteractiveChildrenFocusable([targetItem]); focusOnInteractiveChild(container, { last: isBackwardNavigation }); }; /** * When focus leaves the row actions cell, make all interactive children unfocusable. * This ensures that tabbing through the grid skips over the individual actions within the cell. */ const onRowActionsCellFocusOut = () => { const container = getRowActionsElement(); if (container) { makeAllInteractiveChildrenUnFocusable(container); } }; /** * Handles keyboard navigation within the row actions cell. * Allows using left and right arrow keys to move between interactive elements (e.g., buttons) in the cell. * @param e The keyboard event. */ const onRowActionsCellOnKeyDown = (e: KeyboardEvent) => { const container = getRowActionsElement(); // Escape returns focus to the gridcell container if (e.key === EventKey.ESCAPE) { if (container) { makeAllInteractiveChildrenUnFocusable(container); } isExitingRowActionInteraction.value = true; container?.focus(); e.stopPropagation(); e.preventDefault(); return; } if (e.key !== EventKey.TAB && container) { makeAllInteractiveChildrenUnFocusable(container); } if ( e.key === EventKey.ARROW_RIGHT && focusableItems.value?.length > 1 && focusableItems.value[0].node === document.activeElement ) { if (container) { makeAllInteractiveChildrenUnFocusable(container); } focusableItems.value[1].node.setAttribute('tabindex', '0'); focusableItems.value[1].node.focus(); e.stopPropagation(); e.preventDefault(); } else if ( e.key === EventKey.ARROW_LEFT && focusableItems.value?.length > 1 && focusableItems.value[1].node === document.activeElement ) { if (container) { makeAllInteractiveChildrenUnFocusable(container); } focusableItems.value[0].node.setAttribute('tabindex', '0'); focusableItems.value[0].node.focus(); e.stopPropagation(); e.preventDefault(); } }; // Cell and Columns index calculation const sumColumnAmountsInColumnSets = () => props.columnSets.reduce((total, { columnSet }) => total + columnSet.length, 0); const getRowActionsCellIndex = () => sumColumnAmountsInColumnSets() * props.rowIndex + sumColumnAmountsInColumnSets(); // Nested content expanding logic const { updateContentHeight, contentHeight } = useExpandableContentHeight(nestedContentRef, isNestedContentShown); const nestedContentStyles = computed(() => { if (props.hasNestedContent || props.hasNestedRows) { // calculate the padding by getting the action column width and the first column left padding: const paddingLeft = shouldShowRenderNestedRows.value || isLoadingNestedRows.value ? 0 : gridcellRefs.value[DATAGRID_ACTIONS_COLUMN_FIELD_NAME].offsetWidth + DATAGRID_CELL_OFFSET_PADDING; return { paddingLeft: `${paddingLeft}px`, 'max-height': (props.hasNestedContent || props.hasNestedRows) && contentHeight.value && !contentHeight?.value?.includes('null') ? contentHeight.value : 0, }; } return undefined; }); watch( () => nestedContentRef.value, () => { let observer: ResizeObserver | null = null; if (!observer && nestedContentRef.value) { observer = observeSizeOfElement(nestedContentRef.value, () => { if (nestedContentRef.value) { updateContentHeight(); } }); } } ); const nestedContentToggled = () => { isNestedContentShown.value = !isNestedContentShown.value; updateContentHeight(); emit('nested-content-toggled', props.rowData); }; watch( () => props.rowData?.children, (_newChildren, oldChildren) => { if (props?.rowData?.children?.length > 0 && oldChildren === undefined && !isNestedContentShown.value) { updateContentHeight(); } } ); const isSmallContainer: ComputedRef<boolean> = inject( DATA_GRID_SMALL_CONTAINER, computed(() => false) ); const isHighlightedRow = row => props.highlightedRows.includes(props.getDataItemId(row)); const isSelectedRow = row => props.selectedItems.includes(props.getDataItemId(row)); // Styling const conditionalRowClasses = (row: BentoDatagridDataItem, rowIndex: number) => ({ 'b-data-grid-row-container__row--disabled': props.isDataItemDisabled(row), 'b-data-grid-row-container__row--row-clicks-enabled': props.allowRowClicks, 'b-data-grid-row-container__row--highlighted': isHighlightedRow(row), 'b-data-grid-row-container__row--selected': isSelectedRow(row), 'b-data-grid-row-container__row--hovered': props.hoveredRowIndex === rowIndex, 'b-data-grid-row-container__row--is-last-child': props.isRowLastChild && !isNestedContentShown.value, 'b-data-grid-row-container__row--nested-row': props.isNestedRow, 'b-data-grid-row-container__row--zebra': props.isRowEven, }); const conditionalRowSetClasses = (isFrozenRowSet: boolean, isSmallContainer: boolean) => ({ 'b-data-grid-row-container__row-cell-set--frozen': isFrozenRowSet && !isSmallContainer, [`b-data-grid-row-container__row-cell-set--${BentoDatagridStyleState.OVERFLOW_SHADOW_LEFT}`]: !isSmallContainer && isFrozenRowSet && props.hasLeftOverflow, 'b-data-grid-row-container__row-cell-set--frozen-separator': !isSmallContainer && isFrozenRowSet && props.showFrozenColumnsSeparator, 'b-data-grid-row-container__row-cell-set--unfrozen': !isFrozenRowSet || isSmallContainer, 'b-data-grid-row-container__row-cell-set--is-measuring': props.isMeasuringColumnWidths, }); const conditionalNestedRowClasses = computed(() => ({ 'b-data-grid-row-container__row-nested-slot--is-last-child': props.isRowLastChild, 'b-data-grid-row-container__row-nested-slot': !shouldShowRenderNestedRows.value && props.shouldShowNestedContent(props.rowData), })); const conditionalRowActionsContainerClasses = computed(() => ({ 'b-data-grid-row-container__row-actions-container--row-actions-menu-open': isRowActionMenuOpen.value, })); const computedRowActionsStyles = computed(() => ({ minWidth: `${props.rowActionsMaximumWidth}px`, })); // Exposed refs defineExpose({ gridcellRefs, isNestedContentShown, nestedRowRefs, rowActionsRef, }); </script> <script lang="ts"> /** * Internal component to display the BentoDataGrid data rows containig the data row cells. * * @example * * export default { * components: { DataGridRow }, * template: ` * <data-grid-row * :ref="el => registerRowCellRefs(el, rowIndex)" * :style="gridColumnWidthStyle" * :allow-row-clicks="allowRowClicks" * :column-sets="columnSets" * :condensed="condensed" * :dragging-columns="draggingColumns" * :dragging-events-for-row-cell="draggingEventsForRowCell" * :get-data-item-id="getDataItemId" * :has-left-overflow="hasLeftOverflow" * :highlighted-rows="highlightedRows" * :hovered-row-index="hoveredRowIndex" * :hovered-over-column-state="hoveredOverColumnState" * :is-data-item-disabled="isDataItemDisabled" * :is-measuring-column-widths="isMeasuringColumnWidths" * :row-data="rowData" * :row-index="rowIndex" * :show-frozen-columns-separator="showFrozenColumnsSeparator" * :selectable="selectable" * :selected-items="selectedItems" * @cell-hovered="onCellHover" * @row-click="onRowClick" * @on-frozen-columns-key-press="updateScrollPositionOnFrozenColumnsNavigation" * @select-row="onRowSelect" * > * <template v-for="(_, innerSlot) of slots" #[innerSlot]="scope"> * <slot :name="innerSlot" v-bind="scope" /> * </template> * </data-grid-row> * ` */ export default { name: 'data-grid-row', }; </script> <style lang="scss" scoped src="./data-grid-row.scss" />
|
|
1
|
+
<template> <div class="b-data-grid-row-container" role="presentation"> <div v-bind="ariaRowAttributes" :id="rowId?.toString()" :key="rowIndex" class="b-data-grid-row-container__row" :class="conditionalRowClasses(rowData, rowIndex)" role="row" :aria-selected="getRowAriaSelectedValue(rowData)" @click="onRowClick" @keyup.enter="onRowClick" @keyup.space="onRowClick" > <template v-for="({ columnSet, id, isFrozenColumns }, columnSetIndex) of columnSets"> <div v-if="columnSet?.length" :key="`data-grid-rows-${id}`" class="b-data-grid-row-container__cells" :class="conditionalRowSetClasses(isFrozenColumns, isSmallContainer)" :data-testid="`data-grid-${id}-rows`" > <data-grid-cell v-for="(column, colIndex) in columnSet" :key="`cell-${column.field}`" :ref="el => registerRowCellRef(el, column.field)" class="b-data-grid-row-container__cell" :column="column" :column-set-index="columnSetIndex" :col-index="colIndex" :column-sets="columnSets" :condensed="condensed" :dragging-columns="draggingColumns" :dragging-events-for-row-cell="draggingEventsForRowCell" :get-data-item-id="getDataItemId" :has-zebra-background="isRowEven" :is-highlighted="isHighlightedRow(rowData)" :is-selected="isSelectedRow(rowData)" :hovered-over-column-state="hoveredOverColumnState" :is-data-item-disabled="isDataItemDisabled" :is-measuring-column-widths="isMeasuringColumnWidths" :is-nested-content-expanded="isNestedContentShown" :nested-content="shouldShowNestedContentToggle" :nested-rows-element-ids="allNestedRowIds" :row-data="rowData" :row-index="rowIndex" :selection-is-indeterminate="hasUnSelectedNestedRows" :selectable="selectable" :selected-items="selectedItems" @cell-hovered="emit('cell-hovered', $event)" @cell-focus="emit('cell-focus', $event)" @on-frozen-columns-key-press="emit('on-frozen-columns-key-press', $event)" @select-row="onRowSelect" @nested-content-toggled="nestedContentToggled" > <template v-for="(_, innerSlot) of slots" #[innerSlot]="scope"> <slot :name="innerSlot" v-bind="scope" /> </template> </data-grid-cell> </div> </template> <div v-if="rowActions" ref="rowActionsRef" :aria-colindex="sumColumnAmountsInColumnSets().toString()" class="b-data-grid-row-container__row-actions-container" :class="conditionalRowActionsContainerClasses" :style="computedRowActionsStyles" :data-cellindex="getRowActionsCellIndex()" tabindex="-1" role="gridcell" @focus="onRowActionsCellFocus" @focusOut="onRowActionsCellFocusOut" @keydown="onRowActionsCellOnKeyDown" > <data-grid-cell-actions class="b-data-grid-row-container__row-actions" disable-arrow-down-opening-menu :cell-actions="rowActions" :has-right-overflow="hasRightOverflow" :row-data-item="rowData" :has-zebra-background="isRowEven" :is-highlighted="isHighlightedRow(rowData)" :is-selected="isSelectedRow(rowData)" teleport-menu @menu-open="onRowActionMenuOpen" @menu-close="onRowActionMenuClose" /> </div> </div> <Transition v-if=" (hasNestedContent && hasSlot('nested-slot') && shouldShowNestedContent(rowData)) || (hasNestedRows && shouldShowNestedRowsBooleanResult) " :key="`${rowIndex}-nest-content`" name="b-data-grid-row-container__animation--nested-content" > <div v-if="isNestedContentShown && shouldShowRenderNestedRows" :class="conditionalNestedRowClasses" data-testid="nested-content-container" :style="nestedContentStyles" role="presentation" > <div ref="nestedContentRef"> <div v-if="isLoadingNestedRows" class="b-data-grid-row-container__row-nested-rows-loading-container" > <bento-loading-indicator /> </div> <data-grid-row v-for="(childRowData, childRowIndex) in rowData.children" v-bind="props" :id="getRowId(childRowIndex)" :key="`child-${childRowIndex}`" :ref="el => registerNestedRowRef(el, childRowIndex)" :row-data="childRowData" :row-index="childRowIndex" :row-set-size="rowData.children.length" is-nested-row @cell-hovered="emit('cell-hovered', $event)" @cell-focus="emit('cell-focus', $event)" @row-click="emitRowClick" @on-frozen-columns-key-press="emit('on-frozen-columns-key-press', $event)" @select-row="onNestedRowSelect(childRowData, $event)" > <template v-for="(_, innerSlot) of slots" #[innerSlot]="scope"> <slot :name="innerSlot" v-bind="scope" /> </template> </data-grid-row> </div> </div> <div v-else-if="isNestedContentShown" v-bind="nestedContentContainerDivAriaAttributes" :class="conditionalNestedRowClasses" data-testid="nested-content-container" :style="nestedContentStyles" role="row" tabindex="-1" > <div ref="nestedContentRef" role="gridcell"> <div v-if="isLoadingNestedRows" class="b-data-grid-row-container__row-nested-rows-loading-container" > <bento-loading-indicator /> </div> <div v-else-if="hasSlot('nested-slot') && shouldShowNestedContent(rowData)"> <slot :item="rowData" name="nested-slot" /> </div> </div> </div> </Transition> </div> </template> <script setup lang="ts"> import { type ComponentPublicInstance, computed, type ComputedRef, inject, onMounted, type PropType, ref, useSlots, watch, } from 'vue'; import { BentoLoadingIndicator } from '@/components/loading-indicator'; import { focusOnInteractiveChild, makeAllInteractiveChildrenFocusable, makeAllInteractiveChildrenUnFocusable, } from '@/components/data-grid/utils'; import DataGridRow from '@/components/data-grid/components/data-grid-row/data-grid-row.vue'; import DataGridCell from '@/components/data-grid/components/data-grid-cell/data-grid-cell.vue'; import DataGridCellActions from '@/components/data-grid/components/data-grid-cell-actions/data-grid-cell-actions.vue'; // Types and enums import { type BentoColumn, type BentoDatagridDataItem, type BentoDatagridDataItemId, type BentoDatagridGetDataItemIdFn, type BentoDatagridIsDataItemDisabledFn, type BentoDatagridShouldShowNestedContentFn, type BentoDatagridShouldShowNestedRowsFn, BentoDatagridStyleState, } from '@/components/data-grid/data-grid.types'; import type { DataGridCellColumnSet, DataGridCellDragEventsForColumn, } from '../data-grid-cell/data-grid-cell.types'; import type { BentoDatagridDraggingColumnState } from '../data-grid-columns/data-grid-columns.types'; import type { DatagridColumnHoveringState } from '@/components/data-grid/composables/use-column-hovering'; // Translations import { type Booleanish } from '@/types/prop-types'; import { useHasSlot } from '@/composables/use-has-slot/use-has-slot'; import { useExpandableContentHeight } from '@/composables/use-expandable-content-height/use-expandable-content-height'; import { observeSizeOfElement } from '@/utils/ts/resize'; import { DATAGRID_ACTIONS_COLUMN_FIELD_NAME, DATAGRID_CELL_OFFSET_PADDING, } from '@/components/data-grid/composables/use-calculate-column-width/use-calculate-column-width'; import { generateUid } from '@/core/utils/ts'; import { type BentoDataGridRowActionsProp } from '@/components/data-grid/components/data-grid-cell-actions/data-grid-cell-actions.types'; import type { HTMLAttributes } from 'vue/types/jsx'; import { EventKey } from '@/types/utils-events'; import { DATA_GRID_SMALL_CONTAINER } from '@/components/data-grid/data-grid.keys'; const slots = useSlots(); const hasSlot = useHasSlot(slots); const props = defineProps({ /** * Row actions property that generates which button to show in row actions area */ rowActions: { type: [Function, Array] as PropType<BentoDataGridRowActionsProp>, default: undefined, }, /** * When true, will emit a 'rowClick' event when a row is clicked. Will add cursor: pointer to rows. */ allowRowClicks: { type: Boolean, default: false }, /** * The columns sets including */ columnSets: { type: Array as PropType<Array<DataGridCellColumnSet>>, required: true, }, /** * Sets the row cells to the condensed padding state. */ condensed: { type: Boolean, default: false, }, /** * Columns being dragged, contains dragging column and a target column */ draggingColumns: { type: Object as PropType<BentoDatagridDraggingColumnState>, default: undefined, }, /** * Dragging event handlers for each row cell */ draggingEventsForRowCell: { type: Function as PropType<DataGridCellDragEventsForColumn>, required: true, }, /** * A customisable method to retrieve a unique identifier for a data item in the array of data supplied. * This is used selecting and performance reasons and must exist in each data item. */ getDataItemId: { type: Function as PropType<BentoDatagridGetDataItemIdFn>, default: (item: BentoDatagridDataItem) => item.id, }, /** * Set to true if the datagrid has been scrolled and the left side has overflowing space. */ hasLeftOverflow: { type: Boolean, default: false, }, /** * Enables the nested content slot functionality. */ hasNestedContent: { type: Boolean, default: false, }, /** * Enables the nested content slot functionality. */ hasNestedRows: { type: Boolean, default: false, }, /** * Set to true if the datagrid has been scrolled and the right side has overflowing space. */ hasRightOverflow: { type: Boolean, default: false, }, /** * Highlighted row IDs. */ highlightedRows: { type: Array as PropType<Array<BentoDatagridDataItemId>>, default: () => [], }, /** * The row index of a currently hovered row. */ hoveredRowIndex: { type: Number, default: -1, }, /** * The column hovered over state and whether the hovered over column should show a border right or left. */ hoveredOverColumnState: { type: Object as PropType<DatagridColumnHoveringState>, required: true, }, /** * A customisable method to determine if a row is not selectable. * The default is that every row is selectable. */ isDataItemDisabled: { type: Function as PropType<BentoDatagridIsDataItemDisabledFn>, default: () => false, }, /** * If the row is of an even number. * Rows are 0th based index e.g. index 0 is an odd-numbered row. */ isRowEven: { type: Boolean, default: false, }, /** * If the row is the last child in it's parent container. */ isRowLastChild: { type: Boolean, default: false, }, /** * If columns are being measured for the autoWidth functionality. */ isMeasuringColumnWidths: { type: Boolean, default: false, }, /** * Determines if this row is nested inside another row. */ isNestedRow: { type: Boolean, default: false, }, /** * The maximum width of the row actions in all rows */ rowActionsMaximumWidth: { type: Number, default: 0, }, /** * The row data object */ rowData: { type: Object as PropType<BentoDatagridDataItem>, required: true, }, /** * The row index. */ rowIndex: { type: Number, required: true, }, /** * The row ID to be using for the ID attribute. */ rowId: { type: Number, default: undefined, }, /** * The number of rows that this row is within. */ rowSetSize: { type: Number, default: 0, }, /** * Determines whether to render frozen seperator on the row. */ showFrozenColumnsSeparator: { type: Boolean, default: false, }, /** * Determines if the row selection is enabled. */ selectable: { type: Boolean, default: false, }, /** * The array of IDs for the selected rows. */ selectedItems: { type: Array as PropType<Array<BentoDatagridDataItemId>>, required: true, }, /** * Enables / Disables the nested-content slot */ shouldShowNestedContent: { type: Function as PropType<BentoDatagridShouldShowNestedContentFn>, default: (_item: BentoDatagridDataItem) => true, }, /** * Returns the nested rows for a given BentoDatagridDataItem. */ shouldShowNestedRows: { type: Function as PropType<BentoDatagridShouldShowNestedRowsFn>, default: (_item: BentoDatagridDataItem) => true, }, }); const emit = defineEmits<{ /** * Emitted when a cell has been hovered over. This is used to keep the row in a hovered state when hovering over a menu. */ (e: 'cell-hovered', value: number); /** * Emitted when an a frozen column cell fires a key up or down event. */ (e: 'on-frozen-columns-key-press', event: Event); /** * Emitted when a row has been selected. */ (e: 'select-row', value: Array<BentoDatagridDataItemId>, rowData: BentoDatagridDataItem); /** * Emitted when a nested row has been selected. */ ( e: 'select-nested-row', value: Array<BentoDatagridDataItemId>, parentRowData: BentoDatagridDataItem, rowData: BentoDatagridDataItem ); /** * Emitted when a row has been clicked. */ (e: 'row-click', rowData: BentoDatagridDataItem, event: MouseEvent | KeyboardEvent); /** * Emitted when a row with nested content is toggled */ (e: 'nested-content-toggled', rowData: BentoDatagridDataItem); /** * Emitted when a row cell is focused. */ ( e: 'cell-focus', value: { rowData: BentoDatagridDataItem; column: BentoColumn; rowIndex: number; colIndex: number } ); }>(); /** * Checks if the relatedTarget element appears after the reference element in DOM order. * Used to detect backward navigation (Shift+Tab). */ const isRelatedTargetAfterInDOM = (relatedTarget: HTMLElement | null, referenceEl: HTMLElement): boolean => { if (!relatedTarget || !referenceEl) { return false; } return !!(referenceEl.compareDocumentPosition(relatedTarget) & Node.DOCUMENT_POSITION_FOLLOWING); }; // Refs const gridcellRefs = ref<{ [key: string]: HTMLDivElement }>({}); const isNestedContentShown = ref(false); const nestedContentRef = ref(); const rowActionsRef = ref(); const nestedRowRefs = ref<Array<InstanceType<typeof DataGridRow>>>([]); const nestedContentSlotId = generateUid('nested-content-slot'); const isRowActionMenuOpen = ref(false); const isExitingRowActionInteraction = ref(false); const focusableItems = ref<Array<{ node: HTMLElement }>>(); /** * Returns the row-action container element, handling the case where Vue 2 * may wrap refs inside a v-for as an array. */ const getRowActionsElement = (): HTMLElement | undefined => { const val = rowActionsRef.value; return Array.isArray(val) ? val[0] : val; }; onMounted(() => { const container = getRowActionsElement(); if (container) { focusableItems.value = makeAllInteractiveChildrenUnFocusable(container); } }); const shouldShowNestedRowsBooleanResult = computed(() => props.shouldShowNestedRows(props.rowData)); const shouldShowRenderNestedRows = computed( () => !props.isNestedRow && props.hasNestedRows && shouldShowNestedRowsBooleanResult.value && props.rowData?.children?.length > 0 ); const isLoadingNestedRows = computed( () => !props.isNestedRow && props.hasNestedRows && shouldShowNestedRowsBooleanResult.value && (props.rowData?.children === undefined || props?.rowData?.children.length === 0) ); const shouldShowNestedContentToggle = computed( () => (props.hasNestedContent && props.shouldShowNestedContent(props.rowData)) || (props.hasNestedRows && shouldShowNestedRowsBooleanResult.value && !props.isNestedRow) ); const ariaRowAttributes = computed<HTMLAttributes>(() => { if (props.hasNestedContent || props.hasNestedRows) { const isNestedContentShownBooleanishVal = isNestedContentShown.value.toString() as Booleanish; return { 'aria-expanded': shouldShowNestedContentToggle.value ? isNestedContentShownBooleanishVal : undefined, 'aria-level': props.isNestedRow ? '2' : '1', 'aria-posinset': props.rowIndex + 1, 'aria-setsize': props.rowSetSize, }; } return { 'aria-rowindex': props.rowIndex + 1 }; }); const nestedContentContainerDivAriaAttributes = computed<HTMLAttributes>(() => { if (isLoadingNestedRows.value) { return { 'aria-busy': 'true', 'aria-live': 'polite', }; } if ( shouldShowRenderNestedRows.value || !props.hasNestedContent || !props.shouldShowNestedContent(props.rowData) ) { return undefined; } return { 'aria-level': props.isNestedRow ? '3' : '2', 'aria-posinset': '1', 'aria-setsize': '1', id: nestedContentSlotId, role: 'row', tabindex: '-1', }; }); const nestedRowIds = computed(() => { if (shouldShowRenderNestedRows.value) { return props.rowData?.children?.reduce((acc, _val, index) => { acc[index] = generateUid('nested-row'); return acc; }, {}); } return []; }); const allNestedRowIds = computed(() => { if (!isNestedContentShown.value) { return undefined; } if (props.hasNestedContent && props.shouldShowNestedContent(props.rowData)) { return [nestedContentSlotId]; } if (shouldShowRenderNestedRows.value && Object.keys(nestedRowIds.value)?.length > 0) { const generatedRowIds = Object.values(nestedRowIds.value); if (generatedRowIds?.length > 0) { return generatedRowIds; } } return []; }); const hasUnSelectedNestedRows = computed(() => { const childrendDataItemIds = props.rowData?.children?.map(nestedItem => props.getDataItemId(nestedItem)); if (childrendDataItemIds?.length > 0) { const everyChildIsSelected = childrendDataItemIds.every(childId => props?.selectedItems?.includes(childId)); const someItemsAreSelected = childrendDataItemIds.some(childId => props?.selectedItems?.includes(childId)); return !everyChildIsSelected && someItemsAreSelected; } return false; }); // Methods const registerRowCellRef = (el: Element | ComponentPublicInstance, field: string) => { // We always know this will be used on a DataGridCell component so we can cast it const gridCellComponent = el as InstanceType<typeof DataGridCell>; if (el !== null && gridCellComponent?.gridcellRef) { gridcellRefs.value[field] = gridCellComponent.gridcellRef as HTMLDivElement; } }; const registerNestedRowRef = (el: Element | ComponentPublicInstance, childRowIndex: number) => { if (el !== null) { nestedRowRefs.value[childRowIndex] = el as InstanceType<typeof DataGridRow>; } }; const getRowId = (childRowIndex: number) => { return nestedRowIds.value[childRowIndex]; }; const getRowAriaSelectedValue = (row: BentoDatagridDataItem): Booleanish => { if (!props.selectable) { return null; } const id = props.getDataItemId(row); return props.selectedItems.includes(id).toString() as Booleanish; }; const emitRowClick = (rowData: BentoDatagridDataItem, event: MouseEvent | KeyboardEvent) => { emit('row-click', rowData, event); }; /** * Handles row clicks when the `allowRowClicks` prop is true. * * This method also prevents the `row-click` event from firing if the click * originated from an interactive element (like a button, input, or link) * within the row. This is to avoid unintended behavior when a user * interacts with controls inside a row. * * @param event The mouse or keyboard event from the click or key press. */ const onRowClick = (event: MouseEvent | KeyboardEvent) => { // Only proceed if allowRowClicks is true if (!props.allowRowClicks) { return; } let isEventTargetInteractiveElement = false; const dataGridRowInteractiveTypes = ['button', 'input', 'a']; for (let i = 0; i < dataGridRowInteractiveTypes.length; i++) { if ((event?.target as HTMLElement)?.closest(dataGridRowInteractiveTypes[i])) { isEventTargetInteractiveElement = true; break; } } // Only trigger if element is NOT interactive if (!isEventTargetInteractiveElement) { emitRowClick(props.rowData, event); } }; const onRowSelect = (value: Array<BentoDatagridDataItemId>) => { emit('select-row', value, props.rowData); }; const onNestedRowSelect = (rowData: BentoDatagridDataItem, value: Array<BentoDatagridDataItemId>) => { emit('select-nested-row', value, props.rowData, rowData); }; /** * Sets the state to indicate that a row action menu is open. This is used to apply a higher * z-index to the row's action container, ensuring the menu displays correctly above * subsequent rows. */ const onRowActionMenuOpen = () => { isRowActionMenuOpen.value = true; }; /** * Resets the state when a row action menu is closed, removing the elevated z-index from the * row's action container. */ const onRowActionMenuClose = () => { isRowActionMenuOpen.value = false; }; /** * When the row actions cell is focused, determine navigation direction and focus * the appropriate interactive child. Backward navigation (Shift+Tab) focuses the * last child; forward navigation focuses the first. */ const onRowActionsCellFocus = (e: FocusEvent) => { if (isExitingRowActionInteraction.value) { isExitingRowActionInteraction.value = false; return; } const container = getRowActionsElement(); if (!container || !focusableItems.value?.length) { return; } const isBackwardNavigation = isRelatedTargetAfterInDOM(e.relatedTarget as HTMLElement, container); const lastIndex = focusableItems.value.length - 1; const targetItem = isBackwardNavigation ? focusableItems.value[lastIndex] : focusableItems.value[0]; makeAllInteractiveChildrenFocusable([targetItem]); focusOnInteractiveChild(container, { last: isBackwardNavigation }); }; /** * When focus leaves the row actions cell, make all interactive children unfocusable. * This ensures that tabbing through the grid skips over the individual actions within the cell. */ const onRowActionsCellFocusOut = () => { const container = getRowActionsElement(); if (container) { makeAllInteractiveChildrenUnFocusable(container); } }; /** * Handles keyboard navigation within the row actions cell. * Allows using left and right arrow keys to move between interactive elements (e.g., buttons) in the cell. * @param e The keyboard event. */ const onRowActionsCellOnKeyDown = (e: KeyboardEvent) => { const container = getRowActionsElement(); // Escape returns focus to the gridcell container if (e.key === EventKey.ESCAPE) { if (container) { makeAllInteractiveChildrenUnFocusable(container); } isExitingRowActionInteraction.value = true; container?.focus(); e.stopPropagation(); e.preventDefault(); return; } if (e.key !== EventKey.TAB && container) { makeAllInteractiveChildrenUnFocusable(container); } if ( e.key === EventKey.ARROW_RIGHT && focusableItems.value?.length > 1 && focusableItems.value[0].node === document.activeElement ) { if (container) { makeAllInteractiveChildrenUnFocusable(container); } focusableItems.value[1].node.setAttribute('tabindex', '0'); focusableItems.value[1].node.focus(); e.stopPropagation(); e.preventDefault(); } else if ( e.key === EventKey.ARROW_LEFT && focusableItems.value?.length > 1 && focusableItems.value[1].node === document.activeElement ) { if (container) { makeAllInteractiveChildrenUnFocusable(container); } focusableItems.value[0].node.setAttribute('tabindex', '0'); focusableItems.value[0].node.focus(); e.stopPropagation(); e.preventDefault(); } }; // Cell and Columns index calculation const sumColumnAmountsInColumnSets = () => props.columnSets.reduce((total, { columnSet }) => total + columnSet.length, 0); const getRowActionsCellIndex = () => sumColumnAmountsInColumnSets() * props.rowIndex + sumColumnAmountsInColumnSets(); // Nested content expanding logic const { updateContentHeight, contentHeight } = useExpandableContentHeight(nestedContentRef, isNestedContentShown); const nestedContentStyles = computed(() => { if (props.hasNestedContent || props.hasNestedRows) { // calculate the padding by getting the action column width and the first column left padding: const paddingLeft = shouldShowRenderNestedRows.value || isLoadingNestedRows.value ? 0 : gridcellRefs.value[DATAGRID_ACTIONS_COLUMN_FIELD_NAME].offsetWidth + DATAGRID_CELL_OFFSET_PADDING; return { paddingLeft: `${paddingLeft}px`, 'max-height': (props.hasNestedContent || props.hasNestedRows) && contentHeight.value && !contentHeight?.value?.includes('null') ? contentHeight.value : 0, }; } return undefined; }); watch( () => nestedContentRef.value, () => { let observer: ResizeObserver | null = null; if (!observer && nestedContentRef.value) { observer = observeSizeOfElement(nestedContentRef.value, () => { if (nestedContentRef.value) { updateContentHeight(); } }); } } ); const nestedContentToggled = () => { isNestedContentShown.value = !isNestedContentShown.value; updateContentHeight(); emit('nested-content-toggled', props.rowData); }; watch( () => props.rowData?.children, (_newChildren, oldChildren) => { if (props?.rowData?.children?.length > 0 && oldChildren === undefined && !isNestedContentShown.value) { updateContentHeight(); } } ); const isSmallContainer: ComputedRef<boolean> = inject( DATA_GRID_SMALL_CONTAINER, computed(() => false) ); const isHighlightedRow = row => props.highlightedRows.includes(props.getDataItemId(row)); const isSelectedRow = row => props.selectedItems.includes(props.getDataItemId(row)); // Styling const conditionalRowClasses = (row: BentoDatagridDataItem, rowIndex: number) => ({ 'b-data-grid-row-container__row--disabled': props.isDataItemDisabled(row), 'b-data-grid-row-container__row--row-clicks-enabled': props.allowRowClicks, 'b-data-grid-row-container__row--highlighted': isHighlightedRow(row), 'b-data-grid-row-container__row--selected': isSelectedRow(row), 'b-data-grid-row-container__row--hovered': props.hoveredRowIndex === rowIndex, 'b-data-grid-row-container__row--is-last-child': props.isRowLastChild && !isNestedContentShown.value, 'b-data-grid-row-container__row--nested-row': props.isNestedRow, 'b-data-grid-row-container__row--zebra': props.isRowEven, }); const conditionalRowSetClasses = (isFrozenRowSet: boolean, isSmallContainer: boolean) => ({ 'b-data-grid-row-container__row-cell-set--frozen': isFrozenRowSet && !isSmallContainer, [`b-data-grid-row-container__row-cell-set--${BentoDatagridStyleState.OVERFLOW_SHADOW_LEFT}`]: !isSmallContainer && isFrozenRowSet && props.hasLeftOverflow, 'b-data-grid-row-container__row-cell-set--frozen-separator': !isSmallContainer && isFrozenRowSet && props.showFrozenColumnsSeparator, 'b-data-grid-row-container__row-cell-set--unfrozen': !isFrozenRowSet || isSmallContainer, 'b-data-grid-row-container__row-cell-set--is-measuring': props.isMeasuringColumnWidths, }); const conditionalNestedRowClasses = computed(() => ({ 'b-data-grid-row-container__row-nested-slot--is-last-child': props.isRowLastChild, 'b-data-grid-row-container__row-nested-slot': !shouldShowRenderNestedRows.value && props.shouldShowNestedContent(props.rowData), })); const conditionalRowActionsContainerClasses = computed(() => ({ 'b-data-grid-row-container__row-actions-container--row-actions-menu-open': isRowActionMenuOpen.value, })); const computedRowActionsStyles = computed(() => ({ minWidth: `${props.rowActionsMaximumWidth}px`, })); // Exposed refs defineExpose({ gridcellRefs, isNestedContentShown, nestedRowRefs, rowActionsRef, }); </script> <script lang="ts"> /** * Internal component to display the BentoDataGrid data rows containig the data row cells. * * @example * * export default { * components: { DataGridRow }, * template: ` * <data-grid-row * :ref="el => registerRowCellRefs(el, rowIndex)" * :style="gridColumnWidthStyle" * :allow-row-clicks="allowRowClicks" * :column-sets="columnSets" * :condensed="condensed" * :dragging-columns="draggingColumns" * :dragging-events-for-row-cell="draggingEventsForRowCell" * :get-data-item-id="getDataItemId" * :has-left-overflow="hasLeftOverflow" * :highlighted-rows="highlightedRows" * :hovered-row-index="hoveredRowIndex" * :hovered-over-column-state="hoveredOverColumnState" * :is-data-item-disabled="isDataItemDisabled" * :is-measuring-column-widths="isMeasuringColumnWidths" * :row-data="rowData" * :row-index="rowIndex" * :show-frozen-columns-separator="showFrozenColumnsSeparator" * :selectable="selectable" * :selected-items="selectedItems" * @cell-hovered="onCellHover" * @row-click="onRowClick" * @on-frozen-columns-key-press="updateScrollPositionOnFrozenColumnsNavigation" * @select-row="onRowSelect" * > * <template v-for="(_, innerSlot) of slots" #[innerSlot]="scope"> * <slot :name="innerSlot" v-bind="scope" /> * </template> * </data-grid-row> * ` */ export default { name: 'data-grid-row', }; </script> <style lang="scss" scoped src="./data-grid-row.scss" />
|
|
@@ -305,7 +305,7 @@ You can automatically update `filter-values` using the
|
|
|
305
305
|
| `filters` | Array of filter objects. Each one of the elements in the array corresponds to a filter that will be created and rendered in the FilterBar. |
|
|
306
306
|
| `filterValues` | Array of filter values. Each one of the elements in the array corresponds to a filter's value to be set in the filter bar. |
|
|
307
307
|
| `filterSearchTerm` | String that contains the search term used to filter the data with, it is displayed in the search input of the filter bar. |
|
|
308
|
-
| `filterSearchConfig` | Sets the configuration options (`hint`, `placeholder`, `debounceTime` and `
|
|
308
|
+
| `filterSearchConfig` | Sets the configuration options (`hint`, `placeholder`, `debounceTime`, `inputFieldAriaLabel` and `width`) for the filter's search bar. |
|
|
309
309
|
|
|
310
310
|
<Canvas of={ComponentStories.Filtering} />
|
|
311
311
|
|