@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
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
export interface CollectionQueryRuntimeOptions {
|
|
2
|
+
cardSelector: string;
|
|
3
|
+
filterSelector: string;
|
|
4
|
+
paginationSelector: string;
|
|
5
|
+
perPage: number;
|
|
6
|
+
activeClass: string;
|
|
7
|
+
tagSeparator: string;
|
|
8
|
+
pageButtonClass: string;
|
|
9
|
+
ellipsisClass: string;
|
|
10
|
+
maxPageButtons: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const DEFAULTS: CollectionQueryRuntimeOptions = {
|
|
14
|
+
cardSelector: "[data-card]",
|
|
15
|
+
filterSelector: "[data-filter]",
|
|
16
|
+
paginationSelector: "[data-pagination]",
|
|
17
|
+
perPage: 9,
|
|
18
|
+
activeClass: "is-active",
|
|
19
|
+
tagSeparator: "|",
|
|
20
|
+
pageButtonClass: "af-pager-pill",
|
|
21
|
+
ellipsisClass: "af-pager-ellipsis",
|
|
22
|
+
maxPageButtons: 7,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const getConfig = (root: HTMLElement): CollectionQueryRuntimeOptions => {
|
|
26
|
+
const {
|
|
27
|
+
cardSelector,
|
|
28
|
+
filterSelector,
|
|
29
|
+
paginationSelector,
|
|
30
|
+
perPage,
|
|
31
|
+
activeClass,
|
|
32
|
+
tagSeparator,
|
|
33
|
+
pageButtonClass,
|
|
34
|
+
ellipsisClass,
|
|
35
|
+
maxPageButtons,
|
|
36
|
+
} = root.dataset;
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
cardSelector: cardSelector ?? DEFAULTS.cardSelector,
|
|
40
|
+
filterSelector: filterSelector ?? DEFAULTS.filterSelector,
|
|
41
|
+
paginationSelector: paginationSelector ?? DEFAULTS.paginationSelector,
|
|
42
|
+
perPage: Number(perPage ?? DEFAULTS.perPage),
|
|
43
|
+
activeClass: activeClass ?? DEFAULTS.activeClass,
|
|
44
|
+
tagSeparator: tagSeparator ?? DEFAULTS.tagSeparator,
|
|
45
|
+
pageButtonClass: pageButtonClass ?? DEFAULTS.pageButtonClass,
|
|
46
|
+
ellipsisClass: ellipsisClass ?? DEFAULTS.ellipsisClass,
|
|
47
|
+
maxPageButtons: Number(maxPageButtons ?? DEFAULTS.maxPageButtons),
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const getClientPageSequence = (
|
|
52
|
+
totalPages: number,
|
|
53
|
+
currentPage: number,
|
|
54
|
+
maxButtons: number,
|
|
55
|
+
): Array<number | "…"> => {
|
|
56
|
+
if (totalPages <= maxButtons) {
|
|
57
|
+
return Array.from({ length: totalPages }, (_, index) => index + 1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const innerSlots = Math.max(1, maxButtons - 2);
|
|
61
|
+
const left = Math.max(2, currentPage - Math.floor(innerSlots / 2));
|
|
62
|
+
const right = Math.min(totalPages - 1, left + innerSlots - 1);
|
|
63
|
+
const adjustedLeft = Math.max(2, right - innerSlots + 1);
|
|
64
|
+
|
|
65
|
+
const sequence: Array<number | "…"> = [1];
|
|
66
|
+
if (adjustedLeft > 2) sequence.push("…");
|
|
67
|
+
for (let page = adjustedLeft; page <= right; page += 1) sequence.push(page);
|
|
68
|
+
if (right < totalPages - 1) sequence.push("…");
|
|
69
|
+
sequence.push(totalPages);
|
|
70
|
+
return sequence;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export const initCollectionQueryElement = (root: HTMLElement): void => {
|
|
74
|
+
if (root.dataset.afInit === "true") return;
|
|
75
|
+
root.dataset.afInit = "true";
|
|
76
|
+
|
|
77
|
+
const config = getConfig(root);
|
|
78
|
+
const cards = Array.from(root.querySelectorAll<HTMLElement>(config.cardSelector));
|
|
79
|
+
const filters = Array.from(root.querySelectorAll<HTMLButtonElement>(config.filterSelector));
|
|
80
|
+
const pagination = root.querySelector<HTMLElement>(config.paginationSelector);
|
|
81
|
+
|
|
82
|
+
let page = 1;
|
|
83
|
+
let activeFilter = filters.find((button) => button.classList.contains(config.activeClass))?.dataset.filter ?? "all";
|
|
84
|
+
|
|
85
|
+
const filterCards = (): HTMLElement[] =>
|
|
86
|
+
cards.filter((card) => {
|
|
87
|
+
if (activeFilter === "all") return true;
|
|
88
|
+
const tags = (card.dataset.tags ?? "").split(config.tagSeparator);
|
|
89
|
+
return tags.includes(activeFilter);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const renderPagination = (totalPages: number) => {
|
|
93
|
+
if (!pagination) return;
|
|
94
|
+
pagination.innerHTML = "";
|
|
95
|
+
if (totalPages <= 1) return;
|
|
96
|
+
|
|
97
|
+
const pages = getClientPageSequence(totalPages, page, config.maxPageButtons);
|
|
98
|
+
for (const entry of pages) {
|
|
99
|
+
if (entry === "…") {
|
|
100
|
+
const ellipsis = document.createElement("span");
|
|
101
|
+
ellipsis.className = config.ellipsisClass;
|
|
102
|
+
ellipsis.textContent = "…";
|
|
103
|
+
ellipsis.setAttribute("aria-hidden", "true");
|
|
104
|
+
pagination.appendChild(ellipsis);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const button = document.createElement("button");
|
|
109
|
+
button.type = "button";
|
|
110
|
+
button.dataset.page = String(entry);
|
|
111
|
+
button.textContent = String(entry);
|
|
112
|
+
button.className = [config.pageButtonClass, entry === page ? config.activeClass : ""]
|
|
113
|
+
.filter(Boolean)
|
|
114
|
+
.join(" ");
|
|
115
|
+
button.addEventListener("click", () => {
|
|
116
|
+
page = entry;
|
|
117
|
+
render();
|
|
118
|
+
});
|
|
119
|
+
pagination.appendChild(button);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const render = () => {
|
|
124
|
+
const filtered = filterCards();
|
|
125
|
+
const totalPages = Math.max(1, Math.ceil(filtered.length / config.perPage));
|
|
126
|
+
page = Math.min(page, totalPages);
|
|
127
|
+
|
|
128
|
+
const start = (page - 1) * config.perPage;
|
|
129
|
+
const end = start + config.perPage;
|
|
130
|
+
|
|
131
|
+
cards.forEach((card) => {
|
|
132
|
+
card.style.display = "none";
|
|
133
|
+
});
|
|
134
|
+
filtered.slice(start, end).forEach((card) => {
|
|
135
|
+
card.style.display = "";
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
renderPagination(totalPages);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
filters.forEach((button) => {
|
|
142
|
+
button.addEventListener("click", () => {
|
|
143
|
+
activeFilter = button.dataset.filter ?? "all";
|
|
144
|
+
page = 1;
|
|
145
|
+
filters.forEach((item) => item.classList.toggle(config.activeClass, item === button));
|
|
146
|
+
render();
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
render();
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
export const initCollectionQueryRoots = (scope: ParentNode = document): void => {
|
|
154
|
+
const roots = Array.from(scope.querySelectorAll<HTMLElement>("collection-query[data-af-query]"));
|
|
155
|
+
roots.forEach((root) => initCollectionQueryElement(root));
|
|
156
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
interface MediaProtectState {
|
|
2
|
+
drag: boolean;
|
|
3
|
+
contextMenu: boolean;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
declare global {
|
|
7
|
+
interface Window {
|
|
8
|
+
__astroflareMediaProtectHandlerState__?: MediaProtectState;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const STATE_KEY = "__astroflareMediaProtectHandlerState__";
|
|
13
|
+
|
|
14
|
+
const shouldPrevent = (target: EventTarget | null, mode: "drag" | "contextMenu"): boolean => {
|
|
15
|
+
if (!(target instanceof Element)) return false;
|
|
16
|
+
const root = target.closest<HTMLElement>("[data-media-protect-root]");
|
|
17
|
+
if (!root) return false;
|
|
18
|
+
return mode === "drag"
|
|
19
|
+
? root.dataset.mediaProtectDrag === "true"
|
|
20
|
+
: root.dataset.mediaProtectContextMenu === "true";
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const ensureHandlers = (options: { drag: boolean; contextMenu: boolean }) => {
|
|
24
|
+
const runtime = window;
|
|
25
|
+
const state = runtime[STATE_KEY] ?? { drag: false, contextMenu: false };
|
|
26
|
+
runtime[STATE_KEY] = state;
|
|
27
|
+
|
|
28
|
+
if (options.contextMenu && !state.contextMenu) {
|
|
29
|
+
document.addEventListener(
|
|
30
|
+
"contextmenu",
|
|
31
|
+
(event) => {
|
|
32
|
+
if (shouldPrevent(event.target, "contextMenu")) event.preventDefault();
|
|
33
|
+
},
|
|
34
|
+
{ capture: true },
|
|
35
|
+
);
|
|
36
|
+
state.contextMenu = true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (options.drag && !state.drag) {
|
|
40
|
+
document.addEventListener(
|
|
41
|
+
"dragstart",
|
|
42
|
+
(event) => {
|
|
43
|
+
if (shouldPrevent(event.target, "drag")) event.preventDefault();
|
|
44
|
+
},
|
|
45
|
+
{ capture: true },
|
|
46
|
+
);
|
|
47
|
+
state.drag = true;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const initMediaProtectRoots = (scope: ParentNode = document): void => {
|
|
52
|
+
const roots = Array.from(
|
|
53
|
+
scope.querySelectorAll<HTMLElement>(
|
|
54
|
+
"[data-media-protect-root]:not([data-media-protect-init='true'])",
|
|
55
|
+
),
|
|
56
|
+
);
|
|
57
|
+
if (roots.length === 0) return;
|
|
58
|
+
|
|
59
|
+
let dragNeeded = false;
|
|
60
|
+
let contextMenuNeeded = false;
|
|
61
|
+
|
|
62
|
+
roots.forEach((root) => {
|
|
63
|
+
root.dataset.mediaProtectInit = "true";
|
|
64
|
+
dragNeeded = dragNeeded || root.dataset.mediaProtectDrag === "true";
|
|
65
|
+
contextMenuNeeded =
|
|
66
|
+
contextMenuNeeded || root.dataset.mediaProtectContextMenu === "true";
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
ensureHandlers({ drag: dragNeeded, contextMenu: contextMenuNeeded });
|
|
70
|
+
};
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
background: color-mix(in oklab, var(--color-brand, #7c5cff) 25%, transparent);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
:where([data-theme=
|
|
16
|
+
:where([data-theme="dark"]) ::selection {
|
|
17
17
|
background: color-mix(in oklab, var(--color-brand, #7c5cff) 35%, transparent);
|
|
18
18
|
}
|
|
19
19
|
|
package/src/styles/no-save.css
CHANGED
|
@@ -20,14 +20,14 @@
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
.no-save-container::before {
|
|
23
|
-
content:
|
|
23
|
+
content: "";
|
|
24
24
|
position: absolute;
|
|
25
25
|
inset: 0;
|
|
26
26
|
z-index: 1;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
a:has(.no-save)::after {
|
|
30
|
-
content:
|
|
30
|
+
content: "";
|
|
31
31
|
position: absolute;
|
|
32
32
|
inset: 0;
|
|
33
33
|
z-index: 2;
|
package/src/styles/prose.css
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
* Uses CSS custom property --color-brand for theming.
|
|
4
4
|
* Apply .prose to the container element.
|
|
5
5
|
*/
|
|
6
|
-
.prose {
|
|
6
|
+
.prose {
|
|
7
|
+
color: inherit;
|
|
8
|
+
}
|
|
7
9
|
|
|
8
10
|
.prose h1,
|
|
9
11
|
.prose h2,
|
|
@@ -20,8 +22,12 @@
|
|
|
20
22
|
line-height: 1.6;
|
|
21
23
|
}
|
|
22
24
|
|
|
23
|
-
.prose a {
|
|
24
|
-
|
|
25
|
+
.prose a {
|
|
26
|
+
color: var(--color-brand, #7c5cff);
|
|
27
|
+
}
|
|
28
|
+
.prose a:hover {
|
|
29
|
+
opacity: 0.8;
|
|
30
|
+
}
|
|
25
31
|
|
|
26
32
|
.prose code {
|
|
27
33
|
background: rgba(0, 0, 0, 0.05);
|
|
@@ -30,7 +36,7 @@
|
|
|
30
36
|
font-size: 0.9em;
|
|
31
37
|
}
|
|
32
38
|
|
|
33
|
-
:where([data-theme=
|
|
39
|
+
:where([data-theme="dark"]) .prose code {
|
|
34
40
|
background: rgba(255, 255, 255, 0.1);
|
|
35
41
|
}
|
|
36
42
|
|
|
@@ -52,7 +58,9 @@
|
|
|
52
58
|
padding-left: 2em;
|
|
53
59
|
}
|
|
54
60
|
|
|
55
|
-
.prose li {
|
|
61
|
+
.prose li {
|
|
62
|
+
margin: 0.5em 0;
|
|
63
|
+
}
|
|
56
64
|
|
|
57
65
|
.prose blockquote {
|
|
58
66
|
margin: 2em 0;
|
|
@@ -63,12 +71,16 @@
|
|
|
63
71
|
font-style: italic;
|
|
64
72
|
}
|
|
65
73
|
|
|
66
|
-
:where([data-theme=
|
|
74
|
+
:where([data-theme="dark"]) .prose blockquote {
|
|
67
75
|
background: color-mix(in oklab, var(--color-brand, #7c5cff) 12%, transparent);
|
|
68
76
|
}
|
|
69
77
|
|
|
70
|
-
.prose blockquote p {
|
|
71
|
-
|
|
78
|
+
.prose blockquote p {
|
|
79
|
+
margin-bottom: 0.5rem;
|
|
80
|
+
}
|
|
81
|
+
.prose blockquote p:last-child {
|
|
82
|
+
margin-bottom: 0;
|
|
83
|
+
}
|
|
72
84
|
|
|
73
85
|
.prose img {
|
|
74
86
|
max-width: 100%;
|
|
@@ -90,8 +102,8 @@
|
|
|
90
102
|
text-align: left;
|
|
91
103
|
}
|
|
92
104
|
|
|
93
|
-
:where([data-theme=
|
|
94
|
-
:where([data-theme=
|
|
105
|
+
:where([data-theme="dark"]) .prose th,
|
|
106
|
+
:where([data-theme="dark"]) .prose td {
|
|
95
107
|
border-color: rgba(255, 255, 255, 0.1);
|
|
96
108
|
}
|
|
97
109
|
|
|
@@ -100,6 +112,6 @@
|
|
|
100
112
|
background: rgba(0, 0, 0, 0.05);
|
|
101
113
|
}
|
|
102
114
|
|
|
103
|
-
:where([data-theme=
|
|
115
|
+
:where([data-theme="dark"]) .prose th {
|
|
104
116
|
background: rgba(255, 255, 255, 0.05);
|
|
105
117
|
}
|
package/src/styles/scrollbar.css
CHANGED
|
@@ -2,10 +2,17 @@
|
|
|
2
2
|
* Branded scrollbar styling.
|
|
3
3
|
* Uses --color-brand and --color-brand-pastel for theming.
|
|
4
4
|
*/
|
|
5
|
-
::-webkit-scrollbar {
|
|
6
|
-
|
|
5
|
+
::-webkit-scrollbar {
|
|
6
|
+
width: 8px;
|
|
7
|
+
}
|
|
8
|
+
::-webkit-scrollbar-track {
|
|
9
|
+
background: transparent;
|
|
10
|
+
}
|
|
7
11
|
::-webkit-scrollbar-thumb {
|
|
8
|
-
background: var(
|
|
12
|
+
background: var(
|
|
13
|
+
--color-brand-pastel,
|
|
14
|
+
color-mix(in oklab, var(--color-brand, #7c5cff) 60%, white)
|
|
15
|
+
);
|
|
9
16
|
border-radius: 4px;
|
|
10
17
|
}
|
|
11
18
|
::-webkit-scrollbar-thumb:hover {
|
package/dist/chunk-ALMPH3A2.js
DELETED
|
File without changes
|