VListItem.mjs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. import { withDirectives as _withDirectives, mergeProps as _mergeProps, resolveDirective as _resolveDirective, createVNode as _createVNode, Fragment as _Fragment } from "vue";
  2. // Styles
  3. import "./VListItem.css";
  4. // Components
  5. import { VListItemSubtitle } from "./VListItemSubtitle.mjs";
  6. import { VListItemTitle } from "./VListItemTitle.mjs";
  7. import { VAvatar } from "../VAvatar/index.mjs";
  8. import { VDefaultsProvider } from "../VDefaultsProvider/index.mjs";
  9. import { VIcon } from "../VIcon/index.mjs"; // Composables
  10. import { useList } from "./list.mjs";
  11. import { makeBorderProps, useBorder } from "../../composables/border.mjs";
  12. import { makeComponentProps } from "../../composables/component.mjs";
  13. import { makeDensityProps, useDensity } from "../../composables/density.mjs";
  14. import { makeDimensionProps, useDimension } from "../../composables/dimensions.mjs";
  15. import { makeElevationProps, useElevation } from "../../composables/elevation.mjs";
  16. import { IconValue } from "../../composables/icons.mjs";
  17. import { useNestedItem } from "../../composables/nested/nested.mjs";
  18. import { makeRoundedProps, useRounded } from "../../composables/rounded.mjs";
  19. import { makeRouterProps, useLink } from "../../composables/router.mjs";
  20. import { makeTagProps } from "../../composables/tag.mjs";
  21. import { makeThemeProps, provideTheme } from "../../composables/theme.mjs";
  22. import { genOverlays, makeVariantProps, useVariant } from "../../composables/variant.mjs"; // Directives
  23. import { Ripple } from "../../directives/ripple/index.mjs"; // Utilities
  24. import { computed, onBeforeMount, watch } from 'vue';
  25. import { deprecate, EventProp, genericComponent, propsFactory, useRender } from "../../util/index.mjs"; // Types
  26. export const makeVListItemProps = propsFactory({
  27. active: {
  28. type: Boolean,
  29. default: undefined
  30. },
  31. activeClass: String,
  32. /* @deprecated */
  33. activeColor: String,
  34. appendAvatar: String,
  35. appendIcon: IconValue,
  36. baseColor: String,
  37. disabled: Boolean,
  38. lines: [Boolean, String],
  39. link: {
  40. type: Boolean,
  41. default: undefined
  42. },
  43. nav: Boolean,
  44. prependAvatar: String,
  45. prependIcon: IconValue,
  46. ripple: {
  47. type: [Boolean, Object],
  48. default: true
  49. },
  50. slim: Boolean,
  51. subtitle: [String, Number],
  52. title: [String, Number],
  53. value: null,
  54. onClick: EventProp(),
  55. onClickOnce: EventProp(),
  56. ...makeBorderProps(),
  57. ...makeComponentProps(),
  58. ...makeDensityProps(),
  59. ...makeDimensionProps(),
  60. ...makeElevationProps(),
  61. ...makeRoundedProps(),
  62. ...makeRouterProps(),
  63. ...makeTagProps(),
  64. ...makeThemeProps(),
  65. ...makeVariantProps({
  66. variant: 'text'
  67. })
  68. }, 'VListItem');
  69. export const VListItem = genericComponent()({
  70. name: 'VListItem',
  71. directives: {
  72. Ripple
  73. },
  74. props: makeVListItemProps(),
  75. emits: {
  76. click: e => true
  77. },
  78. setup(props, _ref) {
  79. let {
  80. attrs,
  81. slots,
  82. emit
  83. } = _ref;
  84. const link = useLink(props, attrs);
  85. const id = computed(() => props.value === undefined ? link.href.value : props.value);
  86. const {
  87. activate,
  88. isActivated,
  89. select,
  90. isOpen,
  91. isSelected,
  92. isIndeterminate,
  93. isGroupActivator,
  94. root,
  95. parent,
  96. openOnSelect,
  97. id: uid
  98. } = useNestedItem(id, false);
  99. const list = useList();
  100. const isActive = computed(() => props.active !== false && (props.active || link.isActive?.value || (root.activatable.value ? isActivated.value : isSelected.value)));
  101. const isLink = computed(() => props.link !== false && link.isLink.value);
  102. const isSelectable = computed(() => !!list && (root.selectable.value || root.activatable.value || props.value != null));
  103. const isClickable = computed(() => !props.disabled && props.link !== false && (props.link || link.isClickable.value || isSelectable.value));
  104. const roundedProps = computed(() => props.rounded || props.nav);
  105. const color = computed(() => props.color ?? props.activeColor);
  106. const variantProps = computed(() => ({
  107. color: isActive.value ? color.value ?? props.baseColor : props.baseColor,
  108. variant: props.variant
  109. }));
  110. // useNestedItem doesn't call register until beforeMount,
  111. // so this can't be an immediate watcher as we don't know parent yet
  112. watch(() => link.isActive?.value, val => {
  113. if (!val) return;
  114. handleActiveLink();
  115. });
  116. onBeforeMount(() => {
  117. if (link.isActive?.value) handleActiveLink();
  118. });
  119. function handleActiveLink() {
  120. if (parent.value != null) {
  121. root.open(parent.value, true);
  122. }
  123. openOnSelect(true);
  124. }
  125. const {
  126. themeClasses
  127. } = provideTheme(props);
  128. const {
  129. borderClasses
  130. } = useBorder(props);
  131. const {
  132. colorClasses,
  133. colorStyles,
  134. variantClasses
  135. } = useVariant(variantProps);
  136. const {
  137. densityClasses
  138. } = useDensity(props);
  139. const {
  140. dimensionStyles
  141. } = useDimension(props);
  142. const {
  143. elevationClasses
  144. } = useElevation(props);
  145. const {
  146. roundedClasses
  147. } = useRounded(roundedProps);
  148. const lineClasses = computed(() => props.lines ? `v-list-item--${props.lines}-line` : undefined);
  149. const slotProps = computed(() => ({
  150. isActive: isActive.value,
  151. select,
  152. isOpen: isOpen.value,
  153. isSelected: isSelected.value,
  154. isIndeterminate: isIndeterminate.value
  155. }));
  156. function onClick(e) {
  157. emit('click', e);
  158. if (!isClickable.value) return;
  159. link.navigate?.(e);
  160. if (isGroupActivator) return;
  161. if (root.activatable.value) {
  162. activate(!isActivated.value, e);
  163. } else if (root.selectable.value) {
  164. select(!isSelected.value, e);
  165. } else if (props.value != null) {
  166. select(!isSelected.value, e);
  167. }
  168. }
  169. function onKeyDown(e) {
  170. if (e.key === 'Enter' || e.key === ' ') {
  171. e.preventDefault();
  172. e.target.dispatchEvent(new MouseEvent('click', e));
  173. }
  174. }
  175. useRender(() => {
  176. const Tag = isLink.value ? 'a' : props.tag;
  177. const hasTitle = slots.title || props.title != null;
  178. const hasSubtitle = slots.subtitle || props.subtitle != null;
  179. const hasAppendMedia = !!(props.appendAvatar || props.appendIcon);
  180. const hasAppend = !!(hasAppendMedia || slots.append);
  181. const hasPrependMedia = !!(props.prependAvatar || props.prependIcon);
  182. const hasPrepend = !!(hasPrependMedia || slots.prepend);
  183. list?.updateHasPrepend(hasPrepend);
  184. if (props.activeColor) {
  185. deprecate('active-color', ['color', 'base-color']);
  186. }
  187. return _withDirectives(_createVNode(Tag, _mergeProps({
  188. "class": ['v-list-item', {
  189. 'v-list-item--active': isActive.value,
  190. 'v-list-item--disabled': props.disabled,
  191. 'v-list-item--link': isClickable.value,
  192. 'v-list-item--nav': props.nav,
  193. 'v-list-item--prepend': !hasPrepend && list?.hasPrepend.value,
  194. 'v-list-item--slim': props.slim,
  195. [`${props.activeClass}`]: props.activeClass && isActive.value
  196. }, themeClasses.value, borderClasses.value, colorClasses.value, densityClasses.value, elevationClasses.value, lineClasses.value, roundedClasses.value, variantClasses.value, props.class],
  197. "style": [colorStyles.value, dimensionStyles.value, props.style],
  198. "tabindex": isClickable.value ? list ? -2 : 0 : undefined,
  199. "aria-selected": isSelectable.value ? root.activatable.value ? isActivated.value : root.selectable.value ? isSelected.value : isActive.value : undefined,
  200. "onClick": onClick,
  201. "onKeydown": isClickable.value && !isLink.value && onKeyDown
  202. }, link.linkProps), {
  203. default: () => [genOverlays(isClickable.value || isActive.value, 'v-list-item'), hasPrepend && _createVNode("div", {
  204. "key": "prepend",
  205. "class": "v-list-item__prepend"
  206. }, [!slots.prepend ? _createVNode(_Fragment, null, [props.prependAvatar && _createVNode(VAvatar, {
  207. "key": "prepend-avatar",
  208. "density": props.density,
  209. "image": props.prependAvatar
  210. }, null), props.prependIcon && _createVNode(VIcon, {
  211. "key": "prepend-icon",
  212. "density": props.density,
  213. "icon": props.prependIcon
  214. }, null)]) : _createVNode(VDefaultsProvider, {
  215. "key": "prepend-defaults",
  216. "disabled": !hasPrependMedia,
  217. "defaults": {
  218. VAvatar: {
  219. density: props.density,
  220. image: props.prependAvatar
  221. },
  222. VIcon: {
  223. density: props.density,
  224. icon: props.prependIcon
  225. },
  226. VListItemAction: {
  227. start: true
  228. }
  229. }
  230. }, {
  231. default: () => [slots.prepend?.(slotProps.value)]
  232. }), _createVNode("div", {
  233. "class": "v-list-item__spacer"
  234. }, null)]), _createVNode("div", {
  235. "class": "v-list-item__content",
  236. "data-no-activator": ""
  237. }, [hasTitle && _createVNode(VListItemTitle, {
  238. "key": "title"
  239. }, {
  240. default: () => [slots.title?.({
  241. title: props.title
  242. }) ?? props.title]
  243. }), hasSubtitle && _createVNode(VListItemSubtitle, {
  244. "key": "subtitle"
  245. }, {
  246. default: () => [slots.subtitle?.({
  247. subtitle: props.subtitle
  248. }) ?? props.subtitle]
  249. }), slots.default?.(slotProps.value)]), hasAppend && _createVNode("div", {
  250. "key": "append",
  251. "class": "v-list-item__append"
  252. }, [!slots.append ? _createVNode(_Fragment, null, [props.appendIcon && _createVNode(VIcon, {
  253. "key": "append-icon",
  254. "density": props.density,
  255. "icon": props.appendIcon
  256. }, null), props.appendAvatar && _createVNode(VAvatar, {
  257. "key": "append-avatar",
  258. "density": props.density,
  259. "image": props.appendAvatar
  260. }, null)]) : _createVNode(VDefaultsProvider, {
  261. "key": "append-defaults",
  262. "disabled": !hasAppendMedia,
  263. "defaults": {
  264. VAvatar: {
  265. density: props.density,
  266. image: props.appendAvatar
  267. },
  268. VIcon: {
  269. density: props.density,
  270. icon: props.appendIcon
  271. },
  272. VListItemAction: {
  273. end: true
  274. }
  275. }
  276. }, {
  277. default: () => [slots.append?.(slotProps.value)]
  278. }), _createVNode("div", {
  279. "class": "v-list-item__spacer"
  280. }, null)])]
  281. }), [[_resolveDirective("ripple"), isClickable.value && props.ripple]]);
  282. });
  283. return {
  284. activate,
  285. isActivated,
  286. isGroupActivator,
  287. isSelected,
  288. list,
  289. select,
  290. root,
  291. id: uid
  292. };
  293. }
  294. });
  295. //# sourceMappingURL=VListItem.mjs.map