Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: migrate MainScroll and MainScrollItem components #1456

Merged
Merged
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
00941a3
feat: migrating to vue 2.7 sintax and using hybridInject
albertjcuac Mar 25, 2024
561408c
chore: add space between doc comments and precious code lines
albertjcuac Mar 26, 2024
37a33d6
chore: change @link comment to point to props.item correctly
albertjcuac Mar 26, 2024
b725dcd
chore: removing @link in docs comment
albertjcuac Mar 26, 2024
bf7e95a
feat: use useXBus composable instead of use and fix docs warning
albertjcuac Mar 26, 2024
6504b4b
feat: change import order to avoid lint error
albertjcuac Mar 27, 2024
86a9c52
feat: getting correctly value and provisional use of any type
albertjcuac Mar 27, 2024
cd76aa3
feat: create ElementRef interface to avoid use of any
albertjcuac Mar 28, 2024
3d55c2a
feat: use of native inject instead of hybrid inject
albertjcuac Apr 2, 2024
4eaab83
feat: implementing conditional to take into account that the item ca…
albertjcuac Apr 8, 2024
d770ffe
Merge branch 'main' into feature/EMP-3755-Migrate-MainScrollItem-comp…
lauramargar Apr 9, 2024
e9602a4
feat: move lifecycle hooks into the setup method
lauramargar Apr 9, 2024
2378173
feat: migrate main-scroll to composition api syntax
lauramargar Apr 10, 2024
19580d7
chore: fix pendingScrollTo watcher
lauramargar Apr 11, 2024
aab083d
chore: fix pendingScrollTo reactivity
lauramargar Apr 11, 2024
3c3801c
fix: scroll restore position
diegopf Apr 16, 2024
7981136
fix: emit firstVisibleElement immediate
diegopf Apr 16, 2024
75053b5
test: fix tests
diegopf Apr 17, 2024
c604d8b
test: use Provide over XProvide
diegopf Apr 17, 2024
8ff5735
Merge branch 'main' into feature/EMP-3795-migrate-main-scroll-compone…
diegopf Apr 22, 2024
9197b8f
test: use the bus from the `XPlugin`
diegopf Apr 22, 2024
d60389e
add `MainScroll` and `MainScrollItem` to playground
diegopf Apr 24, 2024
ebb61f7
refactor: use a div as root node for `MainScroll`
diegopf Apr 26, 2024
b341d47
Merge branch 'main' into feature/EMP-3795-migrate-main-scroll-compone…
diegopf Apr 26, 2024
7b27e5b
fix build
diegopf Apr 26, 2024
fa386d4
fix e2e tests
diegopf Apr 30, 2024
f687a14
rename el into rootRef
diegopf Apr 30, 2024
1cfd228
destructure useState
diegopf Apr 30, 2024
ceae18b
destructure useState
diegopf Apr 30, 2024
f60573d
Merge branch 'main' into feature/EMP-3795-migrate-main-scroll-compone…
diegopf May 1, 2024
948dab2
test: restore the scroll position
diegopf May 1, 2024
aa02c76
refactor: use root div as root for intersection observer
diegopf May 2, 2024
55a023d
fix: use null as default value for ScrollObserverKey
diegopf May 2, 2024
8fd374f
fix: use false as default value for DISABLE_ANIMATIONS_KEY
diegopf May 2, 2024
8924f18
fix: set xModule name
diegopf May 2, 2024
ca78b79
drop root-selector usage
diegopf May 2, 2024
cf5ff53
refactor: remove unnecessary ids
diegopf May 6, 2024
299bba4
refactor: avoid decorator
diegopf May 6, 2024
86262c9
Revert "refactor: avoid decorator"
diegopf May 6, 2024
9a9237d
refactor: use a div as default tag prop value
diegopf May 6, 2024
a700c7a
Merge branch 'main' into feature/EMP-3795-migrate-main-scroll-compone…
diegopf May 6, 2024
15b2d09
format file
diegopf May 8, 2024
1369156
move scroll related config to x-module folder
diegopf May 8, 2024
8ed688d
fix export
diegopf May 8, 2024
022fd06
remove unnecessary comment
diegopf May 8, 2024
5a78e3b
type scrollXModule
diegopf May 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/_vue3-migration-test/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export * from './animations';
export { default as TestBaseColumnPickerDropdown } from './column-picker/test-base-column-picker-dropdown.vue';
export { default as TestBaseColumnPickerList } from './column-picker/test-base-column-picker-list.vue';
export { default as TestBaseDropdown } from './test-base-dropdown.vue';
export * from '../x-modules/scroll';
joseacabaneros marked this conversation as resolved.
Show resolved Hide resolved
export { default as TestBaseEventButton } from './test-base-event-button.vue';
9 changes: 8 additions & 1 deletion packages/_vue3-migration-test/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { createStore } from 'vuex';
import { xPlugin } from '../../x-components/src/plugins/x-plugin';
import App from './App.vue';
import router from './router';
import { scrollXModule } from './x-modules/scroll/x-module';

