Skip to content

Commit

Permalink
refactor: update parent child components (#817)
Browse files Browse the repository at this point in the history
* refactor: update parent child components

* feat: add isEqual helper
  • Loading branch information
mlmoravek committed Feb 28, 2024
1 parent c813ca0 commit 484cfe8
Show file tree
Hide file tree
Showing 11 changed files with 118 additions and 112 deletions.
40 changes: 17 additions & 23 deletions packages/oruga/src/components/dropdown/Dropdown.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { computed, nextTick, ref, watch, type PropType, type Ref } from "vue";
import { computed, nextTick, ref, watch, type PropType } from "vue";
import PositionWrapper from "../utils/PositionWrapper.vue";
Expand All @@ -10,14 +10,13 @@ import { isClient } from "@/utils/ssr";
import {
unrefElement,
defineClasses,
useVModelBinding,
useProviderParent,
useMatchMedia,
useEventListener,
usePropBinding,
useClickOutside,
} from "@/composables";
import { provideDropdown } from "./useDropdownShare";
import type { DropdownComponent } from "./types";
import type { ComponentClass, DynamicComponent } from "@/types";
Expand Down Expand Up @@ -270,15 +269,9 @@ const emits = defineEmits<{
(e: "scroll-end"): void;
}>();
const vmodel = useVModelBinding<[string, number, boolean, object, Array<any>]>(
props,
emits,
{ passive: true },
) as Ref<any>;
const vmodel = defineModel<any>();
const isActive = usePropBinding<boolean>("active", props, emits, {
passive: true,
});
const isActive = defineModel<boolean>("active");
const autoPosition = ref(props.position);
Expand Down Expand Up @@ -454,7 +447,7 @@ function checkDropdownScroll(): void {
}
}
// --- Field Dependency Injection Feature ---
// --- Dependency Injection Feature ---
/**
* Click listener from DropdownItem.
Expand All @@ -466,20 +459,20 @@ function selectItem(value: any): void {
if (props.multiple) {
if (vmodel.value && Array.isArray(vmodel.value)) {
if (vmodel.value.indexOf(value) === -1) {
// Add value
// add a value
vmodel.value = [...vmodel.value, value];
} else {
// Remove value
// remove a value
vmodel.value = vmodel.value.filter((val) => val !== value);
}
} else {
// Init value array
// init new value array
vmodel.value = [value];
}
emits("change", vmodel.value);
} else {
if (vmodel.value !== value) {
// Upodate value
// update a single value
vmodel.value = value;
emits("change", vmodel.value);
}
Expand All @@ -493,14 +486,14 @@ function selectItem(value: any): void {
}
// Provided data is a computed ref to enjure reactivity.
const provideData = computed(() => ({
const provideData = computed<DropdownComponent>(() => ({
props,
selected: vmodel.value,
selectItem,
}));
// Provide field component data via dependency injection.
provideDropdown(provideData);
/** Provide functionalities and data to child item components */
useProviderParent(contentRef, { data: provideData });
// --- Computed Component Classes ---
Expand Down Expand Up @@ -605,6 +598,7 @@ defineExpose({ $trigger: triggerRef, $content: contentRef });
<div
v-if="isMobileModal"
v-show="isActive"
:tabindex="-1"
:class="menuMobileOverlayClasses"
:aria-hidden="!isActive" />
</transition>
Expand All @@ -618,10 +612,10 @@ defineExpose({ $trigger: triggerRef, $content: contentRef });
v-trap-focus="trapFocus"
:tabindex="menuTabindex"
:class="menuClasses"
:aria-hidden="!isActive"
:style="menuStyle"
:role="ariaRole"
:aria-modal="!inline && trapFocus"
:style="menuStyle">
:aria-hidden="!isActive"
:aria-modal="!inline && trapFocus">
<!--
@slot Place dropdown items here
@binding {boolean} active - dropdown active state
Expand Down
39 changes: 16 additions & 23 deletions packages/oruga/src/components/dropdown/DropdownItem.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
<script setup lang="ts" generic="T">
import { computed, onMounted, type PropType } from "vue";
<script setup lang="ts" generic="T = string">
import { computed, type ComputedRef, type PropType } from "vue";
import { getOption } from "@/utils/config";
import { uuid } from "@/utils/helpers";
import { defineClasses } from "@/composables";
import { injectDropdown } from "./useDropdownShare";
import { uuid, isEqual } from "@/utils/helpers";
import { defineClasses, useProviderChild } from "@/composables";
import type { DropdownComponent } from "./types";
import type { ComponentClass, DynamicComponent } from "@/types";
/**
Expand Down Expand Up @@ -78,32 +77,26 @@ const emits = defineEmits<{
(e: "click", value: T, event: Event): void;
}>();
// inject parent dropdown component if used inside one
const { parentDropdown } = injectDropdown<T>();
onMounted(() => {
if (!parentDropdown.value)
throw new Error("You should wrap oDropdownItem on a oDropdown");
});
// Inject functionalities and data from the parent component
const { parent } = useProviderChild<ComputedRef<DropdownComponent<T>>>();
const isClickable = computed(
() =>
!parentDropdown.value.props.disabled &&
!props.disabled &&
props.clickable,
() => !parent.value.props.disabled && !props.disabled && props.clickable,
);
const isActive = computed(() => {
if (parentDropdown.value.selected === null) return false;
if (parentDropdown.value.props.multiple)
return parentDropdown.value.selected.indexOf(props.value as T) >= 0;
return props.value === parentDropdown.value.selected;
if (parent.value.selected === null) return false;
if (parent.value.props.multiple && Array.isArray(parent.value.selected))
return parent.value.selected.some((selected) =>
isEqual(props.value, selected),
);
return isEqual(props.value, parent.value.selected);
});
/** Click listener, select the item. */
function selectItem(event: Event): void {
if (!isClickable.value) return;
parentDropdown.value.selectItem(props.value as T);
parent.value.selectItem(props.value as T);
emits("click", props.value as T, event);
}
Expand All @@ -115,7 +108,7 @@ const rootClasses = defineClasses(
"itemDisabledClass",
"o-drop__item--disabled",
null,
computed(() => parentDropdown.value.props.disabled || props.disabled),
computed(() => parent.value.props.disabled || props.disabled),
],
["itemActiveClass", "o-drop__item--active", null, isActive],
["itemClickableClass", "o-drop__item--clickable", null, isClickable],
Expand Down
11 changes: 11 additions & 0 deletions packages/oruga/src/components/dropdown/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { ComponentProps } from "vue-component-type-helpers";

