@uibit/text-clamp 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/frameworks/angular/index.ts +34 -30
- package/dist/frameworks/astro/index.d.ts +6 -1
- package/dist/frameworks/preact/index.d.ts +2 -0
- package/dist/frameworks/react/index.ts +92 -0
- package/dist/frameworks/solid/index.d.ts +1 -1
- package/dist/frameworks/stencil/index.d.ts +2 -0
- package/dist/frameworks/svelte/index.svelte +46 -17
- package/dist/frameworks/vue/index.ts +79 -22
- package/package.json +3 -3
- package/dist/frameworks/react/index.d.ts +0 -20
|
@@ -1,38 +1,42 @@
|
|
|
1
1
|
import { Component, ChangeDetectionStrategy, ElementRef, input, effect, output, numberAttribute } from '@angular/core';
|
|
2
2
|
import '@uibit/text-clamp';
|
|
3
3
|
import type { TextClamp as HTMLElementClass } from '@uibit/text-clamp';
|
|
4
|
-
|
|
5
4
|
@Component({
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
selector: 'uibit-text-clamp',
|
|
6
|
+
template: '<ng-content></ng-content>',
|
|
7
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
8
|
+
standalone: true,
|
|
9
|
+
host: {
|
|
10
|
+
'(toggle)': 'toggle.emit($event)'
|
|
11
|
+
}
|
|
13
12
|
})
|
|
14
13
|
export class NgxTextClamp {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
constructor(private el: ElementRef<HTMLElementClass>){
|
|
15
|
+
effect(()=>{
|
|
16
|
+
if (this.el.nativeElement) {
|
|
17
|
+
this.el.nativeElement.lines = this.lines();
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
effect(()=>{
|
|
21
|
+
if (this.el.nativeElement) {
|
|
22
|
+
this.el.nativeElement._resize = this._resize();
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
effect(()=>{
|
|
26
|
+
if (this.el.nativeElement) {
|
|
27
|
+
this.el.nativeElement.locale = this.locale();
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
readonly lines = input<number, any>(3, {
|
|
32
|
+
transform: numberAttribute
|
|
20
33
|
});
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
readonly lines = input<number, any>(3, { transform: numberAttribute });
|
|
34
|
-
readonly _resize = input<any, any>(new ResizeController(this, { target: () => this._contentEl, callback: () => this._measure(), }));
|
|
35
|
-
readonly locale = input<string, any>('');
|
|
36
|
-
|
|
37
|
-
readonly toggle = output<CustomEvent<{ expanded: boolean }>>();
|
|
34
|
+
readonly _resize = input<any>(new ResizeController(this, {
|
|
35
|
+
target: ()=>this._contentEl,
|
|
36
|
+
callback: ()=>this._measure()
|
|
37
|
+
}));
|
|
38
|
+
readonly locale = input<string>('');
|
|
39
|
+
readonly toggle = output<CustomEvent<{
|
|
40
|
+
expanded: boolean;
|
|
41
|
+
}>>();
|
|
38
42
|
}
|
|
@@ -4,7 +4,12 @@ import '@uibit/text-clamp';
|
|
|
4
4
|
declare global {
|
|
5
5
|
namespace astroHTML.JSX {
|
|
6
6
|
interface IntrinsicElements {
|
|
7
|
-
'uibit-text-clamp': Partial<HTMLElementClass> & astroHTML.JSX.HTMLAttributes
|
|
7
|
+
'uibit-text-clamp': Partial<HTMLElementClass> & astroHTML.JSX.HTMLAttributes & {
|
|
8
|
+
lines?: number;
|
|
9
|
+
_resize?: any;
|
|
10
|
+
locale?: string;
|
|
11
|
+
"on:toggle"?: (event: CustomEvent<{ expanded: boolean }>) => void;
|
|
12
|
+
};
|
|
8
13
|
}
|
|
9
14
|
}
|
|
10
15
|
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import React, { useRef, useEffect, useLayoutEffect, useImperativeHandle } from 'react';
|
|
2
|
+
import type { TextClamp as HTMLElementClass } from '@uibit/text-clamp';
|
|
3
|
+
import '@uibit/text-clamp';
|
|
4
|
+
const useTypeOfLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
|
|
5
|
+
export interface TextClampProps extends Omit<React.HTMLAttributes<HTMLElementClass>, 'lines' | '_resize' | 'locale' | 'onToggle'> {
|
|
6
|
+
children?: React.ReactNode;
|
|
7
|
+
lines?: number;
|
|
8
|
+
_resize?: any;
|
|
9
|
+
locale?: string;
|
|
10
|
+
onToggle?: (event: CustomEvent<{
|
|
11
|
+
expanded: boolean;
|
|
12
|
+
}>) => void;
|
|
13
|
+
}
|
|
14
|
+
export const TextClamp = ({ ref, children, ...props }: TextClampProps & {
|
|
15
|
+
ref?: React.Ref<HTMLElementClass>;
|
|
16
|
+
})=>{
|
|
17
|
+
const innerRef = useRef<HTMLElementClass>(null);
|
|
18
|
+
const propsRef = useRef(props);
|
|
19
|
+
propsRef.current = props;
|
|
20
|
+
useImperativeHandle(ref, ()=>innerRef.current!);
|
|
21
|
+
useTypeOfLayoutEffect(()=>{
|
|
22
|
+
const element = innerRef.current;
|
|
23
|
+
if (element && props.lines !== undefined) {
|
|
24
|
+
(element as any).lines = props.lines;
|
|
25
|
+
}
|
|
26
|
+
}, [
|
|
27
|
+
props.lines
|
|
28
|
+
]);
|
|
29
|
+
useTypeOfLayoutEffect(()=>{
|
|
30
|
+
const element = innerRef.current;
|
|
31
|
+
if (element && props._resize !== undefined) {
|
|
32
|
+
(element as any)._resize = props._resize;
|
|
33
|
+
}
|
|
34
|
+
}, [
|
|
35
|
+
props._resize
|
|
36
|
+
]);
|
|
37
|
+
useTypeOfLayoutEffect(()=>{
|
|
38
|
+
const element = innerRef.current;
|
|
39
|
+
if (element && props.locale !== undefined) {
|
|
40
|
+
(element as any).locale = props.locale;
|
|
41
|
+
}
|
|
42
|
+
}, [
|
|
43
|
+
props.locale
|
|
44
|
+
]);
|
|
45
|
+
useTypeOfLayoutEffect(()=>{
|
|
46
|
+
const element = innerRef.current;
|
|
47
|
+
if (!element) return;
|
|
48
|
+
const handleEvent = (event: Event)=>{
|
|
49
|
+
if (propsRef.current.onToggle) {
|
|
50
|
+
propsRef.current.onToggle(event as any);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
element.addEventListener('toggle', handleEvent);
|
|
54
|
+
return ()=>{
|
|
55
|
+
element.removeEventListener('toggle', handleEvent);
|
|
56
|
+
};
|
|
57
|
+
}, []);
|
|
58
|
+
const domProps = {
|
|
59
|
+
...props
|
|
60
|
+
};
|
|
61
|
+
const customPropNames = [
|
|
62
|
+
'lines',
|
|
63
|
+
'_resize',
|
|
64
|
+
'locale',
|
|
65
|
+
'onToggle'
|
|
66
|
+
];
|
|
67
|
+
for (const key of customPropNames){
|
|
68
|
+
delete (domProps as any)[key];
|
|
69
|
+
}
|
|
70
|
+
return React.createElement('uibit-text-clamp', {
|
|
71
|
+
ref: innerRef,
|
|
72
|
+
...domProps
|
|
73
|
+
}, children);
|
|
74
|
+
};
|
|
75
|
+
declare global {
|
|
76
|
+
namespace React {
|
|
77
|
+
namespace JSX {
|
|
78
|
+
interface IntrinsicElements {
|
|
79
|
+
'uibit-text-clamp': React.ClassAttributes<HTMLElementClass> & React.HTMLAttributes<HTMLElementClass> & {
|
|
80
|
+
children?: React.ReactNode;
|
|
81
|
+
class?: string;
|
|
82
|
+
lines?: number;
|
|
83
|
+
_resize?: any;
|
|
84
|
+
locale?: string;
|
|
85
|
+
onToggle?: (event: CustomEvent<{
|
|
86
|
+
expanded: boolean;
|
|
87
|
+
}>) => void;
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -1,37 +1,66 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import '@uibit/text-clamp';
|
|
3
3
|
import type { TextClamp as HTMLElementClass } from '@uibit/text-clamp';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
lines =
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
lines = $bindable(),
|
|
7
|
+
_resize = $bindable(),
|
|
8
|
+
locale = $bindable(),
|
|
9
|
+
children,
|
|
10
|
+
...restProps
|
|
10
11
|
} = $props<{
|
|
11
|
-
children?:
|
|
12
|
+
children?: import('svelte').Snippet;
|
|
12
13
|
lines?: number;
|
|
13
14
|
_resize?: any;
|
|
14
15
|
locale?: string;
|
|
15
|
-
|
|
16
|
+
|
|
17
|
+
[key: string]: any;
|
|
16
18
|
}>();
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (elementRef && lines !== undefined) {
|
|
19
|
+
|
|
20
|
+
let elementRef: HTMLElementClass | null = $state(null);
|
|
21
|
+
|
|
22
|
+
$effect(() => {
|
|
23
|
+
if (elementRef && lines !== undefined && elementRef.lines !== lines) {
|
|
22
24
|
elementRef.lines = lines;
|
|
23
25
|
}
|
|
24
|
-
if (elementRef && _resize !== undefined) {
|
|
26
|
+
if (elementRef && _resize !== undefined && elementRef._resize !== _resize) {
|
|
25
27
|
elementRef._resize = _resize;
|
|
26
28
|
}
|
|
27
|
-
if (elementRef && locale !== undefined) {
|
|
29
|
+
if (elementRef && locale !== undefined && elementRef.locale !== locale) {
|
|
28
30
|
elementRef.locale = locale;
|
|
29
31
|
}
|
|
30
32
|
});
|
|
31
|
-
|
|
33
|
+
|
|
34
|
+
$effect(() => {
|
|
35
|
+
const element = elementRef;
|
|
36
|
+
if (!element) return;
|
|
37
|
+
|
|
38
|
+
const handleEvent = () => {
|
|
39
|
+
if (lines !== element.lines) {
|
|
40
|
+
lines = element.lines as any;
|
|
41
|
+
}
|
|
42
|
+
if (_resize !== element._resize) {
|
|
43
|
+
_resize = element._resize as any;
|
|
44
|
+
}
|
|
45
|
+
if (locale !== element.locale) {
|
|
46
|
+
locale = element.locale as any;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const events = ['change', 'input', 'toggle'];
|
|
51
|
+
for (const event of events) {
|
|
52
|
+
element.addEventListener(event, handleEvent);
|
|
53
|
+
}
|
|
54
|
+
return () => {
|
|
55
|
+
for (const event of events) {
|
|
56
|
+
element.removeEventListener(event, handleEvent);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
|
|
32
61
|
</script>
|
|
33
62
|
|
|
34
|
-
<uibit-text-clamp bind:this={elementRef} {
|
|
63
|
+
<uibit-text-clamp bind:this={elementRef} {...restProps}>
|
|
35
64
|
|
|
36
65
|
{#if children}
|
|
37
66
|
{@render children()}
|
|
@@ -1,26 +1,83 @@
|
|
|
1
|
-
import { defineComponent, h } from 'vue';
|
|
1
|
+
import { defineComponent, h, ref, watch } from 'vue';
|
|
2
2
|
import type { TextClamp as HTMLElementClass } from '@uibit/text-clamp';
|
|
3
3
|
import '@uibit/text-clamp';
|
|
4
|
-
|
|
5
4
|
export const TextClamp = defineComponent({
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
5
|
+
name: 'TextClamp',
|
|
6
|
+
props: {
|
|
7
|
+
lines: {
|
|
8
|
+
type: [
|
|
9
|
+
String,
|
|
10
|
+
Number,
|
|
11
|
+
Boolean,
|
|
12
|
+
Array,
|
|
13
|
+
Object
|
|
14
|
+
] as any
|
|
15
|
+
},
|
|
16
|
+
_resize: {
|
|
17
|
+
type: [
|
|
18
|
+
String,
|
|
19
|
+
Number,
|
|
20
|
+
Boolean,
|
|
21
|
+
Array,
|
|
22
|
+
Object
|
|
23
|
+
] as any
|
|
24
|
+
},
|
|
25
|
+
locale: {
|
|
26
|
+
type: [
|
|
27
|
+
String,
|
|
28
|
+
Number,
|
|
29
|
+
Boolean,
|
|
30
|
+
Array,
|
|
31
|
+
Object
|
|
32
|
+
] as any
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
emits: [
|
|
36
|
+
'toggle'
|
|
37
|
+
],
|
|
38
|
+
setup (props, { slots, emit }) {
|
|
39
|
+
const elementRef = ref<HTMLElementClass | null>(null);
|
|
40
|
+
if (false) {
|
|
41
|
+
watch(()=>props.modelValue, (newVal)=>{
|
|
42
|
+
if (elementRef.value && newVal !== undefined && elementRef.value.value !== newVal) {
|
|
43
|
+
elementRef.value.value = newVal;
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
watch(()=>props.value, (newVal)=>{
|
|
47
|
+
if (elementRef.value && newVal !== undefined && elementRef.value.value !== newVal) {
|
|
48
|
+
elementRef.value.value = newVal;
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
watch(()=>props.lines, (newVal)=>{
|
|
53
|
+
if (elementRef.value && newVal !== undefined) {
|
|
54
|
+
(elementRef.value as any).lines = newVal;
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
watch(()=>props._resize, (newVal)=>{
|
|
58
|
+
if (elementRef.value && newVal !== undefined) {
|
|
59
|
+
(elementRef.value as any)._resize = newVal;
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
watch(()=>props.locale, (newVal)=>{
|
|
63
|
+
if (elementRef.value && newVal !== undefined) {
|
|
64
|
+
(elementRef.value as any).locale = newVal;
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
return ()=>{
|
|
68
|
+
const eventListeners: Record<string, any> = {};
|
|
69
|
+
eventListeners['onToggle'] = (event: Event)=>{
|
|
70
|
+
emit('toggle', event);
|
|
71
|
+
};
|
|
72
|
+
const mergedProps = {
|
|
73
|
+
...props,
|
|
74
|
+
...eventListeners,
|
|
75
|
+
ref: elementRef
|
|
76
|
+
};
|
|
77
|
+
if (false && props.modelValue !== undefined) {
|
|
78
|
+
(mergedProps as any).value = props.modelValue;
|
|
79
|
+
}
|
|
80
|
+
return h('uibit-text-clamp', mergedProps, slots.default?.());
|
|
81
|
+
};
|
|
82
|
+
}
|
|
26
83
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uibit/text-clamp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Clamps slotted text to an exact line count via a lines attribute. When content overflows, a More/Less toggle appears inline. A ResizeObserver recalculates overflow on every viewport resize so the clamp is always mathematically correct.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"import": "./dist/index.js"
|
|
12
12
|
},
|
|
13
13
|
"./custom-elements.json": "./custom-elements.json",
|
|
14
|
-
"./react": "./dist/frameworks/react/index.
|
|
14
|
+
"./react": "./dist/frameworks/react/index.ts",
|
|
15
15
|
"./vue": "./dist/frameworks/vue/index.ts",
|
|
16
16
|
"./svelte": "./dist/frameworks/svelte/index.svelte",
|
|
17
17
|
"./angular": "./dist/frameworks/angular/index.ts",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"@types/node": "^20.19.43",
|
|
52
52
|
"lit": "^3.3.3",
|
|
53
53
|
"typescript": "7.0.2",
|
|
54
|
-
"@uibit/codegen": "0.
|
|
54
|
+
"@uibit/codegen": "0.2.0"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
57
|
"lit": "^3.0.0",
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type { HTMLAttributes, ClassAttributes } from 'react';
|
|
2
|
-
import type { TextClamp as HTMLElementClass } from '@uibit/text-clamp';
|
|
3
|
-
import '@uibit/text-clamp';
|
|
4
|
-
|
|
5
|
-
declare global {
|
|
6
|
-
namespace React {
|
|
7
|
-
namespace JSX {
|
|
8
|
-
interface IntrinsicElements {
|
|
9
|
-
'uibit-text-clamp': ClassAttributes<HTMLElementClass> & HTMLAttributes<HTMLElementClass> & {
|
|
10
|
-
children?: React.ReactNode;
|
|
11
|
-
class?: string;
|
|
12
|
-
lines?: number;
|
|
13
|
-
_resize?: any;
|
|
14
|
-
locale?: string;
|
|
15
|
-
onToggle?: (event: any) => void;
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
}
|