// Warnings that cannot be solved in Vue 2 (a.k.a. breaking changes) are suppressed
const VUE_COMPAT_MODE = Number(import.meta.env.VITE_VUE_COMPAT_MODE);
Expand All @@ -30,5 +31,11 @@ const store = createStore({});
createApp(App as Component)
.use(router)
.use(store)
.use(xPlugin, { adapter, store })
.use(xPlugin, {
adapter,
store,
__PRIVATE__xModules: {
scroll: scrollXModule
}
})
.mount('#app');
6 changes: 6 additions & 0 deletions packages/_vue3-migration-test/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
TestSortDropdown,
TestSortList,
TestSortPickerList,
TestScroll,
TestBaseColumnPickerDropdown
} from './';

Expand All @@ -22,6 +23,11 @@ const routes = [
name: 'Fade',
component: TestFade
},
{
path: '/scroll',
name: 'Scroll',
component: TestScroll
},
{
path: '/base-dropdown',
name: 'BaseDropdown',
Expand Down
2 changes: 2 additions & 0 deletions packages/_vue3-migration-test/src/x-modules/scroll/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default as TestScroll } from './test-scroll.vue';
export * from './x-module';
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<script setup lang="ts">
import MainScroll from '../../../../x-components/src/x-modules/scroll/components/main-scroll.vue';
// eslint-disable-next-line max-len
joseacabaneros marked this conversation as resolved.
Show resolved Hide resolved
import MainScrollItem from '../../../../x-components/src/x-modules/scroll/components/main-scroll-item.vue';
const items = Array.from({ length: 24 }, (_, index) => ({ id: `item-${index}` }));
</script>

<template>
<MainScroll>
<ul class="list" data-test="scroll">
<MainScrollItem v-for="item in items" :key="item.id" class="item" tag="article" :item="item">
{{ item.id }}
</MainScrollItem>
</ul>
</MainScroll>
</template>

