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(highlight): migrate highlight component to composition API #1486

Merged
merged 1 commit into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -8,3 +8,4 @@ export { default as TestBaseVariableColumnGrid } from './test-base-variable-colu
export { default as TestSlidingPanel } from './test-sliding-panel.vue';
export { default as TestUseLayouts } from './test-use-layouts.vue';
export { default as TestBaseSuggestions } from './suggestions/test-base-suggestions.vue';
export { default as TestHighlight } from './test-highlight.vue';
33 changes: 33 additions & 0 deletions packages/_vue3-migration-test/src/components/test-highlight.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<template>
<Highlight
:text="'Almendra garrapiñada'"
:highlight="'men'"
match-class="match-class"
no-match-class="no-match-class"
matching-part-class="matching-part-class"
/>
<br />
<Highlight
:text="'Almendra garrapiñada'"
:highlight="'tar'"
match-class="match-class"
no-match-class="no-match-class"
matching-part-class="matching-part-class"
/>
</template>

<script setup lang="ts">
import Highlight from '../../../x-components/src/components/highlight.vue';
</script>

<style>
.match-class {
color: red;
}
.matching-part-class {
color: greenyellow;
}
.no-match-class {
color: blue;
}
</style>
8 changes: 7 additions & 1 deletion packages/_vue3-migration-test/src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ import {
TestEmpathize,
TestUseLayouts,
TestSlidingPanel,
TestBaseSuggestions
TestBaseSuggestions,
TestHighlight
} from './';

const routes = [
Expand Down Expand Up @@ -140,6 +141,11 @@ const routes = [
path: '/base-suggestions',
name: 'BaseSuggestions',
component: TestBaseSuggestions
},
{
path: '/highlight',
name: 'Highlight',
component: TestHighlight
}
];

Expand Down
207 changes: 113 additions & 94 deletions packages/x-components/src/components/highlight.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,113 +27,132 @@
</template>

<script lang="ts">
import { Component, Mixins, Prop } from 'vue-property-decorator';
import { computed, defineComponent } from 'vue';
import { normalizeString } from '../utils/normalize';
import { VueCSSClasses } from '../utils/types';
import { NoElement } from './no-element';
import { dynamicPropsMixin } from './dynamic-props.mixin';

/**
* Highlights the given part of the text. The component is smart enough to do matches
* between special characters like tilde, cedilla, eñe, capital letters...
*
* @public
*/
@Component({
components: { NoElement }
})
export default class Highlight extends Mixins(
dynamicPropsMixin(['noMatchClass', 'matchingPartClass'])
) {
/**
* The text to highlight some part of it.
*
* @public
*/
@Prop({ default: '' })
public text!: string;

/**
* The part of the text to be highlighted.
*
* @public
*/
@Prop({ default: '' })
public highlight!: string;

/**
* CSS Class to add when the `text` string contains the `highlight` string.
*/
@Prop({ default: '' })
public matchClass!: string;

/**
* Checks if the normalized suggestion query matches with the module's query, so it has a
* matching part.
*
* @returns True if there is a match between the text and the highlight strings.
* @internal
*/
protected get hasMatch(): boolean {
return !!this.matchParts.match;
}

/**
* CSS classes to add depending on the component state.
*
* @remarks
* `x-highlight--has-match`: When there is a match between the text and the part to highlight.
* `[matchClass]`: When there is a match between the text and
* the part to highlight.
* `[noMatchClass]`: when there is no match between the text to highlight.
* @returns The {@link VueCSSClasses} classes.
* @internal
*/
protected get dynamicCSSClasses(): VueCSSClasses {
const classes: VueCSSClasses = {
'x-highlight--has-match': this.hasMatch,
'x-highlight-text': this.hasMatch,
[this.matchClass]: this.hasMatch
};
if (this.noMatchClass) {
classes[this.noMatchClass] = !this.hasMatch;
export default defineComponent({
name: 'Highlight',
components: { NoElement },
props: {
/**
* The text to highlight some part of it.
*
* @public
*/
text: {
type: String,
default: ''
},
/**
* The part of the text to be highlighted.
*
* @public
*/
highlight: {
type: String,
default: ''
},
/**
* CSS Class to add when the `text` string contains the `highlight` string.
*/
matchClass: {
type: String,
default: ''
},
/**
* CSS Class to add when the given `text` doesn't contain the `highlight` string.
*/
noMatchClass: {
type: String,
default: ''
},
/**
* CSS Class to add to the matching text.
*/
matchingPartClass: {
type: String,
default: ''
}
},
setup: function (props) {
/**
* Splits the text to highlight into 3 parts: a starting part, the matching part
* and the ending part. If there is no match between the text and the highlight, the `start`
* property will contain the whole text.
*
* @returns An object containing the different parts of the text.
* @internal
*/
const matchParts = computed((): HighlightMatch => {
const matcherIndex = normalizeString(props.text).indexOf(normalizeString(props.highlight));
return matcherIndex !== -1 && props.highlight
? splitAt(props.text.trim(), matcherIndex, matcherIndex + props.highlight.trim().length)
: { start: props.text, match: '', end: '' };
});

/**
* Checks if the normalized suggestion query matches with the module's query, so it has a
* matching part.
*
* @returns True if there is a match between the text and the highlight strings.
* @internal
*/
const hasMatch = computed((): boolean => {
return !!matchParts.value.match;
});

/**
* CSS classes to add depending on the component state.
*
* @remarks
* `x-highlight--has-match`: When there is a match between the text and the part to highlight.
* `[matchClass]`: When there is a match between the text and
* the part to highlight.
* `[noMatchClass]`: when there is no match between the text to highlight.
* @returns The {@link VueCSSClasses} classes.
* @internal
*/
const dynamicCSSClasses = computed((): VueCSSClasses => {
const classes: VueCSSClasses = {
'x-highlight--has-match': hasMatch.value,
'x-highlight-text': hasMatch.value,
[props.matchClass]: hasMatch.value
};
if (props.noMatchClass) {
classes[props.noMatchClass] = !hasMatch.value;
}
return classes;
});

/**
* Splits the label in three parts based on two indexes.
*
* @param label - The string that will be divided in three parts.
* @param start - The first index that the label will be divided by.
* @param end - The second index that the label will be divided by.
*
* @returns The three parts of the divided label.
* @internal
*/
function splitAt(label: string, start: number, end: number): HighlightMatch {
return {
start: label.substring(0, start),
match: label.substring(start, end),
end: label.substring(end)
};
}
return classes;
}

/**
* Splits the text to highlight into 3 parts: a starting part, the matching part
* and the ending part. If there is no match between the text and the highlight, the `start`
* property will contain the whole text.
*
* @returns An object containing the different parts of the text.
* @internal
*/
protected get matchParts(): HighlightMatch {
const matcherIndex = normalizeString(this.text).indexOf(normalizeString(this.highlight));
return matcherIndex !== -1 && this.highlight
? this.splitAt(this.text.trim(), matcherIndex, matcherIndex + this.highlight.trim().length)
: { start: this.text, match: '', end: '' };
}

/**
* Splits the label in three parts based on two indexes.
*
* @param label - The string that will be divided in three parts.
* @param start - The first index that the label will be divided by.
* @param end - The second index that the label will be divided by.
*
* @returns The three parts of the divided label.
* @internal
*/
protected splitAt(label: string, start: number, end: number): HighlightMatch {
return {
start: label.substring(0, start),
match: label.substring(start, end),
end: label.substring(end)
};
return { hasMatch, matchParts, dynamicCSSClasses };
}
}
});

/**
* Contains the different parts of a string match.
Expand Down
Loading