@inizioevoke/astro-core 2.1.2 → 2.2.1

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.
@@ -0,0 +1,102 @@
1
+ # Tabs
2
+
3
+ The Tabs component renders a tabbed panel interface. Each tab is defined by a `TabItem` child component. `Tabs` processes its slot at build time, extracts tab metadata from each `TabItem`, and generates the tab list and panels from a single slot.
4
+
5
+ ## Components
6
+
7
+ ### `Tabs`
8
+
9
+ The outer container that renders the tab list and panels.
10
+
11
+ #### Props
12
+
13
+ | Prop | Type | Default | Description |
14
+ |------|------|---------|-------------|
15
+ | `listType` | `'ul' \| 'none'` | `'ul'` | Controls the tab list element. `'ul'` wraps each tab button in an `<li>` (default); `'none'` renders buttons directly in a `<div>` |
16
+
17
+ #### Slots
18
+
19
+ | Slot | Description |
20
+ |------|-------------|
21
+ | *(default)* | One or more `<TabItem>` components |
22
+
23
+ ---
24
+
25
+ ### `TabItem`
26
+
27
+ Defines a single tab. Rendered as a child of `Tabs`. The tab button label comes from the `title` slot; the panel content comes from the default slot.
28
+
29
+ #### Props
30
+
31
+ | Prop | Type | Default | Description |
32
+ |------|------|---------|-------------|
33
+ | `active` | `true` | — | Marks this tab as the initially active one. Omit on all others. |
34
+ | `tabAttrs` | `HTMLAttributes<'div'>` | — | Attributes spread onto the generated tab button. `title` here sets the button label as a fallback if the `title` slot is empty. |
35
+ | `panelAttrs` | `HTMLAttributes<'div'>` | — | Attributes spread onto the panel `<div role="tabpanel">` |
36
+
37
+ #### Slots
38
+
39
+ | Slot | Description |
40
+ |------|-------------|
41
+ | `title` | The tab button label |
42
+ | *(default)* | The panel content shown when this tab is active |
43
+
44
+ ---
45
+
46
+ ## Examples
47
+
48
+ ### Basic tabs
49
+
50
+ ```astro
51
+ ---
52
+ import { Tabs, TabItem } from '@inizioevoke/astro-core';
53
+ ---
54
+ <Tabs>
55
+ <TabItem active>
56
+ <span slot="title">First</span>
57
+ <p>Content for the first tab.</p>
58
+ </TabItem>
59
+ <TabItem>
60
+ <span slot="title">Second</span>
61
+ <p>Content for the second tab.</p>
62
+ </TabItem>
63
+ <TabItem>
64
+ <span slot="title">Third</span>
65
+ <p>Content for the third tab.</p>
66
+ </TabItem>
67
+ </Tabs>
68
+ ```
69
+
70
+ ### Button-style tabs (`listType="none"`)
71
+
72
+ Renders tab buttons in a `<div>` instead of a `<ul>/<li>` structure — useful when you want a button-bar style rather than a classic tabbed nav.
73
+
74
+ ```astro
75
+ <Tabs listType="none">
76
+ <TabItem active>
77
+ <span slot="title">Overview</span>
78
+ <p>Overview content.</p>
79
+ </TabItem>
80
+ <TabItem>
81
+ <span slot="title">Details</span>
82
+ <p>Details content.</p>
83
+ </TabItem>
84
+ </Tabs>
85
+ ```
86
+
87
+ ### Custom attributes on tabs and panels
88
+
89
+ Use `tabAttrs` to add attributes to the tab button (e.g. `id`, `aria-*`), and `panelAttrs` to add attributes to the panel element.
90
+
91
+ ```astro
92
+ <Tabs>
93
+ <TabItem
94
+ active
95
+ tabAttrs={{ id: 'tab-1', 'aria-controls': 'panel-1' }}
96
+ panelAttrs={{ id: 'panel-1' }}
97
+ >
98
+ <span slot="title">Tab One</span>
99
+ <p>Panel content.</p>
100
+ </TabItem>
101
+ </Tabs>
102
+ ```
@@ -0,0 +1,75 @@
1
+ # prune-build
2
+
3
+ An Astro integration that removes unreferenced assets from the build output after `astro build` completes. It scans all `.css`, `.html`, and `.js` files in the output directory and deletes any asset whose filename does not appear in any of those files.
4
+
5
+ ## Usage
6
+
7
+ ```ts
8
+ // astro.config.mjs
9
+ import { defineConfig } from 'astro/config';
10
+ import { pruneBuild } from '@inizioevoke/astro-core/integrations';
11
+
12
+ export default defineConfig({
13
+ integrations: [pruneBuild()],
14
+ });
15
+ ```
16
+
17
+ ## Options
18
+
19
+ | Option | Type | Default | Description |
20
+ |--------|------|---------|-------------|
21
+ | `enabled` | `boolean` | `true` | Set to `false` to skip pruning entirely. Useful for disabling conditionally (e.g. in dev/preview environments). |
22
+ | `keep` | `(string \| RegExp)[]` | `[]` | Assets to retain even if unreferenced. Strings are matched by exact relative path; RegExps are tested against the relative path. |
23
+
24
+ Relative paths are rooted at the build output directory and begin with `/` (e.g. `/_astro/logo.svg`).
25
+
26
+ ## How it works
27
+
28
+ 1. After the build completes, all non-HTML files in the output directory are collected as candidate assets.
29
+ 2. All `.css`, `.html`, and `.js` files are scanned for any occurrence of each asset's **filename** (basename only, no directory).
30
+ 3. Any asset whose filename is not found in any scanned file is deleted, unless it matches an entry in `opts.keep`.
31
+
32
+ ## Examples
33
+
34
+ ### Default — prune all unreferenced assets
35
+
36
+ ```ts
37
+ integrations: [pruneBuild()]
38
+ ```
39
+
40
+ ### Retain specific files
41
+
42
+ Use an exact relative path string to keep a specific file:
43
+
44
+ ```ts
45
+ integrations: [
46
+ pruneBuild({
47
+ keep: ['/_astro/logo.svg'],
48
+ }),
49
+ ]
50
+ ```
51
+
52
+ ### Retain files matching a pattern
53
+
54
+ Use a `RegExp` to keep a set of files by pattern:
55
+
56
+ ```ts
57
+ integrations: [
58
+ pruneBuild({
59
+ keep: [/\/_astro\/fonts\//],
60
+ }),
61
+ ]
62
+ ```
63
+
64
+ ### Mix strings and patterns
65
+
66
+ ```ts
67
+ integrations: [
68
+ pruneBuild({
69
+ keep: [
70
+ '/_astro/og-image.png',
71
+ /\/_astro\/icons\//,
72
+ ],
73
+ }),
74
+ ]
75
+ ```
@@ -0,0 +1,53 @@
1
+ # relative-links
2
+
3
+ An Astro integration that rewrites absolute asset paths in the build output to relative paths after `astro build` completes. This is useful when deploying to a subdirectory or when the final host path is unknown at build time.
4
+
5
+ ## Usage
6
+
7
+ ```ts
8
+ // astro.config.mjs
9
+ import { defineConfig } from 'astro/config';
10
+ import { relativeLinks } from '@inizioevoke/astro-core/integrations';
11
+
12
+ export default defineConfig({
13
+ integrations: [relativeLinks()],
14
+ });
15
+ ```
16
+
17
+ ## Options
18
+
19
+ | Option | Type | Default | Description |
20
+ |--------|------|---------|-------------|
21
+ | `enabled` | `boolean` | `true` | Set to `false` to skip processing entirely. Useful for disabling conditionally (e.g. in dev/preview environments). |
22
+
23
+ ## How it works
24
+
25
+ 1. After the build completes, all `.html` and `.css` files in the output directory are collected.
26
+ 2. Each file is scanned for occurrences of the `base` path (from `astro.config.mjs`) in attribute values and `url()` expressions.
27
+ 3. Matching absolute references are replaced with a relative path calculated from the file's location up to the output root.
28
+
29
+ ### Where replacements are applied in HTML files
30
+
31
+ - Attribute values: `href`, `src`, `srcset`, `poster`, `component-url`, `renderer-url`
32
+ - Inline `style` attribute `url()` expressions
33
+ - `<style>` block `url()` expressions
34
+
35
+ ### Where replacements are applied in CSS files
36
+
37
+ - `url()` expressions
38
+
39
+ ## Examples
40
+
41
+ ### Default — rewrite all absolute paths
42
+
43
+ ```ts
44
+ integrations: [relativeLinks()]
45
+ ```
46
+
47
+ ### Disable conditionally
48
+
49
+ ```ts
50
+ integrations: [
51
+ relativeLinks({ enabled: process.env.NODE_ENV !== 'development' }),
52
+ ]
53
+ ```
@@ -0,0 +1,98 @@
1
+ # createPinchListener
2
+
3
+ Attaches a two-finger pinch gesture listener to `window`. Tracks cumulative scale across gestures, snapping to `1` when crossing the natural scale boundary. Fires a callback on every move tick where the finger distance changes.
4
+
5
+ ## Usage
6
+
7
+ ```ts
8
+ import { createPinchListener } from '@inizioevoke/astro-core/lib/gestures';
9
+
10
+ const pinch = createPinchListener((scale, gesture) => {
11
+ console.log(gesture, scale); // 'spread' 1.23
12
+ });
13
+
14
+ // Later, to clean up:
15
+ pinch.dispose();
16
+ ```
17
+
18
+ ## API
19
+
20
+ ### `createPinchListener(onPinch)`
21
+
22
+ | Parameter | Type | Description |
23
+ |-----------|------|-------------|
24
+ | `onPinch` | `(scale: number, gesture: PinchGesture) => void` | Called each time the pinch distance changes. Receives the current cumulative scale and the gesture direction. |
25
+
26
+ **Returns** a `PinchController`.
27
+
28
+ ### `PinchController`
29
+
30
+ | Method | Description |
31
+ |--------|-------------|
32
+ | `enable()` | Attaches listeners and begins tracking. Called automatically on creation. |
33
+ | `disable()` | Removes all listeners and resets internal state. Can be re-enabled with `enable()`. |
34
+ | `dispose()` | Alias for `disable()`. Use when tearing down permanently. |
35
+
36
+ ### `PinchGesture`
37
+
38
+ ```ts
39
+ type PinchGesture = 'spread' | 'pinch'
40
+ ```
41
+
42
+ | Value | Description |
43
+ |-------|-------------|
44
+ | `'spread'` | Fingers moving apart |
45
+ | `'pinch'` | Fingers moving together |
46
+
47
+ ## Behavior
48
+
49
+ - Requires exactly two simultaneous touch points — single-finger touches are ignored.
50
+ - Scale is **cumulative**: each new gesture continues from where the previous one ended.
51
+ - Snaps to `1` when the scale crosses the natural boundary (e.g. transitioning from `1.2` to `0.9` in a single gesture yields `1` instead of `0.9`).
52
+ - All `touchmove` and `touchend` listeners are added passively and cleaned up automatically when the gesture ends.
53
+
54
+ ## Examples
55
+
56
+ ### Zoom an element
57
+
58
+ ```ts
59
+ import { createPinchListener } from '@inizioevoke/astro-core';
60
+
61
+ const image = document.querySelector('#zoomable') as HTMLElement;
62
+
63
+ const pinch = createPinchListener((scale) => {
64
+ image.style.transform = `scale(${scale})`;
65
+ });
66
+ ```
67
+
68
+ ### React to gesture direction
69
+
70
+ ```ts
71
+ import { createPinchListener, type PinchGesture } from '@inizioevoke/astro-core';
72
+
73
+ const pinch = createPinchListener((scale, gesture) => {
74
+ if (gesture === 'spread') {
75
+ // fingers apart — zooming in
76
+ } else {
77
+ // fingers together — zooming out
78
+ }
79
+ });
80
+ ```
81
+
82
+ ### Temporarily pause and resume
83
+
84
+ ```ts
85
+ const pinch = createPinchListener((scale, gesture) => { /* ... */ });
86
+
87
+ pinch.disable(); // pause
88
+ pinch.enable(); // resume
89
+ ```
90
+
91
+ ### Clean up on component teardown
92
+
93
+ ```ts
94
+ const pinch = createPinchListener((scale, gesture) => { /* ... */ });
95
+
96
+ // Astro / vanilla teardown
97
+ document.addEventListener('astro:before-swap', () => pinch.dispose(), { once: true });
98
+ ```
@@ -0,0 +1,147 @@
1
+ # createSwipeListener
2
+
3
+ Attaches a touch swipe gesture listener to `window`. Detects directional swipes based on a configurable distance threshold and fires a callback when the gesture completes.
4
+
5
+ ## Usage
6
+
7
+ ```ts
8
+ import { createSwipeListener } from '@inizioevoke/astro-core/lib/gestures';
9
+
10
+ const swipe = createSwipeListener('left-right', '1/3', ({ direction, distance, duration }) => {
11
+ console.log(direction, distance.x, duration); // 'left' -180 320
12
+ });
13
+
14
+ // Later, to disable without removing:
15
+ swipe.disable();
16
+
17
+ // Re-enable:
18
+ swipe.enable();
19
+
20
+ // Fully clean up:
21
+ swipe.dispose();
22
+ ```
23
+
24
+ ## API
25
+
26
+ ### `createSwipeListener(direction, threshold, handler)`
27
+
28
+ | Parameter | Type | Description |
29
+ |-----------|------|-------------|
30
+ | `direction` | `SwipeDirection` | Which swipe direction(s) to listen for. |
31
+ | `threshold` | `SwipeThreshold` | Minimum swipe distance required to trigger the handler. A fractional string is relative to the screen dimension of the primary axis. |
32
+ | `handler` | `OnSwipeHandler` | Called when a qualifying swipe completes. |
33
+
34
+ **Returns** a `SwipeController` — `{ enable, disable, dispose }` — for managing the listener lifecycle.
35
+
36
+ The listener is **enabled automatically** on creation. `dispose` is an alias for `disable`.
37
+
38
+ ### `SwipeDirection`
39
+
40
+ ```ts
41
+ type SwipeDirection = 'left' | 'right' | 'left-right' | 'up' | 'down' | 'up-down';
42
+ ```
43
+
44
+ | Value | Description |
45
+ |-------|-------------|
46
+ | `'left'` | Rightward-to-leftward horizontal swipe |
47
+ | `'right'` | Leftward-to-rightward horizontal swipe |
48
+ | `'left-right'` | Either horizontal direction |
49
+ | `'up'` | Bottom-to-top vertical swipe |
50
+ | `'down'` | Top-to-bottom vertical swipe |
51
+ | `'up-down'` | Either vertical direction |
52
+
53
+ ### `SwipeThreshold`
54
+
55
+ ```ts
56
+ type SwipeThreshold = '1/4' | '1/3' | '1/2' | number;
57
+ ```
58
+
59
+ Fractional strings are converted to a pixel distance at creation time based on the primary axis:
60
+
61
+ - Horizontal swipes (`left`, `right`, `left-right`) use `window.innerWidth`
62
+ - Vertical swipes (`up`, `down`, `up-down`) use `window.innerHeight`
63
+
64
+ Pass a `number` to specify an exact pixel distance.
65
+
66
+ ### `OnSwipeHandlerArgs`
67
+
68
+ ```ts
69
+ interface OnSwipeHandlerArgs {
70
+ direction: SwipeDirection;
71
+ threshold: number;
72
+ distance: { x: number; y: number };
73
+ duration: number;
74
+ }
75
+ ```
76
+
77
+ | Field | Description |
78
+ |-------|-------------|
79
+ | `direction` | The resolved direction of the swipe (`'left'`, `'right'`, `'up'`, or `'down'`). |
80
+ | `threshold` | The resolved pixel threshold used for this listener. |
81
+ | `distance.x` | Horizontal delta in pixels (positive = leftward, negative = rightward). |
82
+ | `distance.y` | Vertical delta in pixels (positive = upward, negative = downward). |
83
+ | `duration` | Time in milliseconds from `touchstart` to `touchend`. |
84
+
85
+ ### `SwipeController`
86
+
87
+ ```ts
88
+ interface SwipeController {
89
+ enable: () => void;
90
+ disable: () => void;
91
+ dispose: () => void;
92
+ }
93
+ ```
94
+
95
+ | Method | Description |
96
+ |--------|-------------|
97
+ | `enable()` | Attaches touch event listeners. No-op if already enabled. |
98
+ | `disable()` | Detaches touch event listeners. The controller can be re-enabled. |
99
+ | `dispose()` | Alias for `disable()`. |
100
+
101
+ ## Behavior
102
+
103
+ - Only single-finger swipes are recognized — multi-touch contacts cancel the active gesture.
104
+ - A swipe is only fired if the gesture is primarily along the primary axis. Off-axis drift must stay below the threshold or the swipe is ignored.
105
+ - The threshold pixel value is computed once at creation — it does not update on orientation change or resize.
106
+ - All touch listeners are added passively.
107
+
108
+ ## Examples
109
+
110
+ ### Navigate a carousel on horizontal swipe
111
+
112
+ ```ts
113
+ import { createSwipeListener } from '@inizioevoke/astro-core';
114
+
115
+ const swipe = createSwipeListener('left-right', '1/3', ({ direction }) => {
116
+ if (direction === 'left') carousel.next();
117
+ else carousel.prev();
118
+ });
119
+ ```
120
+
121
+ ### Dismiss a sheet on downward swipe
122
+
123
+ ```ts
124
+ import { createSwipeListener } from '@inizioevoke/astro-core';
125
+
126
+ const swipe = createSwipeListener('down', '1/4', () => {
127
+ sheet.close();
128
+ });
129
+ ```
130
+
131
+ ### Pause detection while a modal is open
132
+
133
+ ```ts
134
+ const swipe = createSwipeListener('left-right', '1/3', handler);
135
+
136
+ modal.addEventListener('open', () => swipe.disable());
137
+ modal.addEventListener('close', () => swipe.enable());
138
+ ```
139
+
140
+ ### Clean up on component teardown
141
+
142
+ ```ts
143
+ const swipe = createSwipeListener('up', '1/3', handler);
144
+
145
+ // Astro / vanilla teardown
146
+ document.addEventListener('astro:before-swap', swipe.dispose, { once: true });
147
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inizioevoke/astro-core",
3
- "version": "2.1.2",
3
+ "version": "2.2.1",
4
4
  "description": "",
5
5
  "license": "ISC",
6
6
  "author": "",
@@ -18,6 +18,7 @@
18
18
  "./lib/gestures": "./src/lib/gestures/index.ts"
19
19
  },
20
20
  "files": [
21
+ "docs",
21
22
  "src",
22
23
  "index.ts"
23
24
  ],
@@ -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} />