<style scoped lang="scss">
.list {
overflow: auto;
height: 100px;
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const scrollXModule = {
joseacabaneros marked this conversation as resolved.
Show resolved Hide resolved
storeModule: {
state: {
pendingScrollTo: 'item-10'
}
}
};
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { mount, Wrapper } from '@vue/test-utils';
import { Component, Prop } from 'vue-property-decorator';
import { Component, Prop, Provide } from 'vue-property-decorator';
import Vue, { ComponentOptions, CreateElement, VNode } from 'vue';
import DisableAnimationMixin from '../disable-animation.mixin';
import { XProvide } from '../../decorators/injection.decorators';
import { DISABLE_ANIMATIONS_KEY } from '../../decorators/injection.consts';

@Component
class Provider extends Vue {
@Prop()
@XProvide(DISABLE_ANIMATIONS_KEY)
@Provide(DISABLE_ANIMATIONS_KEY as string)
public disableAnimation!: boolean;

render(h: CreateElement): VNode {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import Vue from 'vue';
import { Component } from 'vue-property-decorator';
import { XInject } from '../decorators/injection.decorators';
import { Component, Inject } from 'vue-property-decorator';
import { DISABLE_ANIMATIONS_KEY } from '../decorators/injection.consts';

/**
Expand All @@ -22,7 +21,7 @@ export default class DisableAnimationMixin extends Vue {
*
* @public
*/
@XInject(DISABLE_ANIMATIONS_KEY)
@Inject({ from: DISABLE_ANIMATIONS_KEY as string, default: false })
joseacabaneros marked this conversation as resolved.
Show resolved Hide resolved
public disableAnimation!: boolean;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
</Scroll>
</BaseIdTogglePanel>

<MainScroll>
<MainScroll class="x-flex x-flex-auto">
<Scroll v-if="hasContent('main-body')" id="main-scroll" class="x-layout__body-scroll">
<section class="x-layout__main-body x-list x-list--vertical">
<!-- @slot Slot that can be used to insert the body content. -->
Expand Down
2 changes: 1 addition & 1 deletion packages/x-components/src/composables/use-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export function useState<
const store = useStore();

return paths.reduce<Dictionary<ComputedRef>>((stateDictionary, path) => {
stateDictionary[path] = computed(() => store.state.x[module]?.[path]);
stateDictionary[path] = computed(() => store?.state.x[module]?.[path]);
return stateDictionary;
}, {});
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
<template>
<component :is="tag" v-on="$listeners" :data-scroll="item.id">
<component :is="tag" ref="rootRef" v-on="$listeners" :data-scroll="item.id">
<slot />
</component>
</template>
<script lang="ts">
import { Identifiable } from '@empathyco/x-types';
import Vue from 'vue';
import { Component, Prop } from 'vue-property-decorator';
import { NoElement, State, xComponentMixin } from '../../../components';
import { XInject } from '../../../components/decorators/injection.decorators';
import Vue, {
defineComponent,
inject,
nextTick,
onBeforeUnmount,
onMounted,
PropType,
ref,
watch,
Ref,
WatchCallback
} from 'vue';
import { scrollXModule } from '../x-module';
import { useState } from '../../../composables/use-state';
import { useRegisterXModule } from '../../../composables/use-register-x-module';
import { useXBus } from '../../../composables/use-x-bus';
import { ScrollObserverKey } from './scroll.const';
import { ScrollVisibilityObserver } from './scroll.types';

Expand All @@ -19,95 +30,132 @@
*
* @public
*/
@Component({
mixins: [xComponentMixin(scrollXModule)]
})
export default class MainScrollItem extends Vue {
/**
* Rendered HTML node.
*
* @public
*/
public $el!: HTMLElement;

/**
* The item data. Used to set the scroll identifier.
*
* @public
*/
@Prop({ required: true })
public item!: Identifiable;

/**
* The tag to render.
*
* @public
*/
@Prop({ default: () => NoElement })
public tag!: string | typeof Vue;

/**
* Pending identifier scroll position to restore. If it matches the {@link MainScrollItem.item}
* `id` property, this component should be scrolled into view.
*
* @internal
*/
@State('scroll', 'pendingScrollTo')
public pendingScrollTo!: string;

/**
* Observer to detect the first visible element.
*
* @internal
*/
@XInject(ScrollObserverKey)
public firstVisibleItemObserver!: ScrollVisibilityObserver | null;

/**
* Initialise scroll behavior.
* - Observes the rendered element to detect if it is the first visible item.
* - If the rendered element matches the {@link MainScrollItem.pendingScrollTo}, scrolls the
* element into the first position of the view.
*
* @internal
*/
async mounted(): Promise<void> {
await this.$nextTick(); // Mounted does not guarantee that child components are mounted too
// eslint-disable-next-line @typescript-eslint/unbound-method
this.$watch('firstVisibleItemObserver', this.observeItem, { immediate: true });
}

/**
* Detaches the observer from the rendered element to prevent memory leaks.
*
* @internal
*/
beforeDestroy(): void {
this.firstVisibleItemObserver?.unobserve(this.$el);
}

/**
* Initialises the element visibility observation, stopping the previous one if it has.
*
* @param newObserver - The new observer for the HTML element.
* @param oldObserver - The old observer for the HTML element.
*/
observeItem(
newObserver: ScrollVisibilityObserver | null,
oldObserver: ScrollVisibilityObserver | null
): void {
oldObserver?.unobserve(this.$el);
newObserver?.observe(this.$el);
if (this.pendingScrollTo === this.item.id) {
Vue.nextTick(() => {
this.$el.scrollIntoView({
block: 'center'
});
});
this.$x.emit('ScrollRestoreSucceeded');
export default defineComponent({
name: 'MainScrollItem',
xModule: scrollXModule.name,
props: {
/**
* The item data. Used to set the scroll identifier.
*
* @public
*/
item: {
type: Object as PropType<Identifiable>,
required: true
},
/**
* The tag to render.
*
* @public
*/
tag: {
type: [String, Object] as PropType<string | typeof Vue>,
default: 'div'
}
},
setup(props) {
type ElementRef = {
$el: HTMLElement;
};

useRegisterXModule(scrollXModule);
const xBus = useXBus();

/**
* Rendered HTML node.
*
* @public
*/
const rootRef = ref<ElementRef | HTMLElement | null>(null);

/**
* Pending identifier scroll position to restore. If it matches the {@link MainScrollItem} item
* `id` property, this component should be scrolled into view.
*
* @internal
*/
const { pendingScrollTo } = useState('scroll', ['pendingScrollTo']);

/**
* Observer to detect the first visible element.
*
* @internal
*/
const firstVisibleItemObserver = inject<Ref<ScrollVisibilityObserver> | null>(
ScrollObserverKey as string,
null
);
/**
* Checks if a given value is an `ElementRef` object.
*
* @param value - The value to check.
* @returns `true` if the value is an `ElementRef` object, `false` otherwise.
*
* @internal
*/
const isElementRef = (value: any): value is ElementRef => {
return value && value.$el instanceof HTMLElement;
};
/**
* Initialises the element visibility observation, stopping the previous one if it has.
*
* @param newObserver - The new observer for the HTML element.
* @param oldObserver - The old observer for the HTML element.
*/
const observeItem: WatchCallback<ScrollVisibilityObserver> = (
newObserver: ScrollVisibilityObserver | null,
oldObserver: ScrollVisibilityObserver | null
): void => {
{
if (rootRef.value !== null) {
const htmlElement = isElementRef(rootRef.value) ? rootRef.value.$el : rootRef.value;

oldObserver?.unobserve(htmlElement);
newObserver?.observe(htmlElement);
if (pendingScrollTo.value === props.item.id) {
Vue.nextTick(() => {
htmlElement.scrollIntoView({
block: 'center'
});
});
xBus.emit('ScrollRestoreSucceeded');
}
}
}
};

/**
* Detaches the observer from the rendered element to prevent memory leaks.
*
* @internal
*/
onBeforeUnmount(() => {
if (rootRef.value !== null) {
const htmlElement = isElementRef(rootRef.value) ? rootRef.value.$el : rootRef.value;
firstVisibleItemObserver?.value.unobserve(htmlElement);
}
});

/**
* Initialise scroll behavior.
* - Observes the rendered element to detect if it is the first visible item.
* - If the rendered element matches the {@link MainScrollItem.pendingScrollTo}, scrolls the
* element into the first position of the view.
*
* @internal
*/
onMounted(() => {
nextTick(() => {
// Mounted does not guarantee that child components are mounted too
if (firstVisibleItemObserver) {
watch(firstVisibleItemObserver, observeItem, { immediate: true });
}
});
});

return { rootRef, firstVisibleItemObserver, observeItem, isElementRef };
}
}
});
</script>

<docs lang="mdx">
Expand Down
Loading
Loading