VSelectionControl.mjs 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. import { withDirectives as _withDirectives, resolveDirective as _resolveDirective, Fragment as _Fragment, createVNode as _createVNode, mergeProps as _mergeProps } from "vue";
  2. // Styles
  3. import "./VSelectionControl.css";
  4. // Components
  5. import { VIcon } from "../VIcon/index.mjs";
  6. import { VLabel } from "../VLabel/index.mjs";
  7. import { makeSelectionControlGroupProps, VSelectionControlGroupSymbol } from "../VSelectionControlGroup/VSelectionControlGroup.mjs"; // Composables
  8. import { useBackgroundColor, useTextColor } from "../../composables/color.mjs";
  9. import { makeComponentProps } from "../../composables/component.mjs";
  10. import { useDensity } from "../../composables/density.mjs";
  11. import { useProxiedModel } from "../../composables/proxiedModel.mjs"; // Directives
  12. import { Ripple } from "../../directives/ripple/index.mjs"; // Utilities
  13. import { computed, inject, nextTick, ref, shallowRef } from 'vue';
  14. import { filterInputAttrs, genericComponent, getUid, matchesSelector, propsFactory, useRender, wrapInArray } from "../../util/index.mjs"; // Types
  15. export const makeVSelectionControlProps = propsFactory({
  16. label: String,
  17. baseColor: String,
  18. trueValue: null,
  19. falseValue: null,
  20. value: null,
  21. ...makeComponentProps(),
  22. ...makeSelectionControlGroupProps()
  23. }, 'VSelectionControl');
  24. export function useSelectionControl(props) {
  25. const group = inject(VSelectionControlGroupSymbol, undefined);
  26. const {
  27. densityClasses
  28. } = useDensity(props);
  29. const modelValue = useProxiedModel(props, 'modelValue');
  30. const trueValue = computed(() => props.trueValue !== undefined ? props.trueValue : props.value !== undefined ? props.value : true);
  31. const falseValue = computed(() => props.falseValue !== undefined ? props.falseValue : false);
  32. const isMultiple = computed(() => !!props.multiple || props.multiple == null && Array.isArray(modelValue.value));
  33. const model = computed({
  34. get() {
  35. const val = group ? group.modelValue.value : modelValue.value;
  36. return isMultiple.value ? wrapInArray(val).some(v => props.valueComparator(v, trueValue.value)) : props.valueComparator(val, trueValue.value);
  37. },
  38. set(val) {
  39. if (props.readonly) return;
  40. const currentValue = val ? trueValue.value : falseValue.value;
  41. let newVal = currentValue;
  42. if (isMultiple.value) {
  43. newVal = val ? [...wrapInArray(modelValue.value), currentValue] : wrapInArray(modelValue.value).filter(item => !props.valueComparator(item, trueValue.value));
  44. }
  45. if (group) {
  46. group.modelValue.value = newVal;
  47. } else {
  48. modelValue.value = newVal;
  49. }
  50. }
  51. });
  52. const {
  53. textColorClasses,
  54. textColorStyles
  55. } = useTextColor(computed(() => {
  56. if (props.error || props.disabled) return undefined;
  57. return model.value ? props.color : props.baseColor;
  58. }));
  59. const {
  60. backgroundColorClasses,
  61. backgroundColorStyles
  62. } = useBackgroundColor(computed(() => {
  63. return model.value && !props.error && !props.disabled ? props.color : props.baseColor;
  64. }));
  65. const icon = computed(() => model.value ? props.trueIcon : props.falseIcon);
  66. return {
  67. group,
  68. densityClasses,
  69. trueValue,
  70. falseValue,
  71. model,
  72. textColorClasses,
  73. textColorStyles,
  74. backgroundColorClasses,
  75. backgroundColorStyles,
  76. icon
  77. };
  78. }
  79. export const VSelectionControl = genericComponent()({
  80. name: 'VSelectionControl',
  81. directives: {
  82. Ripple
  83. },
  84. inheritAttrs: false,
  85. props: makeVSelectionControlProps(),
  86. emits: {
  87. 'update:modelValue': value => true
  88. },
  89. setup(props, _ref) {
  90. let {
  91. attrs,
  92. slots
  93. } = _ref;
  94. const {
  95. group,
  96. densityClasses,
  97. icon,
  98. model,
  99. textColorClasses,
  100. textColorStyles,
  101. backgroundColorClasses,
  102. backgroundColorStyles,
  103. trueValue
  104. } = useSelectionControl(props);
  105. const uid = getUid();
  106. const isFocused = shallowRef(false);
  107. const isFocusVisible = shallowRef(false);
  108. const input = ref();
  109. const id = computed(() => props.id || `input-${uid}`);
  110. const isInteractive = computed(() => !props.disabled && !props.readonly);
  111. group?.onForceUpdate(() => {
  112. if (input.value) {
  113. input.value.checked = model.value;
  114. }
  115. });
  116. function onFocus(e) {
  117. if (!isInteractive.value) return;
  118. isFocused.value = true;
  119. if (matchesSelector(e.target, ':focus-visible') !== false) {
  120. isFocusVisible.value = true;
  121. }
  122. }
  123. function onBlur() {
  124. isFocused.value = false;
  125. isFocusVisible.value = false;
  126. }
  127. function onClickLabel(e) {
  128. e.stopPropagation();
  129. }
  130. function onInput(e) {
  131. if (!isInteractive.value) {
  132. if (input.value) {
  133. // model value is not updated when input is not interactive
  134. // but the internal checked state of the input is still updated,
  135. // so here it's value is restored
  136. input.value.checked = model.value;
  137. }
  138. return;
  139. }
  140. if (props.readonly && group) {
  141. nextTick(() => group.forceUpdate());
  142. }
  143. model.value = e.target.checked;
  144. }
  145. useRender(() => {
  146. const label = slots.label ? slots.label({
  147. label: props.label,
  148. props: {
  149. for: id.value
  150. }
  151. }) : props.label;
  152. const [rootAttrs, inputAttrs] = filterInputAttrs(attrs);
  153. const inputNode = _createVNode("input", _mergeProps({
  154. "ref": input,
  155. "checked": model.value,
  156. "disabled": !!props.disabled,
  157. "id": id.value,
  158. "onBlur": onBlur,
  159. "onFocus": onFocus,
  160. "onInput": onInput,
  161. "aria-disabled": !!props.disabled,
  162. "aria-label": props.label,
  163. "type": props.type,
  164. "value": trueValue.value,
  165. "name": props.name,
  166. "aria-checked": props.type === 'checkbox' ? model.value : undefined
  167. }, inputAttrs), null);
  168. return _createVNode("div", _mergeProps({
  169. "class": ['v-selection-control', {
  170. 'v-selection-control--dirty': model.value,
  171. 'v-selection-control--disabled': props.disabled,
  172. 'v-selection-control--error': props.error,
  173. 'v-selection-control--focused': isFocused.value,
  174. 'v-selection-control--focus-visible': isFocusVisible.value,
  175. 'v-selection-control--inline': props.inline
  176. }, densityClasses.value, props.class]
  177. }, rootAttrs, {
  178. "style": props.style
  179. }), [_createVNode("div", {
  180. "class": ['v-selection-control__wrapper', textColorClasses.value],
  181. "style": textColorStyles.value
  182. }, [slots.default?.({
  183. backgroundColorClasses,
  184. backgroundColorStyles
  185. }), _withDirectives(_createVNode("div", {
  186. "class": ['v-selection-control__input']
  187. }, [slots.input?.({
  188. model,
  189. textColorClasses,
  190. textColorStyles,
  191. backgroundColorClasses,
  192. backgroundColorStyles,
  193. inputNode,
  194. icon: icon.value,
  195. props: {
  196. onFocus,
  197. onBlur,
  198. id: id.value
  199. }
  200. }) ?? _createVNode(_Fragment, null, [icon.value && _createVNode(VIcon, {
  201. "key": "icon",
  202. "icon": icon.value
  203. }, null), inputNode])]), [[_resolveDirective("ripple"), props.ripple && [!props.disabled && !props.readonly, null, ['center', 'circle']]]])]), label && _createVNode(VLabel, {
  204. "for": id.value,
  205. "onClick": onClickLabel
  206. }, {
  207. default: () => [label]
  208. })]);
  209. });
  210. return {
  211. isFocused,
  212. input
  213. };
  214. }
  215. });
  216. //# sourceMappingURL=VSelectionControl.mjs.map