@at-flux/astroflare 1.0.2 → 1.0.4
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/LICENSE +1 -1
- package/README.md +98 -14
- package/dist/chunk-pbuEa-1d.js +13 -0
- package/dist/core.cjs +162 -138
- package/dist/core.d.cts +59 -2
- package/dist/core.d.ts +59 -2
- package/dist/core.js +147 -16
- package/dist/forms/index.cjs +7 -130
- package/dist/forms/index.d.cts +2 -52
- package/dist/forms/index.d.ts +2 -52
- package/dist/forms/index.js +99 -14
- package/dist/{chunk-DRYHJSYC.js → forms-8uIdWmMD.cjs} +81 -53
- package/dist/index-Bk-K9aDQ.d.ts +44 -0
- package/dist/index-CcH_yXr-.d.cts +44 -0
- package/dist/index.cjs +20 -141
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -17
- package/package.json +14 -8
- package/src/components/CollectionQuery.astro +107 -0
- package/src/components/ContactModalCta.astro +2 -0
- package/src/components/FilterPills.astro +164 -0
- package/src/components/IconButton.astro +5 -0
- package/src/components/InstagramProfileLink.astro +3 -0
- package/src/components/ListSummary.astro +63 -0
- package/src/components/MediaProtect.astro +41 -0
- package/src/components/Modal.astro +3 -0
- package/src/components/ModalTrigger.astro +2 -0
- package/src/components/Pager.astro +92 -0
- package/src/components/Section.astro +3 -0
- package/src/components/TagSummary.astro +71 -0
- package/src/components/ThemeToggle.astro +1 -0
- package/src/components/Tooltip.astro +70 -0
- package/src/components/collection-query-props.ts +45 -0
- package/src/runtime/collection-query.ts +156 -0
- package/src/runtime/media-protect.ts +70 -0
- package/src/styles/accessibility.css +1 -1
- package/src/styles/no-save.css +2 -2
- package/src/styles/prose.css +23 -11
- package/src/styles/scrollbar.css +10 -3
- package/dist/chunk-ALMPH3A2.js +0 -0
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,32 +1,34 @@
|
|
|
1
1
|
# @at-flux/astroflare
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@at-flux/astroflare)
|
|
4
|
+
[](https://github.com/at-flux/astroflare/actions/workflows/ci.yml)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
|
|
3
7
|
Reusable headless components, styles, and utilities for Astro + Tailwind v4 + Cloudflare projects.
|
|
4
8
|
|
|
5
9
|
For type-safe DOM helpers, use the separate package **`@at-flux/dom`**.
|
|
6
10
|
|
|
7
|
-
**There is no `@at-flux/astroflare/dom` subpath** (it is not published and must not be used). Add `@at-flux/dom` as its own dependency.
|
|
8
|
-
|
|
9
11
|
## Package entrypoints
|
|
10
12
|
|
|
11
|
-
| Subpath
|
|
12
|
-
|
|
13
|
-
| `@at-flux/astroflare`
|
|
14
|
-
| `@at-flux/astroflare/core`
|
|
15
|
-
| `@at-flux/astroflare/forms`
|
|
16
|
-
| `@at-flux/astroflare/components/*` | Astro components (source)
|
|
17
|
-
| `@at-flux/astroflare/styles/*`
|
|
13
|
+
| Subpath | Contents |
|
|
14
|
+
| ---------------------------------- | --------------------------------------------- |
|
|
15
|
+
| `@at-flux/astroflare` | **Core** — forms utilities (same as `./core`) |
|
|
16
|
+
| `@at-flux/astroflare/core` | Forms; also exposes the `forms` namespace |
|
|
17
|
+
| `@at-flux/astroflare/forms` | Resend email + form HTML helpers |
|
|
18
|
+
| `@at-flux/astroflare/components/*` | Astro components (source) |
|
|
19
|
+
| `@at-flux/astroflare/styles/*` | CSS (source) |
|
|
18
20
|
|
|
19
21
|
### Examples
|
|
20
22
|
|
|
21
23
|
```ts
|
|
22
24
|
// Flat imports from core
|
|
23
|
-
import { sendEmail } from
|
|
25
|
+
import { sendEmail } from "@at-flux/astroflare";
|
|
24
26
|
|
|
25
27
|
// Explicit subpaths
|
|
26
|
-
import { renderEmailTemplate } from
|
|
28
|
+
import { renderEmailTemplate } from "@at-flux/astroflare/forms";
|
|
27
29
|
|
|
28
30
|
// Namespaced (from core / root)
|
|
29
|
-
import { forms } from
|
|
31
|
+
import { forms } from "@at-flux/astroflare/core";
|
|
30
32
|
```
|
|
31
33
|
|
|
32
34
|
## Contents
|
|
@@ -39,7 +41,75 @@ import { forms } from '@at-flux/astroflare/core';
|
|
|
39
41
|
- `InstagramProfileLink.astro` — Small Instagram icon + `@handle` link with safe defaults
|
|
40
42
|
- `Section.astro` — Page section with optional `narrow` and `contentOnly` (inner width wrapper without outer padding)
|
|
41
43
|
- `ThemeToggle.astro` — Dark/light mode toggle using `<theme-toggle>` web component
|
|
44
|
+
- `IconButton.astro` — Accessible icon-only control that renders `<button>` or `<a>`
|
|
42
45
|
- `ClientRouterLoadingSpinner.astro` — Loading spinner for Astro view transitions
|
|
46
|
+
- `Tooltip.astro` — Lightweight hover/focus tooltip wrapper for compact metadata summaries
|
|
47
|
+
- `ListSummary.astro` — Generic inline list truncation with `+N` overflow and tooltip details
|
|
48
|
+
- `TagSummary.astro` — Generic deterministic tag pill list with `+N` tooltip overflow
|
|
49
|
+
- `MediaProtect.astro` — Style-agnostic media wrapper that applies no-save classes with delegated drag/context-menu protection
|
|
50
|
+
- `FilterPills.astro` — Tag-colored filter chips with an `all` option and active-state styling
|
|
51
|
+
- `Pager.astro` — Pagination UI primitive for both browser-only and link-driven query pagination
|
|
52
|
+
- `CollectionQuery.astro` — Unified collection filtering/pagination component (client mode by default; URL-driven server mode with `useServer`)
|
|
53
|
+
|
|
54
|
+
#### Component props reference
|
|
55
|
+
|
|
56
|
+
- `Modal.astro`: `id`, `class`, `backdropClass`, `panelClass`, `closeButtonClass`, `contentClass`
|
|
57
|
+
- `ModalTrigger.astro`: `modalId`, `class`
|
|
58
|
+
- `ContactModalCta.astro`: `modalId`, `label`, `variant`, `class`
|
|
59
|
+
- `InstagramProfileLink.astro`: `handle`, `href`, `class`, `aria-label`
|
|
60
|
+
- `Section.astro`: `id`, `class`, `narrow`, `contentOnly`
|
|
61
|
+
- `ThemeToggle.astro`: `class`
|
|
62
|
+
- `IconButton.astro`: `label`, `href`, `class`, `id`, passthrough attributes
|
|
63
|
+
- `Tooltip.astro`: `text`, `position`, `class`, `panelClass`
|
|
64
|
+
- `ListSummary.astro`: `items`, `visibleCount`, `separator`, `class`, `emptyLabel`, `overflowClass`, `tooltipPosition`, `itemCase`
|
|
65
|
+
- `TagSummary.astro`: `items`, `visibleCount`, `class`, `itemClass`, `overflowClass`, `itemCase`, `colorOverrides`
|
|
66
|
+
- `MediaProtect.astro`: `class`, `containerClass`, `drag`, `contextMenu`
|
|
67
|
+
- `FilterPills.astro`: `items`, `includeAll`, `allLabel`, `allHref`, `active`, `itemCase`, `colorOverrides`, `class`
|
|
68
|
+
- `Pager.astro`: `pageCount`, `activePage`, `items`, `class`
|
|
69
|
+
- `CollectionQuery.astro`: `useServer`, `pathname`, `query`, `totalPages`, `currentPage`, `filters`, `maxPageButtons`, `filtersClass`, `pagerClass`, `perPage`, `class`
|
|
70
|
+
- when `useServer` is `true`, `pathname`, `query`, `totalPages`, and `currentPage` are required
|
|
71
|
+
|
|
72
|
+
### Server Islands Pattern
|
|
73
|
+
|
|
74
|
+
`CollectionQuery.astro` supports two modes:
|
|
75
|
+
|
|
76
|
+
- client mode (default): static cards are filtered/paged in the browser
|
|
77
|
+
- server mode (`useServer`): renders querystring links for filters + pager
|
|
78
|
+
|
|
79
|
+
For server mode, mount with `server:defer` at the page callsite:
|
|
80
|
+
|
|
81
|
+
```astro
|
|
82
|
+
<CollectionQuery
|
|
83
|
+
useServer
|
|
84
|
+
pathname={Astro.url.pathname}
|
|
85
|
+
query={activeQuery}
|
|
86
|
+
totalPages={pageData.totalPages}
|
|
87
|
+
currentPage={currentPage}
|
|
88
|
+
filters={filters}
|
|
89
|
+
server:defer
|
|
90
|
+
/>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The package styleguide uses the Node adapter, so the server-island pattern can be exercised there with `server:defer`.
|
|
94
|
+
|
|
95
|
+
### Slot customization (headless overrides)
|
|
96
|
+
|
|
97
|
+
Use named slots to replace the default filter/pager rendering:
|
|
98
|
+
|
|
99
|
+
```astro
|
|
100
|
+
<CollectionQuery useServer {...props}>
|
|
101
|
+
<div slot="filters">
|
|
102
|
+
<!-- your custom filter UI -->
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<!-- default slot: your collection items -->
|
|
106
|
+
<div>...</div>
|
|
107
|
+
|
|
108
|
+
<div slot="pager">
|
|
109
|
+
<!-- your custom pager UI -->
|
|
110
|
+
</div>
|
|
111
|
+
</CollectionQuery>
|
|
112
|
+
```
|
|
43
113
|
|
|
44
114
|
### Styles (CSS)
|
|
45
115
|
|
|
@@ -48,6 +118,12 @@ import { forms } from '@at-flux/astroflare/core';
|
|
|
48
118
|
- `styles/accessibility.css` — Focus styles, reduced motion, selection styling
|
|
49
119
|
- `styles/scrollbar.css` — Branded scrollbar styling
|
|
50
120
|
|
|
121
|
+
### Utilities
|
|
122
|
+
|
|
123
|
+
- `getTagPalette(tag, options?)` — Deterministic, readable tag color assignment with optional explicit overrides
|
|
124
|
+
- `formatDisplayDate(date, config?)` — Consistent card/detail date formatting with locale override support
|
|
125
|
+
- `parseCollectionQuery(searchParams, options?)` + `paginateCollection(list, query)` + `buildCollectionHref(path, query, overrides?)` + `buildPageSequence(total, current, max?)` + `matchesCollectionFilters(values, filters)` — URL-driven filtering and pagination helpers (`filters` query param supports stringified JSON for multi-filter payloads)
|
|
126
|
+
|
|
51
127
|
## Usage
|
|
52
128
|
|
|
53
129
|
### Local checkout without changing package.json or lockfile
|
|
@@ -93,8 +169,8 @@ import ModalTrigger from '@at-flux/astroflare/components/ModalTrigger.astro';
|
|
|
93
169
|
### Styles
|
|
94
170
|
|
|
95
171
|
```css
|
|
96
|
-
@import
|
|
97
|
-
@import
|
|
172
|
+
@import "@at-flux/astroflare/styles/prose.css";
|
|
173
|
+
@import "@at-flux/astroflare/styles/no-save.css";
|
|
98
174
|
```
|
|
99
175
|
|
|
100
176
|
## Testing
|
|
@@ -111,3 +187,11 @@ Or in `packages/astroflare`:
|
|
|
111
187
|
```bash
|
|
112
188
|
pnpm test
|
|
113
189
|
```
|
|
190
|
+
|
|
191
|
+
## Styleguide (dev-only)
|
|
192
|
+
|
|
193
|
+
Use the package-local styleguide to preview all astroflare components in one place.
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
pnpm styleguide:dev
|
|
197
|
+
```
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __exportAll = (all, no_symbols) => {
|
|
4
|
+
let target = {};
|
|
5
|
+
for (var name in all) __defProp(target, name, {
|
|
6
|
+
get: all[name],
|
|
7
|
+
enumerable: true
|
|
8
|
+
});
|
|
9
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
10
|
+
return target;
|
|
11
|
+
};
|
|
12
|
+
//#endregion
|
|
13
|
+
export { __exportAll as t };
|
package/dist/core.cjs
CHANGED
|
@@ -1,142 +1,166 @@
|
|
|
1
|
-
"
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_forms = require("./forms-8uIdWmMD.cjs");
|
|
3
|
+
//#region src/tag-colors.ts
|
|
4
|
+
const DEFAULT_OVERRIDES = {
|
|
5
|
+
shoots: "#dc3545",
|
|
6
|
+
tech: "#8a95a5",
|
|
7
|
+
ai: "#6b7b8d",
|
|
8
|
+
sites: "#7dd3c0",
|
|
9
|
+
flow: "#b367e0"
|
|
9
10
|
};
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
11
|
+
const hashStringToHue = (input) => {
|
|
12
|
+
let hash = 0;
|
|
13
|
+
for (let i = 0; i < input.length; i += 1) hash = hash * 31 + input.charCodeAt(i) >>> 0;
|
|
14
|
+
return hash % 360;
|
|
17
15
|
};
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
16
|
+
const hexToRgb = (hex) => {
|
|
17
|
+
const value = hex.replace("#", "").trim();
|
|
18
|
+
if (!/^[0-9a-fA-F]{6}$/.test(value)) return null;
|
|
19
|
+
return [
|
|
20
|
+
Number.parseInt(value.slice(0, 2), 16),
|
|
21
|
+
Number.parseInt(value.slice(2, 4), 16),
|
|
22
|
+
Number.parseInt(value.slice(4, 6), 16)
|
|
23
|
+
];
|
|
24
|
+
};
|
|
25
|
+
const rgbToHsl = (r, g, b) => {
|
|
26
|
+
const red = r / 255;
|
|
27
|
+
const green = g / 255;
|
|
28
|
+
const blue = b / 255;
|
|
29
|
+
const max = Math.max(red, green, blue);
|
|
30
|
+
const min = Math.min(red, green, blue);
|
|
31
|
+
const delta = max - min;
|
|
32
|
+
const lightness = (max + min) / 2;
|
|
33
|
+
const saturation = delta === 0 ? 0 : delta / (1 - Math.abs(2 * lightness - 1));
|
|
34
|
+
let hue = 0;
|
|
35
|
+
if (delta !== 0) if (max === red) hue = (green - blue) / delta % 6;
|
|
36
|
+
else if (max === green) hue = (blue - red) / delta + 2;
|
|
37
|
+
else hue = (red - green) / delta + 4;
|
|
38
|
+
return [
|
|
39
|
+
Math.round((hue * 60 + 360) % 360),
|
|
40
|
+
Math.round(saturation * 100),
|
|
41
|
+
Math.round(lightness * 100)
|
|
42
|
+
];
|
|
43
|
+
};
|
|
44
|
+
const resolveHue = (tag, options) => {
|
|
45
|
+
const key = tag.trim().toLowerCase();
|
|
46
|
+
const color = options.overrides?.[key] ?? DEFAULT_OVERRIDES[key];
|
|
47
|
+
if (!color) return hashStringToHue(key);
|
|
48
|
+
const rgb = hexToRgb(color);
|
|
49
|
+
if (!rgb) return hashStringToHue(key);
|
|
50
|
+
return rgbToHsl(...rgb)[0];
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Build a deterministic HSL-based palette for a tag label.
|
|
54
|
+
* Uses known defaults and optional overrides before falling back to hashed hue.
|
|
55
|
+
*/
|
|
56
|
+
const getTagPalette = (tag, options = {}) => {
|
|
57
|
+
const hue = resolveHue(tag, options);
|
|
58
|
+
return {
|
|
59
|
+
bg: `hsla(${hue} 48% 58% / 0.12)`,
|
|
60
|
+
border: `hsla(${hue} 52% 58% / 0.34)`,
|
|
61
|
+
text: `hsl(${hue} 50% 66%)`
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
//#endregion
|
|
65
|
+
//#region src/date-format.ts
|
|
66
|
+
const DEFAULT_OPTIONS = {
|
|
67
|
+
year: "numeric",
|
|
68
|
+
month: "long",
|
|
69
|
+
day: "numeric"
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Format dates consistently for cards and article metadata.
|
|
73
|
+
*/
|
|
74
|
+
const formatDisplayDate = (date, config = {}) => {
|
|
75
|
+
const { locale = "en-GB", options = DEFAULT_OPTIONS } = config;
|
|
76
|
+
return new Intl.DateTimeFormat(locale, options).format(new Date(date));
|
|
77
|
+
};
|
|
78
|
+
//#endregion
|
|
79
|
+
//#region src/collection-query.ts
|
|
80
|
+
const toInt = (value, fallback) => {
|
|
81
|
+
const parsed = Number.parseInt(value ?? "", 10);
|
|
82
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
83
|
+
};
|
|
84
|
+
const parseCollectionQuery = (searchParams, options = {}) => {
|
|
85
|
+
const { defaultPage = 1, defaultSize = 12, maxSize = 100 } = options;
|
|
86
|
+
const page = Math.max(1, toInt(searchParams.get("page"), defaultPage));
|
|
87
|
+
const size = Math.min(maxSize, Math.max(1, toInt(searchParams.get("size"), defaultSize)));
|
|
88
|
+
const fallbackOffset = (page - 1) * size;
|
|
89
|
+
const offset = Math.max(0, toInt(searchParams.get("offset"), fallbackOffset));
|
|
90
|
+
const rawFilters = searchParams.get("filters");
|
|
91
|
+
let filters = {};
|
|
92
|
+
if (rawFilters) try {
|
|
93
|
+
const parsed = JSON.parse(rawFilters);
|
|
94
|
+
filters = Object.fromEntries(Object.entries(parsed).filter(([, value]) => typeof value === "string").map(([key, value]) => [key, String(value)]));
|
|
95
|
+
} catch {
|
|
96
|
+
filters = {};
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
page,
|
|
100
|
+
size,
|
|
101
|
+
offset,
|
|
102
|
+
filters
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
const paginateCollection = (list, query) => {
|
|
106
|
+
const totalItems = list.length;
|
|
107
|
+
const totalPages = Math.max(1, Math.ceil(totalItems / query.size));
|
|
108
|
+
const start = (Math.min(totalPages, Math.max(1, Math.floor(query.offset / query.size) + 1)) - 1) * query.size;
|
|
109
|
+
const end = start + query.size;
|
|
110
|
+
return {
|
|
111
|
+
items: list.slice(start, end),
|
|
112
|
+
totalItems,
|
|
113
|
+
totalPages,
|
|
114
|
+
start,
|
|
115
|
+
end: Math.min(end, totalItems)
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
const buildCollectionHref = (pathname, query, overrides = {}) => {
|
|
119
|
+
const next = {
|
|
120
|
+
...query,
|
|
121
|
+
...overrides
|
|
122
|
+
};
|
|
123
|
+
const normalizedOffset = (next.page - 1) * next.size;
|
|
124
|
+
const params = new URLSearchParams({
|
|
125
|
+
page: String(next.page),
|
|
126
|
+
size: String(next.size),
|
|
127
|
+
offset: String(next.offset ?? normalizedOffset)
|
|
128
|
+
});
|
|
129
|
+
if (Object.keys(next.filters ?? {}).length > 0) params.set("filters", JSON.stringify(next.filters));
|
|
130
|
+
return `${pathname}?${params.toString()}`;
|
|
131
|
+
};
|
|
132
|
+
const matchesCollectionFilters = (values, filters) => Object.entries(filters).every(([key, value]) => {
|
|
133
|
+
return (values[key] ?? []).includes(value);
|
|
40
134
|
});
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
function generateFormSectionHtml(section) {
|
|
65
|
-
const itemsHtml = section.items.map((item) => {
|
|
66
|
-
const value = item.value ?? "";
|
|
67
|
-
const isHtml = /<[^>]+>/.test(value);
|
|
68
|
-
return `
|
|
69
|
-
<div style="margin-bottom:16px;">
|
|
70
|
-
<p style="margin:0 0 4px;font-weight:600;color:#111827;">${item.key}</p>
|
|
71
|
-
${isHtml ? `<div style="margin:0;color:#111827;line-height:1.6;">${value || "Not specified"}</div>` : `<p style="margin:0;color:#111827;line-height:1.6;white-space:pre-wrap;">${value || "Not specified"}</p>`}
|
|
72
|
-
</div>
|
|
73
|
-
`;
|
|
74
|
-
}).join("");
|
|
75
|
-
return `
|
|
76
|
-
<section style="background:#f3f4f6;padding:16px;border-radius:8px;margin:16px 0;border:1px solid #e5e7eb;">
|
|
77
|
-
<h3 style="margin:0 0 12px;color:#111827;font-size:16px;font-weight:600;">${section.title}</h3>
|
|
78
|
-
<div style="color:#111827;">
|
|
79
|
-
${itemsHtml}
|
|
80
|
-
</div>
|
|
81
|
-
</section>
|
|
82
|
-
`;
|
|
83
|
-
}
|
|
84
|
-
function generateFormResultHtml(sections) {
|
|
85
|
-
return sections.map(generateFormSectionHtml).join("\n");
|
|
86
|
-
}
|
|
87
|
-
async function renderEmailTemplate(args) {
|
|
88
|
-
const { title, contentHtml, brandName, footerText } = args;
|
|
89
|
-
const footer = footerText ?? `This message was generated by the ${brandName ?? "site"} contact form on ${(/* @__PURE__ */ new Date()).toLocaleString(
|
|
90
|
-
"en-GB"
|
|
91
|
-
)}.`;
|
|
92
|
-
return `<!DOCTYPE html>
|
|
93
|
-
<html lang="en">
|
|
94
|
-
<head>
|
|
95
|
-
<meta charset="utf-8">
|
|
96
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
97
|
-
<title>${title}</title>
|
|
98
|
-
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
|
99
|
-
</head>
|
|
100
|
-
<body style="margin:0;padding:0;background:#f3f4f6;color:#111827;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol';">
|
|
101
|
-
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="background:#f3f4f6;">
|
|
102
|
-
<tr>
|
|
103
|
-
<td align="center" style="padding:24px;">
|
|
104
|
-
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="max-width:720px;background:#ffffff;border-radius:12px;overflow:hidden;border:1px solid #e5e7eb;">
|
|
105
|
-
<tr>
|
|
106
|
-
<td style="padding:24px 24px 16px 24px;border-bottom:1px solid #e5e7eb;">
|
|
107
|
-
<h1 style="margin:0 0 8px 0;font-size:20px;line-height:28px;color:#111827;letter-spacing:0.3px;">
|
|
108
|
-
${title}
|
|
109
|
-
</h1>
|
|
110
|
-
<p style="margin:0;font-size:13px;color:#6b7280;line-height:20px;">
|
|
111
|
-
Contact form submission
|
|
112
|
-
</p>
|
|
113
|
-
</td>
|
|
114
|
-
</tr>
|
|
115
|
-
<tr>
|
|
116
|
-
<td style="padding:24px;">
|
|
117
|
-
${contentHtml}
|
|
118
|
-
</td>
|
|
119
|
-
</tr>
|
|
120
|
-
<tr>
|
|
121
|
-
<td style="padding:16px 24px;background:#f9fafb;border-top:1px solid #e5e7eb;">
|
|
122
|
-
<p style="margin:0;color:#6b7280;font-size:12px;line-height:18px;">
|
|
123
|
-
${footer}
|
|
124
|
-
</p>
|
|
125
|
-
</td>
|
|
126
|
-
</tr>
|
|
127
|
-
</table>
|
|
128
|
-
</td>
|
|
129
|
-
</tr>
|
|
130
|
-
</table>
|
|
131
|
-
</body>
|
|
132
|
-
</html>`;
|
|
133
|
-
}
|
|
134
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
135
|
-
0 && (module.exports = {
|
|
136
|
-
composeEmailAddress,
|
|
137
|
-
forms,
|
|
138
|
-
generateFormResultHtml,
|
|
139
|
-
generateFormSectionHtml,
|
|
140
|
-
renderEmailTemplate,
|
|
141
|
-
sendEmail
|
|
135
|
+
const buildPageSequence = (totalPages, currentPage, maxButtons = 7) => {
|
|
136
|
+
if (totalPages <= maxButtons) return Array.from({ length: totalPages }, (_, index) => index + 1);
|
|
137
|
+
const innerSlots = Math.max(1, maxButtons - 2);
|
|
138
|
+
const left = Math.max(2, currentPage - Math.floor(innerSlots / 2));
|
|
139
|
+
const right = Math.min(totalPages - 1, left + innerSlots - 1);
|
|
140
|
+
const adjustedLeft = Math.max(2, right - innerSlots + 1);
|
|
141
|
+
const sequence = [1];
|
|
142
|
+
if (adjustedLeft > 2) sequence.push("…");
|
|
143
|
+
for (let page = adjustedLeft; page <= right; page += 1) sequence.push(page);
|
|
144
|
+
if (right < totalPages - 1) sequence.push("…");
|
|
145
|
+
sequence.push(totalPages);
|
|
146
|
+
return sequence;
|
|
147
|
+
};
|
|
148
|
+
//#endregion
|
|
149
|
+
exports.buildCollectionHref = buildCollectionHref;
|
|
150
|
+
exports.buildPageSequence = buildPageSequence;
|
|
151
|
+
exports.composeEmailAddress = require_forms.composeEmailAddress;
|
|
152
|
+
exports.formatDisplayDate = formatDisplayDate;
|
|
153
|
+
Object.defineProperty(exports, "forms", {
|
|
154
|
+
enumerable: true,
|
|
155
|
+
get: function() {
|
|
156
|
+
return require_forms.forms_exports;
|
|
157
|
+
}
|
|
142
158
|
});
|
|
159
|
+
exports.generateFormResultHtml = require_forms.generateFormResultHtml;
|
|
160
|
+
exports.generateFormSectionHtml = require_forms.generateFormSectionHtml;
|
|
161
|
+
exports.getTagPalette = getTagPalette;
|
|
162
|
+
exports.matchesCollectionFilters = matchesCollectionFilters;
|
|
163
|
+
exports.paginateCollection = paginateCollection;
|
|
164
|
+
exports.parseCollectionQuery = parseCollectionQuery;
|
|
165
|
+
exports.renderEmailTemplate = require_forms.renderEmailTemplate;
|
|
166
|
+
exports.sendEmail = require_forms.sendEmail;
|
package/dist/core.d.cts
CHANGED
|
@@ -1,2 +1,59 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { a as generateFormResultHtml, c as renderEmailTemplate, i as composeEmailAddress, l as sendEmail, n as FormSection, o as generateFormSectionHtml, r as ResendConfig, s as index_d_exports, t as EmailPayload } from "./index-CcH_yXr-.cjs";
|
|
2
|
+
|
|
3
|
+
//#region src/tag-colors.d.ts
|
|
4
|
+
interface TagPalette {
|
|
5
|
+
/** Soft background fill for the tag surface. */
|
|
6
|
+
bg: string;
|
|
7
|
+
/** Border color paired with `bg` for tag outlines. */
|
|
8
|
+
border: string;
|
|
9
|
+
/** Foreground text color with readable contrast on `bg`. */
|
|
10
|
+
text: string;
|
|
11
|
+
}
|
|
12
|
+
interface TagColorOptions {
|
|
13
|
+
/** Optional explicit tag-key to hex color map (expects lowercase keys). */
|
|
14
|
+
overrides?: Record<string, string>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Build a deterministic HSL-based palette for a tag label.
|
|
18
|
+
* Uses known defaults and optional overrides before falling back to hashed hue.
|
|
19
|
+
*/
|
|
20
|
+
declare const getTagPalette: (tag: string, options?: TagColorOptions) => TagPalette;
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/date-format.d.ts
|
|
23
|
+
interface DateFormatOptions {
|
|
24
|
+
/** BCP 47 locale tag used by `Intl.DateTimeFormat`. */
|
|
25
|
+
locale?: string;
|
|
26
|
+
/** Intl formatting options merged with defaults. */
|
|
27
|
+
options?: Intl.DateTimeFormatOptions;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Format dates consistently for cards and article metadata.
|
|
31
|
+
*/
|
|
32
|
+
declare const formatDisplayDate: (date: Date | string | number, config?: DateFormatOptions) => string;
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region src/collection-query.d.ts
|
|
35
|
+
interface CollectionQueryOptions {
|
|
36
|
+
defaultPage?: number;
|
|
37
|
+
defaultSize?: number;
|
|
38
|
+
maxSize?: number;
|
|
39
|
+
}
|
|
40
|
+
interface CollectionQueryState {
|
|
41
|
+
page: number;
|
|
42
|
+
size: number;
|
|
43
|
+
offset: number;
|
|
44
|
+
filters: Record<string, string>;
|
|
45
|
+
}
|
|
46
|
+
interface PaginationSlice<T> {
|
|
47
|
+
items: T[];
|
|
48
|
+
totalItems: number;
|
|
49
|
+
totalPages: number;
|
|
50
|
+
start: number;
|
|
51
|
+
end: number;
|
|
52
|
+
}
|
|
53
|
+
declare const parseCollectionQuery: (searchParams: URLSearchParams, options?: CollectionQueryOptions) => CollectionQueryState;
|
|
54
|
+
declare const paginateCollection: <T>(list: T[], query: CollectionQueryState) => PaginationSlice<T>;
|
|
55
|
+
declare const buildCollectionHref: (pathname: string, query: CollectionQueryState, overrides?: Partial<CollectionQueryState>) => string;
|
|
56
|
+
declare const matchesCollectionFilters: (values: Record<string, string[]>, filters: Record<string, string>) => boolean;
|
|
57
|
+
declare const buildPageSequence: (totalPages: number, currentPage: number, maxButtons?: number) => Array<number | "\u2026">;
|
|
58
|
+
//#endregion
|
|
59
|
+
export { CollectionQueryOptions, CollectionQueryState, DateFormatOptions, EmailPayload, FormSection, PaginationSlice, ResendConfig, TagColorOptions, TagPalette, buildCollectionHref, buildPageSequence, composeEmailAddress, formatDisplayDate, index_d_exports as forms, generateFormResultHtml, generateFormSectionHtml, getTagPalette, matchesCollectionFilters, paginateCollection, parseCollectionQuery, renderEmailTemplate, sendEmail };
|
package/dist/core.d.ts
CHANGED
|
@@ -1,2 +1,59 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { a as generateFormResultHtml, c as renderEmailTemplate, i as composeEmailAddress, l as sendEmail, n as FormSection, o as generateFormSectionHtml, r as ResendConfig, s as index_d_exports, t as EmailPayload } from "./index-Bk-K9aDQ.js";
|
|
2
|
+
|
|
3
|
+
//#region src/tag-colors.d.ts
|
|
4
|
+
interface TagPalette {
|
|
5
|
+
/** Soft background fill for the tag surface. */
|
|
6
|
+
bg: string;
|
|
7
|
+
/** Border color paired with `bg` for tag outlines. */
|
|
8
|
+
border: string;
|
|
9
|
+
/** Foreground text color with readable contrast on `bg`. */
|
|
10
|
+
text: string;
|
|
11
|
+
}
|
|
12
|
+
interface TagColorOptions {
|
|
13
|
+
/** Optional explicit tag-key to hex color map (expects lowercase keys). */
|
|
14
|
+
overrides?: Record<string, string>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Build a deterministic HSL-based palette for a tag label.
|
|
18
|
+
* Uses known defaults and optional overrides before falling back to hashed hue.
|
|
19
|
+
*/
|
|
20
|
+
declare const getTagPalette: (tag: string, options?: TagColorOptions) => TagPalette;
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/date-format.d.ts
|
|
23
|
+
interface DateFormatOptions {
|
|
24
|
+
/** BCP 47 locale tag used by `Intl.DateTimeFormat`. */
|
|
25
|
+
locale?: string;
|
|
26
|
+
/** Intl formatting options merged with defaults. */
|
|
27
|
+
options?: Intl.DateTimeFormatOptions;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Format dates consistently for cards and article metadata.
|
|
31
|
+
*/
|
|
32
|
+
declare const formatDisplayDate: (date: Date | string | number, config?: DateFormatOptions) => string;
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region src/collection-query.d.ts
|
|
35
|
+
interface CollectionQueryOptions {
|
|
36
|
+
defaultPage?: number;
|
|
37
|
+
defaultSize?: number;
|
|
38
|
+
maxSize?: number;
|
|
39
|
+
}
|
|
40
|
+
interface CollectionQueryState {
|
|
41
|
+
page: number;
|
|
42
|
+
size: number;
|
|
43
|
+
offset: number;
|
|
44
|
+
filters: Record<string, string>;
|
|
45
|
+
}
|
|
46
|
+
interface PaginationSlice<T> {
|
|
47
|
+
items: T[];
|
|
48
|
+
totalItems: number;
|
|
49
|
+
totalPages: number;
|
|
50
|
+
start: number;
|
|
51
|
+
end: number;
|
|
52
|
+
}
|
|
53
|
+
declare const parseCollectionQuery: (searchParams: URLSearchParams, options?: CollectionQueryOptions) => CollectionQueryState;
|
|
54
|
+
declare const paginateCollection: <T>(list: T[], query: CollectionQueryState) => PaginationSlice<T>;
|
|
55
|
+
declare const buildCollectionHref: (pathname: string, query: CollectionQueryState, overrides?: Partial<CollectionQueryState>) => string;
|
|
56
|
+
declare const matchesCollectionFilters: (values: Record<string, string[]>, filters: Record<string, string>) => boolean;
|
|
57
|
+
declare const buildPageSequence: (totalPages: number, currentPage: number, maxButtons?: number) => Array<number | "\u2026">;
|
|
58
|
+
//#endregion
|
|
59
|
+
export { CollectionQueryOptions, CollectionQueryState, DateFormatOptions, EmailPayload, FormSection, PaginationSlice, ResendConfig, TagColorOptions, TagPalette, buildCollectionHref, buildPageSequence, composeEmailAddress, formatDisplayDate, index_d_exports as forms, generateFormResultHtml, generateFormSectionHtml, getTagPalette, matchesCollectionFilters, paginateCollection, parseCollectionQuery, renderEmailTemplate, sendEmail };
|