@aparte/plugin-cards 0.0.0-stage → 0.29.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.
- package/LICENSE +21 -0
- package/README.md +151 -2
- package/dist/carousel-DwgHvriV.js +63 -0
- package/dist/carousel-DwgHvriV.js.map +1 -0
- package/dist/controls-BYUm4aRV.js +62 -0
- package/dist/controls-BYUm4aRV.js.map +1 -0
- package/dist/format-BFq79ydr.js +728 -0
- package/dist/format-BFq79ydr.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +120 -0
- package/dist/index.js.map +1 -0
- package/dist/items/icons.d.ts +12 -0
- package/dist/items/icons.d.ts.map +1 -0
- package/dist/items/index.d.ts +100 -0
- package/dist/items/index.d.ts.map +1 -0
- package/dist/items.js +698 -0
- package/dist/items.js.map +1 -0
- package/dist/kit/carousel.d.ts +17 -0
- package/dist/kit/carousel.d.ts.map +1 -0
- package/dist/kit/controls.d.ts +51 -0
- package/dist/kit/controls.d.ts.map +1 -0
- package/dist/kit/define.d.ts +201 -0
- package/dist/kit/define.d.ts.map +1 -0
- package/dist/kit/dom.d.ts +40 -0
- package/dist/kit/dom.d.ts.map +1 -0
- package/dist/kit/fence.d.ts +39 -0
- package/dist/kit/fence.d.ts.map +1 -0
- package/dist/kit/format.d.ts +49 -0
- package/dist/kit/format.d.ts.map +1 -0
- package/dist/kit/rating.d.ts +19 -0
- package/dist/kit/rating.d.ts.map +1 -0
- package/dist/kit/sparkline.d.ts +19 -0
- package/dist/kit/sparkline.d.ts.map +1 -0
- package/dist/kit/strings.d.ts +139 -0
- package/dist/kit/strings.d.ts.map +1 -0
- package/dist/link/icons.d.ts +16 -0
- package/dist/link/icons.d.ts.map +1 -0
- package/dist/link/index.d.ts +98 -0
- package/dist/link/index.d.ts.map +1 -0
- package/dist/link.js +387 -0
- package/dist/link.js.map +1 -0
- package/dist/media/icons.d.ts +28 -0
- package/dist/media/icons.d.ts.map +1 -0
- package/dist/media/index.d.ts +107 -0
- package/dist/media/index.d.ts.map +1 -0
- package/dist/media.js +610 -0
- package/dist/media.js.map +1 -0
- package/dist/order/icons.d.ts +49 -0
- package/dist/order/icons.d.ts.map +1 -0
- package/dist/order/index.d.ts +204 -0
- package/dist/order/index.d.ts.map +1 -0
- package/dist/order.js +1843 -0
- package/dist/order.js.map +1 -0
- package/dist/place/icons.d.ts +18 -0
- package/dist/place/icons.d.ts.map +1 -0
- package/dist/place/index.d.ts +207 -0
- package/dist/place/index.d.ts.map +1 -0
- package/dist/place.js +1456 -0
- package/dist/place.js.map +1 -0
- package/dist/plan/icons.d.ts +25 -0
- package/dist/plan/icons.d.ts.map +1 -0
- package/dist/plan/index.d.ts +153 -0
- package/dist/plan/index.d.ts.map +1 -0
- package/dist/plan.js +1191 -0
- package/dist/plan.js.map +1 -0
- package/dist/product/icons.d.ts +16 -0
- package/dist/product/icons.d.ts.map +1 -0
- package/dist/product/index.d.ts +133 -0
- package/dist/product/index.d.ts.map +1 -0
- package/dist/product.js +1245 -0
- package/dist/product.js.map +1 -0
- package/dist/rating-CHVajQ2b.js +35 -0
- package/dist/rating-CHVajQ2b.js.map +1 -0
- package/dist/score/icons.d.ts +35 -0
- package/dist/score/icons.d.ts.map +1 -0
- package/dist/score/index.d.ts +185 -0
- package/dist/score/index.d.ts.map +1 -0
- package/dist/score.js +1843 -0
- package/dist/score.js.map +1 -0
- package/dist/sparkline-C-cA9obz.js +56 -0
- package/dist/sparkline-C-cA9obz.js.map +1 -0
- package/dist/stats/icons.d.ts +19 -0
- package/dist/stats/icons.d.ts.map +1 -0
- package/dist/stats/index.d.ts +137 -0
- package/dist/stats/index.d.ts.map +1 -0
- package/dist/stats.js +1085 -0
- package/dist/stats.js.map +1 -0
- package/dist/stock/chart.d.ts +135 -0
- package/dist/stock/chart.d.ts.map +1 -0
- package/dist/stock/icons.d.ts +15 -0
- package/dist/stock/icons.d.ts.map +1 -0
- package/dist/stock/index.d.ts +195 -0
- package/dist/stock/index.d.ts.map +1 -0
- package/dist/stock.js +1013 -0
- package/dist/stock.js.map +1 -0
- package/dist/weather/conditions.d.ts +25 -0
- package/dist/weather/conditions.d.ts.map +1 -0
- package/dist/weather/icons.d.ts +12 -0
- package/dist/weather/icons.d.ts.map +1 -0
- package/dist/weather/index.d.ts +146 -0
- package/dist/weather/index.d.ts.map +1 -0
- package/dist/weather.js +1162 -0
- package/dist/weather.js.map +1 -0
- package/package.json +137 -3
|
@@ -0,0 +1,728 @@
|
|
|
1
|
+
import { aparteGlobalConfig, overrideSegmentRenderer, cssEscape } from "@aparte/core";
|
|
2
|
+
function el(tag, props = {}, ...children) {
|
|
3
|
+
const node = document.createElement(tag);
|
|
4
|
+
if (props.class) node.className = props.class;
|
|
5
|
+
for (const [name, value] of Object.entries(props.attrs ?? {})) {
|
|
6
|
+
if (value === null || value === void 0 || value === false) continue;
|
|
7
|
+
node.setAttribute(name, value === true ? "" : String(value));
|
|
8
|
+
}
|
|
9
|
+
if (props.text !== void 0) node.textContent = props.text;
|
|
10
|
+
for (const child of children) {
|
|
11
|
+
if (child === null || child === void 0 || child === false) continue;
|
|
12
|
+
node.append(child);
|
|
13
|
+
}
|
|
14
|
+
return node;
|
|
15
|
+
}
|
|
16
|
+
function glyph(markup, className = "aparte-rich-card__glyph") {
|
|
17
|
+
const holder = document.createElement("span");
|
|
18
|
+
holder.className = className;
|
|
19
|
+
holder.setAttribute("aria-hidden", "true");
|
|
20
|
+
holder.innerHTML = markup;
|
|
21
|
+
return holder;
|
|
22
|
+
}
|
|
23
|
+
function safeUrl(raw) {
|
|
24
|
+
if (typeof raw !== "string" || !raw.trim()) return null;
|
|
25
|
+
try {
|
|
26
|
+
const url = new URL(raw.trim(), typeof location === "undefined" ? "https://localhost/" : location.href);
|
|
27
|
+
return ["http:", "https:", "mailto:", "tel:"].includes(url.protocol) ? url.href : null;
|
|
28
|
+
} catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function webUrl(raw) {
|
|
33
|
+
if (typeof raw !== "string" || !/^\s*(https?:)?\/\//i.test(raw)) return null;
|
|
34
|
+
const url = safeUrl(raw);
|
|
35
|
+
return url && /^https?:/i.test(url) ? url : null;
|
|
36
|
+
}
|
|
37
|
+
function num(value) {
|
|
38
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : void 0;
|
|
39
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
40
|
+
const n = Number(value);
|
|
41
|
+
return Number.isFinite(n) ? n : void 0;
|
|
42
|
+
}
|
|
43
|
+
return void 0;
|
|
44
|
+
}
|
|
45
|
+
function str(value) {
|
|
46
|
+
if (typeof value === "string") return value.trim() || void 0;
|
|
47
|
+
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
48
|
+
return void 0;
|
|
49
|
+
}
|
|
50
|
+
function languageOf(config) {
|
|
51
|
+
const tag = config.getLocale().tag;
|
|
52
|
+
return typeof tag === "string" && tag ? tag.split("-")[0].toLowerCase() : "en";
|
|
53
|
+
}
|
|
54
|
+
function intlTag(config) {
|
|
55
|
+
const tag = config.getLocale().tag;
|
|
56
|
+
if (typeof tag !== "string" || !tag) return void 0;
|
|
57
|
+
for (const candidate of [tag, tag.replace(/_/g, "-")]) {
|
|
58
|
+
try {
|
|
59
|
+
return Intl.getCanonicalLocales(candidate)[0];
|
|
60
|
+
} catch {
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return void 0;
|
|
64
|
+
}
|
|
65
|
+
function word(config, key, table, vars = {}) {
|
|
66
|
+
const fromApp = config.getLocale()[key];
|
|
67
|
+
const lang = languageOf(config);
|
|
68
|
+
const text = typeof fromApp === "string" && fromApp || table[lang] || table.en;
|
|
69
|
+
return text.replace(/\{(\w+)\}/g, (all, name) => vars[name] ?? all);
|
|
70
|
+
}
|
|
71
|
+
const KIT_WORDS = {
|
|
72
|
+
cardsUnavailable: {
|
|
73
|
+
en: "Not available",
|
|
74
|
+
fr: "Indisponible",
|
|
75
|
+
de: "Nicht verfügbar",
|
|
76
|
+
es: "No disponible",
|
|
77
|
+
it: "Non disponibile",
|
|
78
|
+
ja: "利用できません",
|
|
79
|
+
ko: "사용할 수 없음",
|
|
80
|
+
pt: "Indisponível",
|
|
81
|
+
ru: "Недоступно",
|
|
82
|
+
zh: "不可用"
|
|
83
|
+
},
|
|
84
|
+
cardsWaiting: {
|
|
85
|
+
en: "Waiting for your approval",
|
|
86
|
+
fr: "En attente de votre accord",
|
|
87
|
+
de: "Wartet auf Ihre Zustimmung",
|
|
88
|
+
es: "Esperando tu aprobación",
|
|
89
|
+
it: "In attesa della tua approvazione",
|
|
90
|
+
ja: "承認を待っています",
|
|
91
|
+
ko: "승인을 기다리는 중",
|
|
92
|
+
pt: "Aguardando sua aprovação",
|
|
93
|
+
ru: "Ожидает вашего подтверждения",
|
|
94
|
+
zh: "等待您的批准"
|
|
95
|
+
},
|
|
96
|
+
cardsRefused: {
|
|
97
|
+
en: "Not run",
|
|
98
|
+
fr: "Non exécuté",
|
|
99
|
+
de: "Nicht ausgeführt",
|
|
100
|
+
es: "No ejecutado",
|
|
101
|
+
it: "Non eseguito",
|
|
102
|
+
ja: "実行されませんでした",
|
|
103
|
+
ko: "실행되지 않음",
|
|
104
|
+
pt: "Não executado",
|
|
105
|
+
ru: "Не выполнено",
|
|
106
|
+
zh: "未执行"
|
|
107
|
+
},
|
|
108
|
+
cardsLoading: {
|
|
109
|
+
en: "Loading",
|
|
110
|
+
fr: "Chargement",
|
|
111
|
+
de: "Wird geladen",
|
|
112
|
+
es: "Cargando",
|
|
113
|
+
it: "Caricamento",
|
|
114
|
+
ja: "読み込み中",
|
|
115
|
+
ko: "불러오는 중",
|
|
116
|
+
pt: "Carregando",
|
|
117
|
+
ru: "Загрузка",
|
|
118
|
+
zh: "加载中"
|
|
119
|
+
},
|
|
120
|
+
cardsPrevious: {
|
|
121
|
+
en: "Previous",
|
|
122
|
+
fr: "Précédent",
|
|
123
|
+
de: "Zurück",
|
|
124
|
+
es: "Anterior",
|
|
125
|
+
it: "Precedente",
|
|
126
|
+
ja: "前へ",
|
|
127
|
+
ko: "이전",
|
|
128
|
+
pt: "Anterior",
|
|
129
|
+
ru: "Назад",
|
|
130
|
+
zh: "上一个"
|
|
131
|
+
},
|
|
132
|
+
cardsNext: {
|
|
133
|
+
en: "Next",
|
|
134
|
+
fr: "Suivant",
|
|
135
|
+
de: "Weiter",
|
|
136
|
+
es: "Siguiente",
|
|
137
|
+
it: "Successivo",
|
|
138
|
+
ja: "次へ",
|
|
139
|
+
ko: "다음",
|
|
140
|
+
pt: "Próximo",
|
|
141
|
+
ru: "Далее",
|
|
142
|
+
zh: "下一个"
|
|
143
|
+
},
|
|
144
|
+
cardsSlide: {
|
|
145
|
+
en: "{n} of {total}",
|
|
146
|
+
fr: "{n} sur {total}",
|
|
147
|
+
de: "{n} von {total}",
|
|
148
|
+
es: "{n} de {total}",
|
|
149
|
+
it: "{n} di {total}",
|
|
150
|
+
ja: "{total}件中{n}件目",
|
|
151
|
+
ko: "{total}개 중 {n}번째",
|
|
152
|
+
pt: "{n} de {total}",
|
|
153
|
+
ru: "{n} из {total}",
|
|
154
|
+
zh: "第 {n} 个,共 {total} 个"
|
|
155
|
+
},
|
|
156
|
+
cardsRating: {
|
|
157
|
+
en: "Rated {value} out of {max}",
|
|
158
|
+
fr: "Noté {value} sur {max}",
|
|
159
|
+
de: "Bewertet mit {value} von {max}",
|
|
160
|
+
es: "Valoración: {value} de {max}",
|
|
161
|
+
it: "Valutato {value} su {max}",
|
|
162
|
+
ja: "{max}点中{value}点",
|
|
163
|
+
ko: "{max}점 만점에 {value}점",
|
|
164
|
+
pt: "Avaliado em {value} de {max}",
|
|
165
|
+
ru: "Оценка {value} из {max}",
|
|
166
|
+
zh: "评分 {value}/{max}"
|
|
167
|
+
},
|
|
168
|
+
cardsReviews: {
|
|
169
|
+
en: "{count} reviews",
|
|
170
|
+
fr: "{count} avis",
|
|
171
|
+
de: "{count} Bewertungen",
|
|
172
|
+
es: "{count} reseñas",
|
|
173
|
+
it: "{count} recensioni",
|
|
174
|
+
ja: "レビュー{count}件",
|
|
175
|
+
ko: "리뷰 {count}개",
|
|
176
|
+
pt: "{count} avaliações",
|
|
177
|
+
ru: "Отзывов: {count}",
|
|
178
|
+
zh: "{count} 条评价"
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
const kitStyles = '/* =========================================\n @aparte/plugin-cards — what every card shares\n =========================================\n A card wears core\'s `.aparte-card` recipe (border, radius, surface) and adds only what\n is its own: its width in the bubble, its one-line state drawing, its actions. Every\n value is a token, so a theme that restyles `--aparte-*` restyles the cards with it.\n */\n\n.aparte-rich-card {\n --aparte-rich-card-max-inline-size: 26rem;\n --aparte-rich-card-gap: var(--aparte-space-4);\n --aparte-rich-card-padding: var(--aparte-space-6);\n\n position: relative;\n /* A definite width: a bubble shrinks to its content, so a percentage would resolve to\n nothing. The card asks for its size and the bubble\'s own limit caps it. */\n inline-size: var(--aparte-rich-card-max-inline-size);\n max-inline-size: 100%;\n margin-block: var(--aparte-space-3);\n font-family: var(--aparte-font-family);\n font-variant-numeric: tabular-nums;\n line-height: 1.35;\n isolation: isolate;\n}\n\n.aparte-rich-card__glyph {\n display: inline-flex;\n flex: none;\n inline-size: var(--aparte-rich-card-glyph-size, 1.5rem);\n block-size: var(--aparte-rich-card-glyph-size, 1.5rem);\n}\n\n.aparte-rich-card__glyph > svg {\n inline-size: 100%;\n block-size: 100%;\n overflow: visible;\n}\n\n/* The one line a call shows when it has nothing to draw: waiting, refused, failed. */\n.aparte-rich-card[data-state="waiting"],\n.aparte-rich-card[data-state="refused"],\n.aparte-rich-card[data-state="unavailable"] {\n inline-size: fit-content;\n background: var(--aparte-surface-1);\n}\n\n.aparte-rich-card__strip {\n display: flex;\n align-items: center;\n gap: var(--aparte-space-3);\n padding: var(--aparte-space-3) var(--aparte-space-5);\n color: var(--aparte-text-muted);\n font-size: var(--aparte-font-size-md);\n --aparte-rich-card-glyph-size: 1.25rem;\n}\n\n.aparte-rich-card__strip .aparte-rich-card__glyph {\n opacity: 0.7;\n filter: grayscale(0.6);\n}\n\n.aparte-rich-card__strip-title {\n color: var(--aparte-text);\n font-weight: var(--aparte-font-weight-medium);\n}\n\n.aparte-rich-card__strip-state::before {\n content: "·";\n margin-inline-end: var(--aparte-space-3);\n}\n\n/* Buttons under a card: the button recipe, laid out. */\n.aparte-rich-card__actions {\n display: flex;\n flex-wrap: wrap;\n gap: var(--aparte-space-3);\n padding: var(--aparte-space-4) var(--aparte-rich-card-padding) var(--aparte-space-5);\n border-block-start: var(--aparte-border-width) solid var(--aparte-border);\n background: var(--aparte-surface-1);\n}\n\n/* A card this page did not set up: its sentence. */\n.aparte-rich-card__fallback {\n margin-block: var(--aparte-space-2);\n}\n\n/* Sparklines: the line takes the card\'s accent, the area fades from it. */\n.aparte-rich-card__sparkline {\n display: block;\n inline-size: 100%;\n block-size: 100%;\n overflow: visible;\n}\n\n.aparte-rich-card__sparkline-line {\n fill: none;\n stroke: var(--aparte-rich-card-spark, currentColor);\n stroke-width: 2;\n stroke-linecap: round;\n stroke-linejoin: round;\n}\n\n.aparte-rich-card__sparkline-area {\n fill: var(--aparte-rich-card-spark, currentColor);\n opacity: 0.14;\n stroke: none;\n}\n\n/* ── Rating: five stars, the coloured row clipped to the value ─────────── */\n\n.aparte-rich-card__rating {\n display: inline-flex;\n align-items: center;\n gap: var(--aparte-space-2);\n font-size: var(--aparte-font-size-sm);\n white-space: nowrap;\n}\n\n.aparte-rich-card__stars {\n position: relative;\n display: inline-block;\n inline-size: 5.25em;\n block-size: 1.05em;\n flex: none;\n}\n\n.aparte-rich-card__stars-base,\n.aparte-rich-card__stars-fill {\n position: absolute;\n inset: 0;\n inline-size: 100%;\n block-size: 100%;\n}\n\n.aparte-rich-card__stars-base { fill: color-mix(in oklab, var(--aparte-text) 16%, transparent); }\n\n.aparte-rich-card__stars-fill {\n fill: var(--aparte-rich-card-star, #f5a623);\n clip-path: inset(0 calc(100% - var(--aparte-rich-card-rating, 0%)) 0 0);\n}\n\n/* Right to left the fill grows from the right: the clip is mirrored. The stars are symmetric,\n so the glyphs themselves need no flip — a scaleX(-1) here moved the fill back to the left. */\n.aparte-rich-card__stars-fill:dir(rtl) {\n clip-path: inset(0 0 0 calc(100% - var(--aparte-rich-card-rating, 0%)));\n}\n\n.aparte-rich-card__stars svg {\n display: block;\n inline-size: 100%;\n block-size: 100%;\n}\n\n.aparte-rich-card__rating-value { font-weight: var(--aparte-font-weight-semibold); }\n.aparte-rich-card__rating-count { color: var(--aparte-text-muted); }\n\n/* ── Carousel: native scrolling, snap points, two buttons ───────────────── */\n\n.aparte-rich-card__carousel {\n --aparte-rich-card-slide-size: 13.5rem;\n --aparte-rich-card-slide-gap: var(--aparte-space-4);\n position: relative;\n}\n\n.aparte-rich-card__track {\n display: grid;\n grid-auto-flow: column;\n grid-auto-columns: var(--aparte-rich-card-slide-size);\n column-gap: var(--aparte-rich-card-slide-gap);\n overflow-x: auto;\n overscroll-behavior-x: contain;\n scroll-snap-type: x mandatory;\n /* Room round the slides for a tile\'s shadow, its lift on hover and its focus ring —\n a scroller clips everything outside its box — given back by the margin, so the\n slides still line up with what is above and below them. */\n --aparte-rich-card-track-inset: var(--aparte-space-3);\n --aparte-rich-card-track-inset-end: var(--aparte-space-6);\n margin: calc(var(--aparte-rich-card-track-inset) * -1) calc(var(--aparte-rich-card-track-inset) * -1) calc(var(--aparte-rich-card-track-inset-end) * -1);\n padding: var(--aparte-rich-card-track-inset) var(--aparte-rich-card-track-inset) var(--aparte-rich-card-track-inset-end);\n scroll-padding-inline: var(--aparte-rich-card-track-inset);\n scrollbar-width: none;\n}\n\n.aparte-rich-card__track::-webkit-scrollbar { display: none; }\n\n.aparte-rich-card__track:focus-visible {\n outline: var(--aparte-focus-outline-width) solid var(--aparte-border-focus);\n outline-offset: var(--aparte-focus-outline-offset);\n border-radius: var(--aparte-radius-lg);\n}\n\n.aparte-rich-card__slide {\n display: flex;\n scroll-snap-align: start;\n min-inline-size: 0;\n}\n\n.aparte-rich-card__slide > * { flex: 1; min-inline-size: 0; }\n\n.aparte-rich-card__nav {\n position: absolute;\n inset-block-start: 50%;\n z-index: 1;\n translate: 0 -50%;\n border-radius: var(--aparte-radius-full);\n box-shadow: 0 2px 10px -2px rgb(0 0 0 / 0.25);\n opacity: 0;\n transition: opacity var(--aparte-duration-fast, 120ms) ease;\n}\n\n.aparte-rich-card__nav--prev { inset-inline-start: calc(var(--aparte-space-3) * -1); }\n.aparte-rich-card__nav--next { inset-inline-end: calc(var(--aparte-space-3) * -1); }\n.aparte-rich-card__nav--prev:dir(rtl), .aparte-rich-card__nav--next:dir(rtl) { transform: scaleX(-1); }\n\n.aparte-rich-card__carousel[data-overflow] .aparte-rich-card__nav:not(:disabled) { opacity: 1; }\n.aparte-rich-card__carousel:not([data-overflow]) .aparte-rich-card__nav,\n.aparte-rich-card__nav:disabled { pointer-events: none; }\n\n/* ── Live cards: busy while the app answers a change ────────────────────── */\n\n.aparte-rich-card[data-busy] {\n cursor: progress;\n}\n\n.aparte-rich-card[data-busy]::after {\n content: "";\n position: absolute;\n inset-inline: 0;\n inset-block-start: 0;\n z-index: 3;\n block-size: 2px;\n background: linear-gradient(90deg, transparent, var(--aparte-primary), transparent);\n background-size: 50% 100%;\n background-repeat: no-repeat;\n animation: aparte-rich-card-busy 1s linear infinite;\n}\n\n@keyframes aparte-rich-card-busy {\n from { background-position: -50% 0; }\n to { background-position: 150% 0; }\n}\n\n/* ── Controls: a row of choices, a switch, a slider ─────────────────────── */\n\n.aparte-rich-card__segmented {\n display: inline-flex;\n gap: 2px;\n padding: 2px;\n border-radius: var(--aparte-radius-full);\n background: color-mix(in oklab, var(--aparte-text) 7%, transparent);\n}\n\n.aparte-rich-card__segment {\n min-inline-size: 2.5rem;\n /* 24px at least: the smallest target WCAG 2.2 accepts for a pointer (2.5.8). */\n min-block-size: 1.5rem;\n padding: var(--aparte-space-1) var(--aparte-space-3);\n border: 0;\n border-radius: var(--aparte-radius-full);\n background: transparent;\n color: var(--aparte-text-muted);\n font: inherit;\n font-size: var(--aparte-font-size-xs);\n font-weight: var(--aparte-font-weight-semibold);\n cursor: pointer;\n}\n\n.aparte-rich-card__segment:hover { color: var(--aparte-text); }\n\n.aparte-rich-card__segment[aria-pressed="true"] {\n background: var(--aparte-surface-1);\n color: var(--aparte-text);\n box-shadow: 0 1px 3px rgb(0 0 0 / 0.14);\n cursor: default;\n}\n\n.aparte-rich-card__segment:focus-visible,\n.aparte-rich-card__toggle:focus-visible,\n.aparte-rich-card__slider-input:focus-visible {\n outline: var(--aparte-focus-outline-width) solid var(--aparte-border-focus);\n outline-offset: var(--aparte-focus-outline-offset);\n}\n\n.aparte-rich-card__toggle {\n position: relative;\n flex: none;\n inline-size: 2.5rem;\n block-size: 1.5rem;\n padding: 0;\n border: 0;\n border-radius: var(--aparte-radius-full);\n background: color-mix(in oklab, var(--aparte-text) 22%, transparent);\n cursor: pointer;\n transition: background-color 0.15s ease;\n}\n\n.aparte-rich-card__toggle[aria-checked="true"] { background: var(--aparte-primary); }\n\n.aparte-rich-card__toggle-thumb {\n position: absolute;\n inset-block-start: 3px;\n inset-inline-start: 3px;\n inline-size: calc(1.5rem - 6px);\n block-size: calc(1.5rem - 6px);\n border-radius: var(--aparte-radius-full);\n background: #fff;\n box-shadow: 0 1px 3px rgb(0 0 0 / 0.25);\n transition: inset-inline-start 0.15s ease;\n}\n\n.aparte-rich-card__toggle[aria-checked="true"] .aparte-rich-card__toggle-thumb {\n inset-inline-start: calc(100% - 1.5rem + 3px);\n}\n\n.aparte-rich-card__slider {\n display: grid;\n grid-template-columns: 1fr auto;\n align-items: center;\n gap: var(--aparte-space-1) var(--aparte-space-4);\n font-size: var(--aparte-font-size-sm);\n}\n\n.aparte-rich-card__slider-label { color: var(--aparte-text-muted); }\n\n.aparte-rich-card__slider-value {\n grid-row: 1;\n grid-column: 2;\n font-weight: var(--aparte-font-weight-semibold);\n font-variant-numeric: tabular-nums;\n}\n\n.aparte-rich-card__slider-input {\n grid-column: 1 / -1;\n inline-size: 100%;\n margin: 0;\n accent-color: var(--aparte-primary);\n cursor: pointer;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .aparte-rich-card,\n .aparte-rich-card::after,\n .aparte-rich-card *,\n .aparte-rich-card *::before,\n .aparte-rich-card *::after {\n animation: none !important;\n transition: none !important;\n }\n}\n';
|
|
182
|
+
const CARD_SEGMENT_TYPE = "card";
|
|
183
|
+
const registries = /* @__PURE__ */ new WeakMap();
|
|
184
|
+
function drawCardSegment(config, segment) {
|
|
185
|
+
return registries.get(config)?.get(segment.card)?.at(-1)?.draw(segment) ?? null;
|
|
186
|
+
}
|
|
187
|
+
function cardsSetUp(config) {
|
|
188
|
+
return [...registries.get(config)?.keys() ?? []];
|
|
189
|
+
}
|
|
190
|
+
const bindings = /* @__PURE__ */ new WeakMap();
|
|
191
|
+
const MARKUP = ":markup";
|
|
192
|
+
function apply(target, compute, attr) {
|
|
193
|
+
const text = compute();
|
|
194
|
+
if (attr === MARKUP) target.innerHTML = text;
|
|
195
|
+
else if (attr) target.setAttribute(attr, text);
|
|
196
|
+
else target.textContent = text;
|
|
197
|
+
}
|
|
198
|
+
function bindMarkup(target, compute) {
|
|
199
|
+
apply(target, compute, MARKUP);
|
|
200
|
+
const list = bindings.get(target) ?? [];
|
|
201
|
+
list.push({ attr: MARKUP, compute });
|
|
202
|
+
bindings.set(target, list);
|
|
203
|
+
target.setAttribute("data-card-bound", "");
|
|
204
|
+
}
|
|
205
|
+
function relabel(root) {
|
|
206
|
+
const own = bindings.get(root);
|
|
207
|
+
if (own) for (const b of own) apply(root, b.compute, b.attr);
|
|
208
|
+
for (const node of root.querySelectorAll("[data-card-bound]")) {
|
|
209
|
+
for (const b of bindings.get(node) ?? []) apply(node, b.compute, b.attr);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
function makeContext(config, variant, source, card, onChange) {
|
|
213
|
+
const bind = (target, compute, attr) => {
|
|
214
|
+
apply(target, compute, attr);
|
|
215
|
+
const list = bindings.get(target) ?? [];
|
|
216
|
+
list.push({ attr, compute });
|
|
217
|
+
bindings.set(target, list);
|
|
218
|
+
target.setAttribute("data-card-bound", "");
|
|
219
|
+
};
|
|
220
|
+
return {
|
|
221
|
+
config,
|
|
222
|
+
variant,
|
|
223
|
+
source,
|
|
224
|
+
bind,
|
|
225
|
+
word: (target, key, table, vars) => bind(target, () => word(config, key, table, vars)),
|
|
226
|
+
action: (action, data, className) => actionButton(card, action, data, className),
|
|
227
|
+
change: (name, value) => onChange?.(name, value),
|
|
228
|
+
live: onChange !== void 0
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
function patch(element, next) {
|
|
232
|
+
const active = typeof document === "undefined" ? null : document.activeElement;
|
|
233
|
+
let key = null;
|
|
234
|
+
let path = null;
|
|
235
|
+
if (active instanceof HTMLElement && element.contains(active) && active !== element) {
|
|
236
|
+
key = active.getAttribute("data-card-key");
|
|
237
|
+
path = [];
|
|
238
|
+
for (let node = active; node !== element; node = node.parentElement) {
|
|
239
|
+
path.unshift(Array.prototype.indexOf.call(node.parentElement.children, node));
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
for (const name of element.getAttributeNames()) if (!next.hasAttribute(name)) element.removeAttribute(name);
|
|
243
|
+
for (const name of next.getAttributeNames()) element.setAttribute(name, next.getAttribute(name));
|
|
244
|
+
element.replaceChildren(...next.childNodes);
|
|
245
|
+
const moved = bindings.get(next);
|
|
246
|
+
if (moved) bindings.set(element, moved);
|
|
247
|
+
const from = drawnFrom.get(next);
|
|
248
|
+
if (from === void 0) drawnFrom.delete(element);
|
|
249
|
+
else drawnFrom.set(element, from);
|
|
250
|
+
homes.set(next, element);
|
|
251
|
+
if (key !== null || path) {
|
|
252
|
+
let target = key ? element.querySelector(`[data-card-key="${cssEscape(key)}"]`) : null;
|
|
253
|
+
if (!target && path) {
|
|
254
|
+
target = element;
|
|
255
|
+
for (const i of path) target = target?.children[i] ?? null;
|
|
256
|
+
}
|
|
257
|
+
if (target instanceof HTMLElement) target.focus({ preventScroll: true });
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
const homes = /* @__PURE__ */ new WeakMap();
|
|
261
|
+
const changeTurns = /* @__PURE__ */ new WeakMap();
|
|
262
|
+
const queues = /* @__PURE__ */ new WeakMap();
|
|
263
|
+
const drawnFrom = /* @__PURE__ */ new WeakMap();
|
|
264
|
+
function bumpTurn(element) {
|
|
265
|
+
changeTurns.set(element, (changeTurns.get(element) ?? 0) + 1);
|
|
266
|
+
queues.delete(element);
|
|
267
|
+
}
|
|
268
|
+
function busy(element, on) {
|
|
269
|
+
element.toggleAttribute("data-busy", on);
|
|
270
|
+
if (on) element.setAttribute("aria-busy", "true");
|
|
271
|
+
else element.removeAttribute("aria-busy");
|
|
272
|
+
}
|
|
273
|
+
function actionButton(card, action, data, className = "") {
|
|
274
|
+
if (!action || typeof action.label !== "string" || !action.label.trim()) return null;
|
|
275
|
+
const url = action.url === void 0 ? void 0 : safeUrl(action.url);
|
|
276
|
+
if (action.url !== void 0 && !url) return null;
|
|
277
|
+
if (!url && action.send === void 0 && action.id === void 0) return null;
|
|
278
|
+
const button = el("button", {
|
|
279
|
+
class: `aparte-btn aparte-btn--sm ${action.primary ? "aparte-btn--primary aparte-btn--solid" : "aparte-btn--surface"} aparte-rich-card__action${className ? ` ${className}` : ""}`,
|
|
280
|
+
attrs: { type: "button" },
|
|
281
|
+
text: action.label
|
|
282
|
+
});
|
|
283
|
+
button.addEventListener("click", (event) => {
|
|
284
|
+
event.stopPropagation();
|
|
285
|
+
runAction(card, button, action, url, data);
|
|
286
|
+
});
|
|
287
|
+
return button;
|
|
288
|
+
}
|
|
289
|
+
function runAction(card, button, action, url, data) {
|
|
290
|
+
const detail = { card, action, data };
|
|
291
|
+
const proceed = button.dispatchEvent(new CustomEvent("aparte-card-action", {
|
|
292
|
+
detail,
|
|
293
|
+
bubbles: true,
|
|
294
|
+
composed: true,
|
|
295
|
+
cancelable: true
|
|
296
|
+
}));
|
|
297
|
+
if (!proceed) return;
|
|
298
|
+
if (url) {
|
|
299
|
+
if (/^https?:/i.test(url)) window.open(url, "_blank", "noopener,noreferrer");
|
|
300
|
+
else window.location.href = url;
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
if (action.send !== void 0) {
|
|
304
|
+
const composer = button.closest("aparte-chat, [data-aparte-chat]")?.querySelector("aparte-composer") ?? document.querySelector("aparte-composer");
|
|
305
|
+
if (!composer) {
|
|
306
|
+
console.warn(`[aparte-cards] the ${card} card has no <aparte-composer> to send "${action.send}" through.`);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
if (composer.streaming || composer.panelActive) return;
|
|
310
|
+
const draft = composer.value;
|
|
311
|
+
composer.setValue(action.send);
|
|
312
|
+
composer.submit();
|
|
313
|
+
if (draft.trim()) composer.setValue(draft);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
function parseResult(segment) {
|
|
317
|
+
if (segment.structuredResult !== void 0) return segment.structuredResult;
|
|
318
|
+
const text = segment.result;
|
|
319
|
+
if (typeof text !== "string") return void 0;
|
|
320
|
+
try {
|
|
321
|
+
return JSON.parse(text);
|
|
322
|
+
} catch {
|
|
323
|
+
return text;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
function stateOf(segment) {
|
|
327
|
+
switch (segment.status) {
|
|
328
|
+
case "resolved":
|
|
329
|
+
return "ready";
|
|
330
|
+
case "pending":
|
|
331
|
+
return "loading";
|
|
332
|
+
case "awaiting-approval":
|
|
333
|
+
return "waiting";
|
|
334
|
+
case "rejected":
|
|
335
|
+
return "refused";
|
|
336
|
+
default:
|
|
337
|
+
return "unavailable";
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
function defineCard(definition) {
|
|
341
|
+
const styles = `${kitStyles}
|
|
342
|
+
${definition.styles}`;
|
|
343
|
+
const defaultVariant = definition.variants[0];
|
|
344
|
+
const known = new Set(definition.variants);
|
|
345
|
+
const warned = /* @__PURE__ */ new WeakSet();
|
|
346
|
+
function chooseVariant(options, data, asked) {
|
|
347
|
+
const wanted = asked ?? (typeof options.variant === "function" ? data ? options.variant(data) : void 0 : options.variant);
|
|
348
|
+
return wanted && known.has(wanted) ? wanted : defaultVariant;
|
|
349
|
+
}
|
|
350
|
+
function root(state, variant, id) {
|
|
351
|
+
return el("div", {
|
|
352
|
+
class: `aparte-segment aparte-card aparte-rich-card aparte-${definition.name}-card`,
|
|
353
|
+
attrs: {
|
|
354
|
+
"data-segment-id": id,
|
|
355
|
+
"data-card": definition.name,
|
|
356
|
+
"data-variant": variant,
|
|
357
|
+
"data-state": state,
|
|
358
|
+
role: "group",
|
|
359
|
+
"aria-busy": state === "loading" ? "true" : void 0
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
function actionsOf(options, data) {
|
|
364
|
+
const buttons = (options.actions?.(data) ?? []).map((action) => actionButton(definition.name, action, data)).filter((b) => b !== null).slice(0, 3);
|
|
365
|
+
return buttons.length ? el("div", { class: "aparte-rich-card__actions" }, ...buttons) : null;
|
|
366
|
+
}
|
|
367
|
+
function strip(config, state, partial, variant, id, source = "tool") {
|
|
368
|
+
const node = root(state, variant, id);
|
|
369
|
+
const ctx = makeContext(config, variant, source, definition.name);
|
|
370
|
+
const key = state === "waiting" ? "cardsWaiting" : state === "refused" ? "cardsRefused" : "cardsUnavailable";
|
|
371
|
+
const title = el("span", { class: "aparte-rich-card__strip-title" });
|
|
372
|
+
ctx.bind(title, () => definition.label(partial, ctx));
|
|
373
|
+
const status = el("span", { class: "aparte-rich-card__strip-state" });
|
|
374
|
+
ctx.word(status, key, KIT_WORDS[key]);
|
|
375
|
+
node.append(el("div", { class: "aparte-rich-card__strip" }, glyph(definition.icon), title, status));
|
|
376
|
+
ctx.bind(node, () => definition.label(partial, ctx), "aria-label");
|
|
377
|
+
return node;
|
|
378
|
+
}
|
|
379
|
+
function drawTool(options, config, segment2) {
|
|
380
|
+
const state = stateOf(segment2);
|
|
381
|
+
const input = segment2.toolCall?.input ?? {};
|
|
382
|
+
const readInput = options.mapInput ?? definition.partial;
|
|
383
|
+
let partial = null;
|
|
384
|
+
try {
|
|
385
|
+
partial = readInput?.(input) ?? null;
|
|
386
|
+
} catch {
|
|
387
|
+
partial = null;
|
|
388
|
+
}
|
|
389
|
+
if (state === "loading") {
|
|
390
|
+
const variant = chooseVariant(options, null);
|
|
391
|
+
const node = root(state, variant, segment2.id);
|
|
392
|
+
const ctx = makeContext(config, variant, "tool", definition.name);
|
|
393
|
+
node.append(...definition.skeleton(partial, ctx));
|
|
394
|
+
ctx.bind(node, () => `${definition.label(partial, ctx)} — ${word(config, "cardsLoading", KIT_WORDS.cardsLoading)}`, "aria-label");
|
|
395
|
+
return node;
|
|
396
|
+
}
|
|
397
|
+
if (state !== "ready") return strip(config, state, partial, chooseVariant(options, null), segment2.id);
|
|
398
|
+
let data = null;
|
|
399
|
+
let from = "";
|
|
400
|
+
try {
|
|
401
|
+
const raw = parseResult(segment2);
|
|
402
|
+
from = fingerprint(raw);
|
|
403
|
+
const live = recall(options, `tool:${segment2.id}`, from);
|
|
404
|
+
if (live) return drawData(options, config, live.data, live.variant, segment2.id, "tool", from);
|
|
405
|
+
data = definition.normalize(options.map ? options.map(raw, input, segment2) : raw);
|
|
406
|
+
} catch (error) {
|
|
407
|
+
console.warn(`[aparte-cards] the ${definition.name} card's map threw:`, error);
|
|
408
|
+
}
|
|
409
|
+
if (!data) {
|
|
410
|
+
if (!warned.has(options)) {
|
|
411
|
+
warned.add(options);
|
|
412
|
+
console.warn(`[aparte-cards] the ${definition.name} card got a result it cannot draw — check its \`map\` (tool "${segment2.toolCall?.name}").`);
|
|
413
|
+
}
|
|
414
|
+
return strip(config, "unavailable", partial, chooseVariant(options, null), segment2.id);
|
|
415
|
+
}
|
|
416
|
+
return drawData(options, config, data, chooseVariant(options, data), segment2.id, "tool", from);
|
|
417
|
+
}
|
|
418
|
+
const kept = /* @__PURE__ */ new WeakMap();
|
|
419
|
+
function recall(options, key, from) {
|
|
420
|
+
const entry = kept.get(options)?.get(key);
|
|
421
|
+
return entry && from && entry.from === from ? entry : void 0;
|
|
422
|
+
}
|
|
423
|
+
function remember(options, key, from, data, variant) {
|
|
424
|
+
if (!from) return;
|
|
425
|
+
const byKey = kept.get(options) ?? /* @__PURE__ */ new Map();
|
|
426
|
+
kept.set(options, byKey);
|
|
427
|
+
byKey.delete(key);
|
|
428
|
+
byKey.set(key, { from, data, variant });
|
|
429
|
+
if (byKey.size > KEPT_MAX) byKey.delete(byKey.keys().next().value);
|
|
430
|
+
}
|
|
431
|
+
function drawData(options, config, data, variant, id, source, from) {
|
|
432
|
+
const node = root("ready", variant, id);
|
|
433
|
+
drawnFrom.set(node, from);
|
|
434
|
+
const onChange = (name, value) => void change(homes.get(node) ?? node, options, config, data, variant, id, source, from, name, value);
|
|
435
|
+
const ctx = makeContext(config, variant, source, definition.name, options.onChange ? onChange : void 0);
|
|
436
|
+
node.append(...definition.render(data, ctx));
|
|
437
|
+
const actions = actionsOf(options, data);
|
|
438
|
+
if (actions) node.append(actions);
|
|
439
|
+
ctx.bind(node, () => definition.label(data, ctx), "aria-label");
|
|
440
|
+
return node;
|
|
441
|
+
}
|
|
442
|
+
async function change(node, options, config, data, variant, id, source, from, name, value) {
|
|
443
|
+
const element = node;
|
|
444
|
+
const waiting = queues.get(element);
|
|
445
|
+
const detail = { card: definition.name, name, value, data: waiting?.data ?? data };
|
|
446
|
+
const proceed = element.dispatchEvent(new CustomEvent("aparte-card-change", {
|
|
447
|
+
detail,
|
|
448
|
+
bubbles: true,
|
|
449
|
+
composed: true,
|
|
450
|
+
cancelable: true
|
|
451
|
+
}));
|
|
452
|
+
if (!proceed || !options.onChange) return;
|
|
453
|
+
const queue = waiting ?? { pending: /* @__PURE__ */ new Map(), running: false, data, variant };
|
|
454
|
+
queues.set(element, queue);
|
|
455
|
+
queue.pending.delete(name);
|
|
456
|
+
queue.pending.set(name, value);
|
|
457
|
+
if (queue.running) return;
|
|
458
|
+
queue.running = true;
|
|
459
|
+
const turn = changeTurns.get(element) ?? 0;
|
|
460
|
+
busy(element, true);
|
|
461
|
+
try {
|
|
462
|
+
while (queue.pending.size > 0) {
|
|
463
|
+
const [nextName, nextValue] = queue.pending.entries().next().value;
|
|
464
|
+
queue.pending.delete(nextName);
|
|
465
|
+
let next;
|
|
466
|
+
try {
|
|
467
|
+
next = await options.onChange({ name: nextName, value: nextValue }, queue.data);
|
|
468
|
+
} catch (error) {
|
|
469
|
+
console.warn(`[aparte-cards] the ${definition.name} card's onChange threw:`, error);
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
if ((changeTurns.get(element) ?? 0) !== turn) return;
|
|
473
|
+
if (next === void 0 || next === null) continue;
|
|
474
|
+
const fresh = definition.normalize(next);
|
|
475
|
+
if (!fresh) {
|
|
476
|
+
console.warn(`[aparte-cards] the ${definition.name} card's onChange returned data it cannot draw — it was left as it was.`);
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
queue.data = fresh;
|
|
480
|
+
queue.variant = chooseVariant(options, fresh, queue.variant);
|
|
481
|
+
remember(options, `${source}:${id}`, from, fresh, queue.variant);
|
|
482
|
+
patch(element, drawData(options, config, fresh, queue.variant, id, source, from));
|
|
483
|
+
if (queue.pending.size > 0) busy(element, true);
|
|
484
|
+
}
|
|
485
|
+
} finally {
|
|
486
|
+
queue.running = false;
|
|
487
|
+
if (queues.get(element) === queue) queues.delete(element);
|
|
488
|
+
if ((changeTurns.get(element) ?? 0) === turn) busy(element, false);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
function setup(options = {}) {
|
|
492
|
+
const config = options.config ?? aparteGlobalConfig;
|
|
493
|
+
const matchers = options.tool === void 0 ? [] : Array.isArray(options.tool) ? options.tool : [options.tool];
|
|
494
|
+
const toolRenderer = {
|
|
495
|
+
render: (segment2) => drawTool(options, config, segment2),
|
|
496
|
+
// Same element, new inside: the card the reader is looking at stays put.
|
|
497
|
+
update: (element, segment2) => {
|
|
498
|
+
const next = drawTool(options, config, segment2);
|
|
499
|
+
if (drawnFrom.get(next) !== drawnFrom.get(element)) bumpTurn(element);
|
|
500
|
+
const wasBusy = element.hasAttribute("data-busy") && queues.has(element);
|
|
501
|
+
patch(element, next);
|
|
502
|
+
if (wasBusy) busy(element, true);
|
|
503
|
+
},
|
|
504
|
+
relabel: (element) => relabel(element),
|
|
505
|
+
getStyles: () => styles
|
|
506
|
+
};
|
|
507
|
+
const restores = matchers.map((match) => config.overrideToolRenderer(match, toolRenderer));
|
|
508
|
+
const registry = registries.get(config) ?? /* @__PURE__ */ new Map();
|
|
509
|
+
registries.set(config, registry);
|
|
510
|
+
const entry = {
|
|
511
|
+
draw: (segment2) => {
|
|
512
|
+
const from = fingerprint(segment2.data);
|
|
513
|
+
const live = recall(options, `segment:${segment2.id}`, from);
|
|
514
|
+
if (live) return drawData(options, config, live.data, live.variant, segment2.id, "segment", from);
|
|
515
|
+
const data = definition.normalize(segment2.data);
|
|
516
|
+
const variant = chooseVariant(options, data, segment2.variant);
|
|
517
|
+
if (!data) return strip(config, "unavailable", null, variant, segment2.id, "segment");
|
|
518
|
+
return drawData(options, config, data, variant, segment2.id, "segment", from);
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
const stack = registry.get(definition.name) ?? [];
|
|
522
|
+
stack.push(entry);
|
|
523
|
+
registry.set(definition.name, stack);
|
|
524
|
+
const restoreSegments = overrideSegmentRenderer(cardSegmentRenderer(config), config);
|
|
525
|
+
let stopped = false;
|
|
526
|
+
return () => {
|
|
527
|
+
if (stopped) return;
|
|
528
|
+
stopped = true;
|
|
529
|
+
for (const restore of restores) restore();
|
|
530
|
+
const own = registry.get(definition.name);
|
|
531
|
+
const at = own?.indexOf(entry) ?? -1;
|
|
532
|
+
if (own && at >= 0) own.splice(at, 1);
|
|
533
|
+
if (own && own.length === 0) registry.delete(definition.name);
|
|
534
|
+
restoreSegments();
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
function segment(raw, options = {}) {
|
|
538
|
+
const config = options.config ?? aparteGlobalConfig;
|
|
539
|
+
const data = definition.normalize(raw);
|
|
540
|
+
const ctx = makeContext(config, options.variant ?? defaultVariant, "segment", definition.name);
|
|
541
|
+
return {
|
|
542
|
+
id: options.id ?? `card-${definition.name}-${Math.random().toString(36).slice(2, 10)}`,
|
|
543
|
+
type: CARD_SEGMENT_TYPE,
|
|
544
|
+
card: definition.name,
|
|
545
|
+
data: raw,
|
|
546
|
+
...options.variant ? { variant: options.variant } : {},
|
|
547
|
+
...data ? { fallback: definition.fallback(data, ctx) } : {}
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
sheets.add(styles);
|
|
551
|
+
return { name: definition.name, variants: definition.variants, setup, segment };
|
|
552
|
+
}
|
|
553
|
+
const sheets = /* @__PURE__ */ new Set();
|
|
554
|
+
const KEPT_MAX = 100;
|
|
555
|
+
function fingerprint(raw) {
|
|
556
|
+
try {
|
|
557
|
+
return JSON.stringify(raw) ?? "";
|
|
558
|
+
} catch {
|
|
559
|
+
return "";
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
function cardSegmentRenderer(config) {
|
|
563
|
+
return {
|
|
564
|
+
type: CARD_SEGMENT_TYPE,
|
|
565
|
+
render: (segment) => {
|
|
566
|
+
const entry = registries.get(config)?.get(segment.card)?.at(-1);
|
|
567
|
+
if (entry) return entry.draw(segment);
|
|
568
|
+
return el("p", { class: "aparte-segment aparte-rich-card__fallback", attrs: { "data-segment-id": segment.id }, text: segment.fallback ?? "" });
|
|
569
|
+
},
|
|
570
|
+
// A card segment sent again with new data (`updateSegment`) is drawn in place.
|
|
571
|
+
update: (element, segment) => {
|
|
572
|
+
const entry = registries.get(config)?.get(segment.card)?.at(-1);
|
|
573
|
+
if (!entry) return;
|
|
574
|
+
if (!element.hasAttribute("data-card")) {
|
|
575
|
+
element.replaceWith(entry.draw(segment));
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
const next = entry.draw(segment);
|
|
579
|
+
if (drawnFrom.get(next) !== drawnFrom.get(element)) bumpTurn(element);
|
|
580
|
+
const wasBusy = element.hasAttribute("data-busy") && queues.has(element);
|
|
581
|
+
patch(element, next);
|
|
582
|
+
if (wasBusy) busy(element, true);
|
|
583
|
+
},
|
|
584
|
+
relabel: (element) => relabel(element),
|
|
585
|
+
getStyles: () => [...sheets].join("\n")
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
function toDate(value) {
|
|
589
|
+
if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value;
|
|
590
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
591
|
+
const d = new Date(value < 1e12 ? value * 1e3 : value);
|
|
592
|
+
return Number.isNaN(d.getTime()) ? null : d;
|
|
593
|
+
}
|
|
594
|
+
if (typeof value === "string" && value.trim()) {
|
|
595
|
+
const day = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value.trim());
|
|
596
|
+
if (day) {
|
|
597
|
+
const local = new Date(Number(day[1]), Number(day[2]) - 1, Number(day[3]));
|
|
598
|
+
return local.getMonth() === Number(day[2]) - 1 ? local : null;
|
|
599
|
+
}
|
|
600
|
+
const d = new Date(value.trim());
|
|
601
|
+
return Number.isNaN(d.getTime()) ? null : d;
|
|
602
|
+
}
|
|
603
|
+
return null;
|
|
604
|
+
}
|
|
605
|
+
function formatNumber(config, value, options = {}) {
|
|
606
|
+
return new Intl.NumberFormat(intlTag(config), options).format(value);
|
|
607
|
+
}
|
|
608
|
+
function formatPrice(config, amount, currency) {
|
|
609
|
+
const code = currencyCode(currency);
|
|
610
|
+
const plain = () => formatNumber(config, amount, { minimumFractionDigits: Number.isInteger(amount) ? 0 : 2, maximumFractionDigits: 2 });
|
|
611
|
+
if (!code) return typeof currency === "string" && currency.trim() ? `${plain()} ${currency.trim()}` : plain();
|
|
612
|
+
try {
|
|
613
|
+
return formatNumber(config, amount, { style: "currency", currency: code });
|
|
614
|
+
} catch {
|
|
615
|
+
return `${formatNumber(config, amount, { maximumFractionDigits: 2 })} ${currency}`;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
const SYMBOLS = { "€": "EUR", $: "USD", "£": "GBP", "¥": "JPY", "₩": "KRW", "₽": "RUB", "₹": "INR", "R$": "BRL", CHF: "CHF" };
|
|
619
|
+
function currencyCode(value) {
|
|
620
|
+
if (typeof value !== "string" || !value.trim()) return void 0;
|
|
621
|
+
const v = value.trim();
|
|
622
|
+
if (SYMBOLS[v]) return SYMBOLS[v];
|
|
623
|
+
return /^[A-Za-z]{3}$/.test(v) ? v.toUpperCase() : void 0;
|
|
624
|
+
}
|
|
625
|
+
const AMOUNT = /(?:\d|[.,]\d)[\d.,'\s]*/;
|
|
626
|
+
const DOLLARS = { US: "USD", CA: "CAD", A: "AUD", AU: "AUD", NZ: "NZD", HK: "HKD", S: "SGD", SG: "SGD", MX: "MXN", NT: "TWD", R: "BRL" };
|
|
627
|
+
let isoCodes;
|
|
628
|
+
function isCurrency(code) {
|
|
629
|
+
if (isoCodes === void 0) {
|
|
630
|
+
try {
|
|
631
|
+
isoCodes = new Set(Intl.supportedValuesOf("currency"));
|
|
632
|
+
} catch {
|
|
633
|
+
isoCodes = null;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
return isoCodes ? isoCodes.has(code) : /^[A-Z]{3}$/.test(code);
|
|
637
|
+
}
|
|
638
|
+
function currencyIn(text) {
|
|
639
|
+
const dollar = /(?:^|[^A-Za-z])([A-Z]{1,2})\$/.exec(text);
|
|
640
|
+
if (dollar && DOLLARS[dollar[1]]) return DOLLARS[dollar[1]];
|
|
641
|
+
if (/(?:^|[^A-Za-z])CHF(?:$|[^A-Za-z])/.test(text)) return "CHF";
|
|
642
|
+
const sign = /[€£¥₩₽₹$]/.exec(text);
|
|
643
|
+
if (sign) return currencyCode(sign[0]);
|
|
644
|
+
const words = text.match(/[A-Za-z]+/g) ?? [];
|
|
645
|
+
for (const w of words) {
|
|
646
|
+
const code = w.toUpperCase();
|
|
647
|
+
if (w.length === 3 && (w === code || words.length === 1) && isCurrency(code)) return code;
|
|
648
|
+
}
|
|
649
|
+
return void 0;
|
|
650
|
+
}
|
|
651
|
+
function readPriceText(text) {
|
|
652
|
+
const match = AMOUNT.exec(text);
|
|
653
|
+
const currency = currencyIn(text.replace(AMOUNT, " "));
|
|
654
|
+
if (!match) return currency ? { currency } : {};
|
|
655
|
+
let digits = match[0].replace(/[\s']/g, "").replace(/[.,]+$/, "");
|
|
656
|
+
const dot = digits.lastIndexOf(".");
|
|
657
|
+
const comma = digits.lastIndexOf(",");
|
|
658
|
+
if (dot >= 0 && comma >= 0) {
|
|
659
|
+
const [decimal, group] = dot > comma ? [".", ","] : [",", "."];
|
|
660
|
+
digits = digits.split(group).join("").replace(decimal, ".");
|
|
661
|
+
} else if (comma >= 0) {
|
|
662
|
+
digits = /^\d*,\d{1,2}$/.test(digits) ? digits.replace(",", ".") : digits.split(",").join("");
|
|
663
|
+
} else if (dot >= 0 && (digits.indexOf(".") !== dot || /^[1-9]\d{0,2}\.\d{3}$/.test(digits))) {
|
|
664
|
+
digits = digits.split(".").join("");
|
|
665
|
+
}
|
|
666
|
+
const n = Number(digits);
|
|
667
|
+
if (!Number.isFinite(n)) return currency ? { currency } : {};
|
|
668
|
+
const amount = /[-−][^\d]*$/.test(text.slice(0, match.index)) ? -n : n;
|
|
669
|
+
return currency ? { amount, currency } : { amount };
|
|
670
|
+
}
|
|
671
|
+
function formatPercent(config, ratio) {
|
|
672
|
+
return formatNumber(config, ratio, { style: "percent", maximumFractionDigits: 0 });
|
|
673
|
+
}
|
|
674
|
+
function formatUnit(config, value, unit, digits = 0) {
|
|
675
|
+
try {
|
|
676
|
+
return formatNumber(config, value, { style: "unit", unit, unitDisplay: "short", maximumFractionDigits: digits });
|
|
677
|
+
} catch {
|
|
678
|
+
return `${formatNumber(config, value, { maximumFractionDigits: digits })} ${unit}`;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
function formatDay(config, date, style = "short", now = /* @__PURE__ */ new Date()) {
|
|
682
|
+
const tag = intlTag(config);
|
|
683
|
+
const startOf = (d) => new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();
|
|
684
|
+
const days = Math.round((startOf(date) - startOf(now)) / 864e5);
|
|
685
|
+
let text;
|
|
686
|
+
if (days === 0 || days === 1) {
|
|
687
|
+
text = new Intl.RelativeTimeFormat(tag, { numeric: "auto" }).format(days, "day");
|
|
688
|
+
} else {
|
|
689
|
+
text = new Intl.DateTimeFormat(tag, { weekday: style }).format(date);
|
|
690
|
+
}
|
|
691
|
+
return text.charAt(0).toLocaleUpperCase(tag) + text.slice(1);
|
|
692
|
+
}
|
|
693
|
+
function formatHour(config, date) {
|
|
694
|
+
const tag = intlTag(config);
|
|
695
|
+
const twelve = /h1[12]/.test(new Intl.DateTimeFormat(tag, { hour: "numeric" }).resolvedOptions().hourCycle ?? "");
|
|
696
|
+
return twelve ? new Intl.DateTimeFormat(tag, { hour: "numeric", minute: date.getMinutes() ? "2-digit" : void 0 }).format(date) : new Intl.DateTimeFormat(tag, { hour: "2-digit", minute: "2-digit" }).format(date);
|
|
697
|
+
}
|
|
698
|
+
function formatWhen(config, date) {
|
|
699
|
+
return new Intl.DateTimeFormat(intlTag(config), { weekday: "short", hour: "numeric", minute: "2-digit" }).format(date);
|
|
700
|
+
}
|
|
701
|
+
export {
|
|
702
|
+
CARD_SEGMENT_TYPE as C,
|
|
703
|
+
KIT_WORDS as K,
|
|
704
|
+
currencyCode as a,
|
|
705
|
+
defineCard as b,
|
|
706
|
+
cardsSetUp as c,
|
|
707
|
+
drawCardSegment as d,
|
|
708
|
+
el as e,
|
|
709
|
+
formatDay as f,
|
|
710
|
+
formatHour as g,
|
|
711
|
+
formatNumber as h,
|
|
712
|
+
formatPercent as i,
|
|
713
|
+
formatPrice as j,
|
|
714
|
+
formatUnit as k,
|
|
715
|
+
formatWhen as l,
|
|
716
|
+
glyph as m,
|
|
717
|
+
intlTag as n,
|
|
718
|
+
languageOf as o,
|
|
719
|
+
num as p,
|
|
720
|
+
str as q,
|
|
721
|
+
word as r,
|
|
722
|
+
safeUrl as s,
|
|
723
|
+
toDate as t,
|
|
724
|
+
readPriceText as u,
|
|
725
|
+
bindMarkup as v,
|
|
726
|
+
webUrl as w
|
|
727
|
+
};
|
|
728
|
+
//# sourceMappingURL=format-BFq79ydr.js.map
|