import Dropdown from "./Dropdown.vue";

export type DropdownProps = ComponentProps<typeof Dropdown>;

export type DropdownComponent<T = unknown> = {
props: DropdownProps;
selected: T[];
selectItem: (value: T) => void;
};
30 changes: 0 additions & 30 deletions packages/oruga/src/components/dropdown/useDropdownShare.ts

This file was deleted.

8 changes: 4 additions & 4 deletions packages/oruga/src/components/steps/StepItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
import { computed, ref, useSlots, type ComputedRef, type PropType } from "vue";
import { getOption } from "@/utils/config";
import { uuid } from "@/utils/helpers";
import { isEqual, uuid } from "@/utils/helpers";
import { defineClasses, useProviderChild } from "@/composables";
import type { StepsComponent } from "./types";
import type { StepsComponent, StepItemComponent } from "./types";
import type { ComponentClass, DynamicComponent } from "@/types";
/**
Expand Down Expand Up @@ -98,7 +98,7 @@ const emits = defineEmits<{
const slots = useSlots();
const providedData = computed(() => ({
const providedData = computed<StepItemComponent>(() => ({
...props,
$slots: slots,
isTransitioning: isTransitioning.value,
Expand All @@ -113,7 +113,7 @@ const { parent, item } = useProviderChild<ComputedRef<StepsComponent>>({
const transitionName = ref();
const isActive = computed(() => parent.value.activeId === props.value);
const isActive = computed(() => isEqual(props.value, parent.value.activeValue));
const isTransitioning = ref(false);
Expand Down
27 changes: 11 additions & 16 deletions packages/oruga/src/components/steps/Steps.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,9 @@ import OIcon from "../icon/Icon.vue";
import { getOption } from "@/utils/config";
import { isDefined } from "@/utils/helpers";
import {
defineClasses,
useProviderParent,
useVModelBinding,
useMatchMedia,
} from "@/composables";
import type { StepItem, StepItemComponent } from "./types";
import { defineClasses, useProviderParent, useMatchMedia } from "@/composables";
import type { StepItem, StepItemComponent, StepsComponent } from "./types";
import type { ComponentClass, ClassBind } from "@/types";
/**
Expand Down Expand Up @@ -243,8 +238,8 @@ const { isMobile } = useMatchMedia(props.mobileBreakpoint);
const rootRef = ref();
// Provided data is a computed ref to enjure reactivity.
const provideData = computed(() => ({
activeId: activeId.value,
const provideData = computed<StepsComponent>(() => ({
activeValue: vmodel.value,
vertical: props.vertical,
animated: props.animated,
animation: props.animation,
Expand All @@ -264,19 +259,19 @@ const items = computed<StepItem[]>(() =>
})),
);
const activeId = useVModelBinding(props, emits, { passive: true });
const vmodel = defineModel<string | number>();
/** When v-model is changed set the new active tab. */
watch(
() => props.modelValue,
(value) => {
if (activeId.value !== value) performAction(value);
if (vmodel.value !== value) performAction(value);
},
);
const activeItem = computed(() =>
isDefined(activeId)
? items.value.find((item) => item.value === activeId.value) ||
isDefined(vmodel.value)
? items.value.find((item) => item.value === vmodel.value) ||
items.value[0]
: items.value[0],
);
Expand Down Expand Up @@ -339,7 +334,7 @@ function next(): void {
/** Item click listener, emit input event and change active child. */
function itemClick(item: StepItem): void {
if (activeId.value !== item.value) performAction(item.value);
if (vmodel.value !== item.value) performAction(item.value);
}
/** Activate next child and deactivate prev child */
Expand All @@ -355,7 +350,7 @@ function performAction(newId: number | string): void {
}
nextTick(() => {
activeId.value = newId;
vmodel.value = newId;
emits("change", newId, oldId);
});
}
Expand Down
2 changes: 1 addition & 1 deletion packages/oruga/src/components/steps/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export type StepItemComponent = StepItemProps & {
};

export type StepsComponent = {
activeId: string | number;
activeValue: string | number;
vertical: boolean;
animated: boolean;
animation: string[];
Expand Down
6 changes: 3 additions & 3 deletions packages/oruga/src/components/tabs/TabItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { computed, ref, useSlots, type ComputedRef, type PropType } from "vue";
import { getOption } from "@/utils/config";
import { uuid } from "@/utils/helpers";
import { isEqual, uuid } from "@/utils/helpers";
import { defineClasses, useProviderChild } from "@/composables";
import type { TabsComponent, TabItemComponent } from "./types";
Expand Down Expand Up @@ -107,14 +107,14 @@ const providedData = computed<TabItemComponent>(() => ({
deactivate,
}));
// Inject functionalities and data from the parent carousel component
// Inject functionalities and data from the parent component
const { parent, item } = useProviderChild<ComputedRef<TabsComponent>>({
data: providedData,
});
const transitionName = ref();
const isActive = computed(() => parent.value.activeId === props.value);
const isActive = computed(() => isEqual(props.value, parent.value.activeValue));
const isTransitioning = ref(false);
Expand Down
Loading

0 comments on commit 484cfe8

Please sign in to comment.