@inizioevoke/astro-core 2.1.1 → 2.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/package.json +1 -1
- package/src/components/CssBackgroundImage.astro +103 -0
- package/src/components/CssVariables.astro +129 -0
- package/src/components/FlipCard/FlipCard.astro +2 -2
- package/src/components/Modal/Modal.ts +4 -4
- package/src/components/ScrollContainer/ScrollContainer.astro +5 -1
- package/src/components/ScrollContainer/ScrollContainer.ts +47 -52
- package/src/components/Tabs/TabItem.astro +1 -2
- package/src/components/Tabs/Tabs.astro +2 -3
- package/src/components/index.ts +2 -0
- package/src/integrations/prune-build.ts +33 -25
- package/src/lib/gestures/pinch.ts +62 -15
package/package.json
CHANGED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
---
|
|
2
|
+
// See /docs/components/CssBackgroundImage.md
|
|
3
|
+
import type { ImageMetadata, ImageOutputFormat } from "astro";
|
|
4
|
+
import { getImage } from "astro:assets";
|
|
5
|
+
|
|
6
|
+
type ImageSize = 'contain' | 'cover';
|
|
7
|
+
type ImagePosition = 'left top' | 'left center' | 'left bottom' | 'center top' | 'center center' | 'center bottom' | 'right top' | 'right center' | 'right bottom';
|
|
8
|
+
|
|
9
|
+
interface ImageSrcProps {
|
|
10
|
+
image: ImageMetadata;
|
|
11
|
+
optimize?: boolean;
|
|
12
|
+
format?: ImageOutputFormat;
|
|
13
|
+
quality?: number;
|
|
14
|
+
width?: number;
|
|
15
|
+
height?: number;
|
|
16
|
+
}
|
|
17
|
+
interface BgImageProps extends ImageSrcProps {
|
|
18
|
+
size?: ImageSize | (string & {});
|
|
19
|
+
position?: ImagePosition | (string & {});
|
|
20
|
+
repeat?: 'no-repeat' | 'repeat-x' | 'repeat-y' | 'repeat';
|
|
21
|
+
important?: true;
|
|
22
|
+
}
|
|
23
|
+
export interface Props extends BgImageProps {
|
|
24
|
+
selector?: string;
|
|
25
|
+
|
|
26
|
+
breakpoints?: ({
|
|
27
|
+
minWidth?: string | number;
|
|
28
|
+
maxWidth?: string | number;
|
|
29
|
+
} & BgImageProps)[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const {
|
|
33
|
+
selector,
|
|
34
|
+
image,
|
|
35
|
+
optimize = true,
|
|
36
|
+
format,
|
|
37
|
+
quality,
|
|
38
|
+
width,
|
|
39
|
+
height,
|
|
40
|
+
size,
|
|
41
|
+
position,
|
|
42
|
+
repeat,
|
|
43
|
+
important,
|
|
44
|
+
breakpoints = []
|
|
45
|
+
} = Astro.props;
|
|
46
|
+
|
|
47
|
+
const styles: string[] = [];
|
|
48
|
+
|
|
49
|
+
async function getImageSrc({image, format, quality, width, height, optimize}: ImageSrcProps) {
|
|
50
|
+
if (optimize !== false) {
|
|
51
|
+
const img = await getImage({
|
|
52
|
+
src: image,
|
|
53
|
+
format: format ?? image.format,
|
|
54
|
+
quality: quality,
|
|
55
|
+
width,
|
|
56
|
+
height
|
|
57
|
+
});
|
|
58
|
+
return img.src;
|
|
59
|
+
} else {
|
|
60
|
+
return image.src;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (image) {
|
|
65
|
+
const src = await getImageSrc({ image, format, quality, width, height, optimize });
|
|
66
|
+
styles.push(`background-image:url("${src}")${important ? '!important' : ''};`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
size && styles.push(`background-size:${size}${important ? '!important' : ''};`);
|
|
70
|
+
position && styles.push(`background-position:${position}${important ? '!important' : ''};`);
|
|
71
|
+
repeat && styles.push(`background-repeat:${repeat}${important ? '!important' : ''};`);
|
|
72
|
+
|
|
73
|
+
for (const bp of breakpoints) {
|
|
74
|
+
const widths = [];
|
|
75
|
+
if (bp.minWidth) {
|
|
76
|
+
widths.push(`(min-width:${typeof bp.minWidth == 'number' ? `${bp.minWidth}px` : bp.minWidth})`);
|
|
77
|
+
}
|
|
78
|
+
if (bp.maxWidth) {
|
|
79
|
+
widths.push(`(max-width:${typeof bp.maxWidth == 'number' ? `${bp.maxWidth}px` : bp.maxWidth})`);
|
|
80
|
+
}
|
|
81
|
+
if (widths.length > 0) {
|
|
82
|
+
// const bpImg = await getImage({ src: bp.image });
|
|
83
|
+
styles.push(`@media screen and ${widths.join(' and ')}{`);
|
|
84
|
+
if (bp.image) {
|
|
85
|
+
const src = await getImageSrc(bp);
|
|
86
|
+
styles.push(`background-image:url("${src}")${important ? '!important' : ''};`);
|
|
87
|
+
}
|
|
88
|
+
bp.size && styles.push(`background-size:${bp.size}${important ? '!important' : ''};`);
|
|
89
|
+
bp.position && styles.push(`background-position:${bp.position}${important ? '!important' : ''};`);
|
|
90
|
+
bp.repeat && styles.push(`background-repeat:${bp.repeat}${important ? '!important' : ''};`);
|
|
91
|
+
styles.push('}');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const html = [`${import.meta.env.PROD ? '' : '\n'}${selector ?? 'body'}{`]
|
|
96
|
+
.concat(styles.map((s) => {
|
|
97
|
+
return `${s}`;
|
|
98
|
+
}))
|
|
99
|
+
.concat(['}'])
|
|
100
|
+
.join(import.meta.env.PROD ? '' : '\n');
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
<style set:html={html} />
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
---
|
|
2
|
+
// See /docs/components/CssVariables.md
|
|
3
|
+
import type { ImageMetadata, ImageOutputFormat } from "astro";
|
|
4
|
+
import { getImage } from "astro:assets";
|
|
5
|
+
|
|
6
|
+
export type CssVariableType = 'image' | 'url';
|
|
7
|
+
|
|
8
|
+
export interface Breakpoint {
|
|
9
|
+
minWidth?: string | number;
|
|
10
|
+
maxWidth?: string | number;
|
|
11
|
+
value: string | number | ImageMetadata;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface CssVariable {
|
|
15
|
+
name: string;
|
|
16
|
+
type?: CssVariableType;
|
|
17
|
+
value: string | number | ImageMetadata;
|
|
18
|
+
breakpoints?: Breakpoint[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ImageCssVariable extends CssVariable {
|
|
22
|
+
type: 'image';
|
|
23
|
+
value: ImageMetadata;
|
|
24
|
+
optimize?: boolean;
|
|
25
|
+
format?: ImageOutputFormat;
|
|
26
|
+
quality?: number;
|
|
27
|
+
width?: number;
|
|
28
|
+
height?: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface Props {
|
|
32
|
+
selector?: string;
|
|
33
|
+
variables: (CssVariable | ImageCssVariable)[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const { selector, variables } = Astro.props;
|
|
37
|
+
|
|
38
|
+
const breakpoints: {
|
|
39
|
+
minWidth?: string | number,
|
|
40
|
+
maxWidth?: string | number,
|
|
41
|
+
values: {
|
|
42
|
+
name: string,
|
|
43
|
+
type?: CssVariableType,
|
|
44
|
+
value: string | number | ImageMetadata
|
|
45
|
+
}[]
|
|
46
|
+
}[] = [];
|
|
47
|
+
|
|
48
|
+
// group all the breakpoints together to minimize the output
|
|
49
|
+
for (const v of variables) {
|
|
50
|
+
if (v.breakpoints) {
|
|
51
|
+
for (const bp of v.breakpoints) {
|
|
52
|
+
const b = breakpoints.find((pt) => {
|
|
53
|
+
return pt.minWidth === bp.minWidth && pt.maxWidth === bp.maxWidth;
|
|
54
|
+
});
|
|
55
|
+
if (b) {
|
|
56
|
+
b.values.push({
|
|
57
|
+
name: v.name,
|
|
58
|
+
type: v.type,
|
|
59
|
+
value: bp.value
|
|
60
|
+
});
|
|
61
|
+
} else {
|
|
62
|
+
breakpoints.push({
|
|
63
|
+
minWidth: bp.minWidth,
|
|
64
|
+
maxWidth: bp.maxWidth,
|
|
65
|
+
values: [{
|
|
66
|
+
name: v.name,
|
|
67
|
+
type: v.type,
|
|
68
|
+
value: bp.value
|
|
69
|
+
}]
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function createVariable(v: ImageCssVariable | CssVariable) {
|
|
77
|
+
switch (v.type) {
|
|
78
|
+
case 'image': {
|
|
79
|
+
const iv = v as ImageCssVariable;
|
|
80
|
+
if (iv.optimize !== false) {
|
|
81
|
+
const img = await getImage({
|
|
82
|
+
src: iv.value,
|
|
83
|
+
format: iv.format ?? iv.value.format,
|
|
84
|
+
quality: iv.quality,
|
|
85
|
+
width: iv.width,
|
|
86
|
+
height: iv.height
|
|
87
|
+
});
|
|
88
|
+
return `--${v.name}:url("${img.src}");`;
|
|
89
|
+
} else {
|
|
90
|
+
return `--${v.name}:url("${iv.value.src}");`;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
case 'url':
|
|
94
|
+
return `--${v.name}:url("${v.value}");`;
|
|
95
|
+
default:
|
|
96
|
+
return `--${v.name}:${v.value};`;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const styles: string[] = [];
|
|
101
|
+
for (const v of variables) {
|
|
102
|
+
styles.push(await createVariable(v));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
for (const bp of breakpoints) {
|
|
106
|
+
const widths = [];
|
|
107
|
+
if (bp.minWidth) {
|
|
108
|
+
widths.push(`(min-width:${typeof bp.minWidth == 'number' ? `${bp.minWidth}px` : bp.minWidth})`);
|
|
109
|
+
}
|
|
110
|
+
if (bp.maxWidth) {
|
|
111
|
+
widths.push(`(max-width:${typeof bp.maxWidth == 'number' ? `${bp.maxWidth}px` : bp.maxWidth})`);
|
|
112
|
+
}
|
|
113
|
+
if (widths.length > 0) {
|
|
114
|
+
styles.push(`@media screen and ${widths.join(' and ')}{`);
|
|
115
|
+
for (const v of bp.values) {
|
|
116
|
+
styles.push(await createVariable(v));
|
|
117
|
+
}
|
|
118
|
+
styles.push('}');
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const html = [`${import.meta.env.PROD ? '' : '\n'}${selector ?? ':root'}{`]
|
|
123
|
+
.concat(styles.map((s) => {
|
|
124
|
+
return `${s}`;
|
|
125
|
+
}))
|
|
126
|
+
.concat(['}'])
|
|
127
|
+
.join(import.meta.env.PROD ? '' : '\n');
|
|
128
|
+
---
|
|
129
|
+
<style set:html={html} />
|
|
@@ -37,11 +37,11 @@ const backTriggerSlot = Astro.slots.has('back-trigger') ? await Astro.slots.rend
|
|
|
37
37
|
<evo-flip-card flipped="false" style={style.join(';')} {...attrs}>
|
|
38
38
|
<div class="evo-flip-card-wrapper">
|
|
39
39
|
<div class="evo-flip-card-front">
|
|
40
|
-
<button class="evo-flip-card-trigger"
|
|
40
|
+
<button class="evo-flip-card-trigger"><Fragment set:html={frontTriggerSlot ?? '↻'} /></button>
|
|
41
41
|
<slot name="front" />
|
|
42
42
|
</div>
|
|
43
43
|
<div class="evo-flip-card-back">
|
|
44
|
-
<button class="evo-flip-card-trigger"
|
|
44
|
+
<button class="evo-flip-card-trigger"><Fragment set:html={backTriggerSlot ?? '↻'} /></button>
|
|
45
45
|
<slot name="back" />
|
|
46
46
|
</div>
|
|
47
47
|
</div>
|
|
@@ -74,14 +74,14 @@ export class EvoModalElement extends HTMLElement implements IEvoModalElement {
|
|
|
74
74
|
|
|
75
75
|
const duration = getAnimationDuration(this);
|
|
76
76
|
|
|
77
|
-
this.classList.add(`${CSS_PREFIX_HIDE}-${
|
|
78
|
-
this.classList.remove(`${CSS_PREFIX_SHOW}-${
|
|
77
|
+
this.classList.add(`${CSS_PREFIX_HIDE}-${animation}`);
|
|
78
|
+
this.classList.remove(`${CSS_PREFIX_SHOW}-${animation}`);
|
|
79
79
|
if (overlay) {
|
|
80
80
|
hideOverlay({ modal: this, animation: this.#animation, duration });
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
setTimeout(() => {
|
|
84
|
-
this.classList.remove('evo-modal-visible', `${CSS_PREFIX_HIDE}-${
|
|
84
|
+
this.classList.remove('evo-modal-visible', `${CSS_PREFIX_HIDE}-${animation}`);
|
|
85
85
|
this.dispatchEvent(createModalEvent('evomodal-hidden', this, { replacedBy }));
|
|
86
86
|
resolve();
|
|
87
87
|
}, animation !== 'none' ? duration : 0);
|
|
@@ -176,7 +176,7 @@ export function hideOverlay({ modal, animation = 'fade', duration }: { modal?: E
|
|
|
176
176
|
/**
|
|
177
177
|
* Call this function when dynamically adding a trigger to the DOM
|
|
178
178
|
*/
|
|
179
|
-
export function bindTriggers(container = document) {
|
|
179
|
+
export function bindTriggers(container: Document | HTMLElement = document) {
|
|
180
180
|
container.querySelectorAll<HTMLElement>('[data-evo-modal-show]').forEach((trigger) => {
|
|
181
181
|
if (!trigger.hasAttribute('data-evo-modal-bound')) {
|
|
182
182
|
trigger.setAttribute('data-evo-modal-bound','true');
|
|
@@ -32,7 +32,11 @@ switch (scrolling) {
|
|
|
32
32
|
break;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
containerAttrs.style = typeof containerAttrs.style == 'string'
|
|
35
|
+
containerAttrs.style = typeof containerAttrs.style == 'string'
|
|
36
|
+
? `${containerAttrs.style}${containerAttrs.style.endsWith(';') ? '' : ';'}`
|
|
37
|
+
: typeof containerAttrs.style == 'object' && containerAttrs.style !== null
|
|
38
|
+
? Object.entries(containerAttrs.style).map(([k, v]) => `${k.replace(/([A-Z])/g, '-$1').toLowerCase()}:${v}`).join(';') + ';'
|
|
39
|
+
: '';
|
|
36
40
|
if (width) {
|
|
37
41
|
containerAttrs.style = `${containerAttrs.style}width:${typeof width == 'number' || /^\d+$/.test(width) ? `${width}px` : width};`;
|
|
38
42
|
}
|
|
@@ -11,18 +11,6 @@ const intersectionObserver = new IntersectionObserver((entries) => {
|
|
|
11
11
|
{ threshold: 0.1 }
|
|
12
12
|
);
|
|
13
13
|
|
|
14
|
-
const mutationObserver = new MutationObserver((mutations) => {
|
|
15
|
-
const containers = new Set<IScrollContainer>();
|
|
16
|
-
mutations.forEach((mutation) => {
|
|
17
|
-
const sc = getScrollContainer(mutation.target as HTMLElement);
|
|
18
|
-
if (sc) {
|
|
19
|
-
containers.add(sc);
|
|
20
|
-
}
|
|
21
|
-
});
|
|
22
|
-
[...containers].forEach((container) => {
|
|
23
|
-
container.refresh();
|
|
24
|
-
});
|
|
25
|
-
});
|
|
26
14
|
|
|
27
15
|
export type Scrolling = 'horizontal' | 'vertical' | 'both';
|
|
28
16
|
|
|
@@ -68,10 +56,12 @@ export class EvoScrollContainerElement extends HTMLElement implements IEvoScroll
|
|
|
68
56
|
#mouseupHandlerHorz: any = undefined;
|
|
69
57
|
#mousemoveHandlerHorz: any = undefined;
|
|
70
58
|
#boundResizeHandler: () => void;
|
|
59
|
+
#mutationObserver: MutationObserver;
|
|
71
60
|
|
|
72
61
|
constructor() {
|
|
73
62
|
super();
|
|
74
63
|
this.#boundResizeHandler = this.refresh.bind(this);
|
|
64
|
+
this.#mutationObserver = new MutationObserver(() => this.refresh());
|
|
75
65
|
}
|
|
76
66
|
|
|
77
67
|
connectedCallback() {
|
|
@@ -90,7 +80,7 @@ export class EvoScrollContainerElement extends HTMLElement implements IEvoScroll
|
|
|
90
80
|
this.#scrolling = (this.dataset.scrolling as Scrolling) ?? 'vertical';
|
|
91
81
|
|
|
92
82
|
intersectionObserver.observe(this);
|
|
93
|
-
mutationObserver.observe(this, { attributes: true, childList: true, subtree: true });
|
|
83
|
+
this.#mutationObserver.observe(this, { attributes: true, childList: true, subtree: true });
|
|
94
84
|
|
|
95
85
|
window.addEventListener('resize', this.#boundResizeHandler, { passive: true });
|
|
96
86
|
if (this.#scrolling === 'horizontal') {
|
|
@@ -100,7 +90,8 @@ export class EvoScrollContainerElement extends HTMLElement implements IEvoScroll
|
|
|
100
90
|
|
|
101
91
|
disconnectedCallback() {
|
|
102
92
|
intersectionObserver.unobserve(this);
|
|
103
|
-
|
|
93
|
+
this.#mutationObserver.disconnect();
|
|
94
|
+
window.removeEventListener('resize', this.#boundResizeHandler);
|
|
104
95
|
this.removeEventListener('wheel', this.#wheelHorz);
|
|
105
96
|
}
|
|
106
97
|
|
|
@@ -182,14 +173,14 @@ export class EvoScrollContainerElement extends HTMLElement implements IEvoScroll
|
|
|
182
173
|
}
|
|
183
174
|
}
|
|
184
175
|
|
|
185
|
-
removeEventListener(type: keyof HTMLElementEventMap, listener: (this: EvoScrollContainerElement, ev: any) => void) {
|
|
176
|
+
removeEventListener(type: keyof HTMLElementEventMap, listener: (this: EvoScrollContainerElement, ev: any) => void, options?: boolean | EventListenerOptions) {
|
|
186
177
|
switch (type) {
|
|
187
178
|
case 'scroll':
|
|
188
179
|
case 'scrollend':
|
|
189
|
-
this.#content.removeEventListener(type, listener);
|
|
180
|
+
this.#content.removeEventListener(type, listener, options);
|
|
190
181
|
break;
|
|
191
182
|
default:
|
|
192
|
-
super.removeEventListener(type, listener);
|
|
183
|
+
super.removeEventListener(type, listener, options);
|
|
193
184
|
break;
|
|
194
185
|
}
|
|
195
186
|
}
|
|
@@ -198,45 +189,49 @@ export class EvoScrollContainerElement extends HTMLElement implements IEvoScroll
|
|
|
198
189
|
if (this.#thumbVert && this.#trackVert) {
|
|
199
190
|
const scrollHeight = this.#content.scrollHeight - getPaddingY(this.#content);
|
|
200
191
|
const containerHeight = this.clientHeight - getPaddingY(this);
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
this.#
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
192
|
+
|
|
193
|
+
if ((containerHeight / scrollHeight) < 1 || this.#scrollbarSettings.trackVisible === true) { // should scroll
|
|
194
|
+
const trackHeight = this.#trackVert.clientHeight;
|
|
195
|
+
const trackStyle = getComputedStyle(this.#trackVert);
|
|
196
|
+
const trackTop = parseInt(trackStyle.getPropertyValue('--evo-scroll-container-track-vert-top'), 10);
|
|
197
|
+
const trackBot = parseInt(trackStyle.getPropertyValue('--evo-scroll-container-track-vert-bottom'), 10);
|
|
198
|
+
const trackOffset = parseInt(trackStyle.getPropertyValue('--evo-scroll-container-height-offset'), 10);
|
|
199
|
+
const trackAdjust = (!isNaN(trackTop) ? trackTop : 0) + (!isNaN(trackBot) ? trackBot : 0) + (!isNaN(trackOffset) ? trackOffset : 0);
|
|
200
|
+
|
|
201
|
+
// const shouldScroll = (containerHeight / scrollHeight) < 1;
|
|
202
|
+
const visibleRatioVert = (containerHeight - trackAdjust) / scrollHeight;
|
|
203
|
+
const thumbHeight = visibleRatioVert * trackHeight;
|
|
204
|
+
|
|
205
|
+
if (this.#scrollbarSettings.thumbHeight != undefined) {
|
|
206
|
+
this.#thumbVert.style.height = `${Math.max(0, Math.min(this.#scrollbarSettings.thumbHeight, trackHeight))}px`;
|
|
207
|
+
} else {
|
|
208
|
+
this.#thumbVert.style.height = `${Math.min(thumbHeight > 20 ? thumbHeight : 20, trackHeight)}px`;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
this.#trackVert.style.display = 'block';
|
|
218
212
|
} else {
|
|
219
|
-
this.#trackVert.style.display =
|
|
213
|
+
this.#trackVert.style.display = 'none';
|
|
220
214
|
}
|
|
221
215
|
}
|
|
222
216
|
|
|
223
217
|
if (this.#thumbHorz && this.#trackHorz) {
|
|
224
218
|
const scrollWidth = this.#content.scrollWidth - getPaddingX(this.#content);
|
|
225
219
|
const containerWidth = this.clientWidth - getPaddingX(this);
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
this.#
|
|
220
|
+
|
|
221
|
+
if ((containerWidth / scrollWidth) < 1 || this.#scrollbarSettings.trackVisible === true) {
|
|
222
|
+
const trackWidth = this.#trackHorz.clientWidth;
|
|
223
|
+
const trackStyle = getComputedStyle(this.#trackHorz);
|
|
224
|
+
const trackLeft = parseInt(trackStyle.getPropertyValue('--evo-scroll-container-track-horz-left'), 10);
|
|
225
|
+
const trackRight = parseInt(trackStyle.getPropertyValue('--evo-scroll-container-track-horz-right'), 10);
|
|
226
|
+
const trackOffset = parseInt(trackStyle.getPropertyValue('--evo-scroll-container-width-offset'), 10);
|
|
227
|
+
const trackAdjust = (!isNaN(trackLeft) ? trackLeft : 0) + (!isNaN(trackRight) ? trackRight : 0) + (!isNaN(trackOffset) ? trackOffset : 0);
|
|
228
|
+
|
|
229
|
+
const visibleRatioHorz = (containerWidth - trackAdjust) / scrollWidth;
|
|
230
|
+
const thumbWidth = visibleRatioHorz * trackWidth;
|
|
231
|
+
this.#thumbHorz.style.width = `${Math.min(thumbWidth > 20 ? thumbWidth : 20, trackWidth)}px`;
|
|
232
|
+
this.#trackHorz.style.display = 'block';
|
|
238
233
|
} else {
|
|
239
|
-
this.#trackHorz.style.display =
|
|
234
|
+
this.#trackHorz.style.display = 'none';
|
|
240
235
|
}
|
|
241
236
|
}
|
|
242
237
|
}
|
|
@@ -257,7 +252,7 @@ export class EvoScrollContainerElement extends HTMLElement implements IEvoScroll
|
|
|
257
252
|
#thumbMouseMoveVert(e: MouseEvent) {
|
|
258
253
|
if (this.#isDraggingVert) {
|
|
259
254
|
const deltaY = e.clientY - this.#startY;
|
|
260
|
-
const thumbTrackHeight = this
|
|
255
|
+
const thumbTrackHeight = this.#trackVert!.clientHeight - this.#thumbVert!.clientHeight;
|
|
261
256
|
if (thumbTrackHeight <= 0) return;
|
|
262
257
|
const scrollRatio = (this.#content.scrollHeight - this.clientHeight) / thumbTrackHeight;
|
|
263
258
|
this.#content.scrollTop = this.#startScrollTop + deltaY * scrollRatio;
|
|
@@ -290,7 +285,7 @@ export class EvoScrollContainerElement extends HTMLElement implements IEvoScroll
|
|
|
290
285
|
#thumbMouseMoveHorz(e: MouseEvent) {
|
|
291
286
|
if (this.#isDraggingHorz) {
|
|
292
287
|
const deltaX = e.clientX - this.#startX;
|
|
293
|
-
const thumbTrackWidth = this
|
|
288
|
+
const thumbTrackWidth = this.#trackHorz!.clientWidth - this.#thumbHorz!.clientWidth;
|
|
294
289
|
if (thumbTrackWidth <= 0) return;
|
|
295
290
|
const scrollRatio = (this.#content.scrollWidth - this.clientWidth) / thumbTrackWidth;
|
|
296
291
|
this.#content.scrollLeft = this.#startScrollLeft + deltaX * scrollRatio;
|
|
@@ -363,8 +358,8 @@ export function refresh(selector?: string | HTMLElement) {
|
|
|
363
358
|
sc.refresh()
|
|
364
359
|
}
|
|
365
360
|
} else {
|
|
366
|
-
[...document.querySelectorAll<EvoScrollContainerElement>(EvoScrollContainerElement.htmlTagName)].forEach((
|
|
367
|
-
|
|
361
|
+
[...document.querySelectorAll<EvoScrollContainerElement>(EvoScrollContainerElement.htmlTagName)].forEach((sc) => {
|
|
362
|
+
sc.refresh();
|
|
368
363
|
});
|
|
369
364
|
}
|
|
370
365
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
---
|
|
2
2
|
import type { HTMLAttributes } from 'astro/types';
|
|
3
|
-
import { undefined } from 'astro:schema';
|
|
4
3
|
interface Props {
|
|
5
4
|
active?: true;
|
|
6
5
|
tabAttrs?: HTMLAttributes<'div'>
|
|
@@ -15,7 +14,7 @@ const tabTitle = await Astro.slots.render('title');
|
|
|
15
14
|
const attrs = { ...tabAttrs, title: tabTitle };
|
|
16
15
|
---
|
|
17
16
|
<div
|
|
18
|
-
data-tab-attrs={
|
|
17
|
+
data-tab-attrs={btoa(JSON.stringify(attrs))}
|
|
19
18
|
data-tab-active={active ?? false}
|
|
20
19
|
{...panelAttrs}
|
|
21
20
|
role="tabpanel"
|
|
@@ -55,8 +55,7 @@ const itemHtml = html.replace(
|
|
|
55
55
|
let updated = match
|
|
56
56
|
.replace(/\s*data-tab-attrs="[^"]*"/, '')
|
|
57
57
|
.replace(/\s*data-tab-active="[^"]*"/, '')
|
|
58
|
-
.replace(/\s*hidden(?:="[^"]*")?/, '')
|
|
59
|
-
+ (active ? '' : '');
|
|
58
|
+
.replace(/\s*hidden(?:="[^"]*")?/, '');
|
|
60
59
|
|
|
61
60
|
// Insert data-tab index
|
|
62
61
|
updated = updated.replace(/^<(\w+)/, `<$1 data-tab="${currentIndex}"`);
|
|
@@ -79,7 +78,7 @@ const tabHtml = tabs.map((tab) => {
|
|
|
79
78
|
Object.keys(tab)
|
|
80
79
|
.filter(key => !['class','active','title','index'].includes(key))
|
|
81
80
|
.forEach((key) => {
|
|
82
|
-
attrs.push(`${key}="${tab[key as keyof typeof tab].replace(/"/g, '')}"`)
|
|
81
|
+
attrs.push(`${key}="${String(tab[key as keyof typeof tab] ?? '').replace(/"/g, '')}"`)
|
|
83
82
|
});
|
|
84
83
|
|
|
85
84
|
if (listType === 'none') {
|
package/src/components/index.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
export { default as CssBackgroundImage } from './CssBackgroundImage.astro';
|
|
2
|
+
export { default as CssVariables } from './CssVariables.astro';
|
|
1
3
|
export { default as FlipCard } from './FlipCard/FlipCard.astro';
|
|
2
4
|
export { default as RightISI } from './ISI/Right/RightISI.astro';
|
|
3
5
|
export { default as Modal } from './Modal/Modal.astro';
|
|
@@ -20,6 +20,7 @@ async function getFiles(dir: string, { includeExts, excludeExts }: { includeExts
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
interface PruneBuildOpts {
|
|
23
|
+
enabled?: boolean;
|
|
23
24
|
keep?: (string | RegExp)[]
|
|
24
25
|
}
|
|
25
26
|
export default function(opts?: PruneBuildOpts): AstroIntegration {
|
|
@@ -27,43 +28,50 @@ export default function(opts?: PruneBuildOpts): AstroIntegration {
|
|
|
27
28
|
name: 'prune-build',
|
|
28
29
|
hooks: {
|
|
29
30
|
'astro:build:done': async ({ dir, logger }) => {
|
|
31
|
+
if (opts?.enabled === false) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
30
34
|
logger.info('Pruning unused assets from build');
|
|
31
35
|
let dirPath = fileURLToPath(dir);
|
|
32
36
|
if (dirPath.endsWith(sep)) {
|
|
33
37
|
dirPath = dirPath.slice(0, -1);
|
|
34
38
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
try {
|
|
40
|
+
const assets = await getFiles(dirPath, { excludeExts: ['.html'] });
|
|
41
|
+
const files = await getFiles(dirPath, { includeExts: ['.css', '.html', '.js'] });
|
|
42
|
+
const keepAssets = new Set<string>();
|
|
43
|
+
|
|
44
|
+
for (const file of files) {
|
|
45
|
+
const content = await readFile(file, 'utf8');
|
|
46
|
+
for (const asset of assets) {
|
|
47
|
+
if (content.includes(basename(asset))) {
|
|
48
|
+
keepAssets.add(asset);
|
|
49
|
+
}
|
|
44
50
|
}
|
|
45
51
|
}
|
|
46
|
-
}
|
|
47
52
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
for (const asset of assets) {
|
|
54
|
+
if (!keepAssets.has(asset)) {
|
|
55
|
+
let prune = true;
|
|
56
|
+
const relPath = asset.slice(dirPath.length).replace(/\\/g, '/');
|
|
57
|
+
if (opts?.keep && opts.keep.length > 0) {
|
|
58
|
+
for (const k of opts.keep) {
|
|
59
|
+
if ((typeof k == 'string' && k === relPath) || (k instanceof RegExp && k.test(relPath))) {
|
|
60
|
+
prune = false;
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
57
63
|
}
|
|
58
64
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
+
if (prune) {
|
|
66
|
+
logger.warn(`Pruning asset: ${relPath}`);
|
|
67
|
+
await rm(asset, { force: true });
|
|
68
|
+
} else {
|
|
69
|
+
logger.info(`Retaining asset: ${relPath}`);
|
|
70
|
+
}
|
|
65
71
|
}
|
|
66
72
|
}
|
|
73
|
+
} catch (error: any) {
|
|
74
|
+
logger.error(error.toString());
|
|
67
75
|
}
|
|
68
76
|
}
|
|
69
77
|
}
|
|
@@ -1,22 +1,47 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/** `'spread'` — fingers moving apart; `'pinch'` — fingers moving together. */
|
|
2
|
+
export type PinchGesture = 'spread' | 'pinch';
|
|
3
3
|
|
|
4
|
+
export interface PinchController {
|
|
5
|
+
enable: () => void;
|
|
6
|
+
disable: () => void;
|
|
7
|
+
dispose: () => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Attaches a two-finger pinch gesture listener to `window`.
|
|
12
|
+
*
|
|
13
|
+
* Tracks the cumulative scale across gestures, snapping to `1` when crossing
|
|
14
|
+
* the natural scale boundary. Fires `onPinch` on every move tick where the
|
|
15
|
+
* finger distance changes.
|
|
16
|
+
*
|
|
17
|
+
* @param onPinch - Called with the current cumulative scale and gesture direction.
|
|
18
|
+
* @returns A `PinchController` with `enable`, `disable`, and `dispose` methods.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* const pinch = createPinchListener((scale, gesture) => {
|
|
22
|
+
* console.log(gesture, scale); // 'spread' 1.23
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* // Later, to clean up:
|
|
26
|
+
* pinch.dispose();
|
|
27
|
+
*/
|
|
28
|
+
export function createPinchListener(onPinch: (scale: number, gesture: PinchGesture) => void): PinchController {
|
|
4
29
|
let initialDist: number = 0;
|
|
5
30
|
let lastDistance: number = 0;
|
|
6
31
|
let currentScale: number = 1;
|
|
7
32
|
let activeScale: number = 1;
|
|
8
33
|
|
|
9
|
-
|
|
34
|
+
const getTouchDistance = (touches: TouchList) => {
|
|
10
35
|
return Math.hypot(touches[0].clientX - touches[1].clientX, touches[0].clientY - touches[1].clientY);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const reset = () => {
|
|
14
39
|
window.removeEventListener('touchmove', touchmove);
|
|
15
40
|
window.removeEventListener('touchend', touchend);
|
|
16
41
|
currentScale = activeScale;
|
|
17
42
|
initialDist = 0;
|
|
18
43
|
lastDistance = 0;
|
|
19
|
-
}
|
|
44
|
+
};
|
|
20
45
|
|
|
21
46
|
const touchstart = (e: TouchEvent) => {
|
|
22
47
|
if (e.touches.length === 2) {
|
|
@@ -36,22 +61,22 @@ export function createPinchListener() {
|
|
|
36
61
|
|
|
37
62
|
const rawScale = currentScale * ratio;
|
|
38
63
|
|
|
39
|
-
// Detect if we're crossing 1 from either
|
|
64
|
+
// Detect if we're crossing 1 from either gesture
|
|
40
65
|
const crossingOne =
|
|
41
66
|
(currentScale > 1 && rawScale < 1) ||
|
|
42
67
|
(currentScale < 1 && rawScale > 1);
|
|
43
68
|
|
|
44
69
|
activeScale = crossingOne ? 1 : rawScale; // snap to 1
|
|
45
70
|
|
|
46
|
-
let
|
|
71
|
+
let gesture: PinchGesture | undefined = undefined;
|
|
47
72
|
if (currentDistance > lastDistance) {
|
|
48
|
-
|
|
73
|
+
gesture = 'spread';
|
|
49
74
|
} else if (currentDistance < lastDistance) {
|
|
50
|
-
|
|
75
|
+
gesture = 'pinch';
|
|
51
76
|
}
|
|
52
77
|
|
|
53
|
-
if (
|
|
54
|
-
|
|
78
|
+
if (gesture !== undefined) {
|
|
79
|
+
onPinch(activeScale, gesture);
|
|
55
80
|
lastDistance = currentDistance;
|
|
56
81
|
}
|
|
57
82
|
}
|
|
@@ -62,5 +87,27 @@ export function createPinchListener() {
|
|
|
62
87
|
reset();
|
|
63
88
|
}
|
|
64
89
|
};
|
|
65
|
-
|
|
66
|
-
|
|
90
|
+
|
|
91
|
+
let listening = false;
|
|
92
|
+
|
|
93
|
+
const enable = () => {
|
|
94
|
+
if (!listening) {
|
|
95
|
+
listening = true;
|
|
96
|
+
window.addEventListener('touchstart', touchstart, { passive: true });
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const disable = () => {
|
|
101
|
+
listening = false;
|
|
102
|
+
window.removeEventListener('touchstart', touchstart);
|
|
103
|
+
reset();
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
enable();
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
enable,
|
|
110
|
+
disable,
|
|
111
|
+
dispose: disable
|
|
112
|
+
};
|
|
113
|
+
}
|