@nymbalhq/web-components 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/astro/NymbalElement.astro +19 -0
- package/dist/base-element-CPMgAdyQ.d.ts +91 -0
- package/dist/chunk-2TPQNQYO.js +155 -0
- package/dist/chunk-2TPQNQYO.js.map +1 -0
- package/dist/chunk-2XXBQPDB.js +113 -0
- package/dist/chunk-2XXBQPDB.js.map +1 -0
- package/dist/chunk-42SSME5H.js +84 -0
- package/dist/chunk-42SSME5H.js.map +1 -0
- package/dist/chunk-4COVK727.js +204 -0
- package/dist/chunk-4COVK727.js.map +1 -0
- package/dist/chunk-5VWZ2U5K.js +149 -0
- package/dist/chunk-5VWZ2U5K.js.map +1 -0
- package/dist/chunk-6XCCAAMF.js +656 -0
- package/dist/chunk-6XCCAAMF.js.map +1 -0
- package/dist/chunk-7JAAOVCE.js +114 -0
- package/dist/chunk-7JAAOVCE.js.map +1 -0
- package/dist/chunk-B73QVSA5.js +206 -0
- package/dist/chunk-B73QVSA5.js.map +1 -0
- package/dist/chunk-DR5QB5VN.js +17 -0
- package/dist/chunk-DR5QB5VN.js.map +1 -0
- package/dist/chunk-DXWMNB5F.js +70 -0
- package/dist/chunk-DXWMNB5F.js.map +1 -0
- package/dist/chunk-EBMA7JGV.js +130 -0
- package/dist/chunk-EBMA7JGV.js.map +1 -0
- package/dist/chunk-EYDMJNNK.js +176 -0
- package/dist/chunk-EYDMJNNK.js.map +1 -0
- package/dist/chunk-FIF6GQ6U.js +141 -0
- package/dist/chunk-FIF6GQ6U.js.map +1 -0
- package/dist/chunk-GHXTJ2CF.js +109 -0
- package/dist/chunk-GHXTJ2CF.js.map +1 -0
- package/dist/chunk-H3ZBYNHV.js +153 -0
- package/dist/chunk-H3ZBYNHV.js.map +1 -0
- package/dist/chunk-HPBC3OG7.js +174 -0
- package/dist/chunk-HPBC3OG7.js.map +1 -0
- package/dist/chunk-IYT7VXFS.js +105 -0
- package/dist/chunk-IYT7VXFS.js.map +1 -0
- package/dist/chunk-LHYXNQWY.js +105 -0
- package/dist/chunk-LHYXNQWY.js.map +1 -0
- package/dist/chunk-LO2EW5YP.js +25 -0
- package/dist/chunk-LO2EW5YP.js.map +1 -0
- package/dist/chunk-LSQPUZK6.js +291 -0
- package/dist/chunk-LSQPUZK6.js.map +1 -0
- package/dist/chunk-N4Y5DI4X.js +82 -0
- package/dist/chunk-N4Y5DI4X.js.map +1 -0
- package/dist/chunk-NE6UHTOT.js +148 -0
- package/dist/chunk-NE6UHTOT.js.map +1 -0
- package/dist/chunk-NW47OJVN.js +138 -0
- package/dist/chunk-NW47OJVN.js.map +1 -0
- package/dist/chunk-O2YKCDR2.js +77 -0
- package/dist/chunk-O2YKCDR2.js.map +1 -0
- package/dist/chunk-PN6QKYE4.js +71 -0
- package/dist/chunk-PN6QKYE4.js.map +1 -0
- package/dist/chunk-RAOGZH3Z.js +188 -0
- package/dist/chunk-RAOGZH3Z.js.map +1 -0
- package/dist/chunk-RVSKX2RX.js +11 -0
- package/dist/chunk-RVSKX2RX.js.map +1 -0
- package/dist/chunk-RZVFA3N5.js +154 -0
- package/dist/chunk-RZVFA3N5.js.map +1 -0
- package/dist/chunk-SM7TASEW.js +135 -0
- package/dist/chunk-SM7TASEW.js.map +1 -0
- package/dist/chunk-U2PU4CWB.js +124 -0
- package/dist/chunk-U2PU4CWB.js.map +1 -0
- package/dist/chunk-UCEVCLQ7.js +5 -0
- package/dist/chunk-UCEVCLQ7.js.map +1 -0
- package/dist/chunk-UYSZOEOA.js +32 -0
- package/dist/chunk-UYSZOEOA.js.map +1 -0
- package/dist/chunk-V55K2CS5.js +126 -0
- package/dist/chunk-V55K2CS5.js.map +1 -0
- package/dist/chunk-VTQVGNVC.js +346 -0
- package/dist/chunk-VTQVGNVC.js.map +1 -0
- package/dist/chunk-W23P7SI4.js +239 -0
- package/dist/chunk-W23P7SI4.js.map +1 -0
- package/dist/chunk-W7D3STJH.js +279 -0
- package/dist/chunk-W7D3STJH.js.map +1 -0
- package/dist/chunk-WEEHXWM3.js +172 -0
- package/dist/chunk-WEEHXWM3.js.map +1 -0
- package/dist/chunk-WTEVTCIL.js +128 -0
- package/dist/chunk-WTEVTCIL.js.map +1 -0
- package/dist/chunk-XE6IWISA.js +137 -0
- package/dist/chunk-XE6IWISA.js.map +1 -0
- package/dist/elements/nymbal-add-to-cart.d.ts +14 -0
- package/dist/elements/nymbal-add-to-cart.js +10 -0
- package/dist/elements/nymbal-add-to-cart.js.map +1 -0
- package/dist/elements/nymbal-address-book.d.ts +30 -0
- package/dist/elements/nymbal-address-book.js +13 -0
- package/dist/elements/nymbal-address-book.js.map +1 -0
- package/dist/elements/nymbal-address-form.d.ts +29 -0
- package/dist/elements/nymbal-address-form.js +12 -0
- package/dist/elements/nymbal-address-form.js.map +1 -0
- package/dist/elements/nymbal-breadcrumb.d.ts +15 -0
- package/dist/elements/nymbal-breadcrumb.js +11 -0
- package/dist/elements/nymbal-breadcrumb.js.map +1 -0
- package/dist/elements/nymbal-cart-drawer.d.ts +24 -0
- package/dist/elements/nymbal-cart-drawer.js +11 -0
- package/dist/elements/nymbal-cart-drawer.js.map +1 -0
- package/dist/elements/nymbal-cart-page.d.ts +20 -0
- package/dist/elements/nymbal-cart-page.js +14 -0
- package/dist/elements/nymbal-cart-page.js.map +1 -0
- package/dist/elements/nymbal-category-nav.d.ts +15 -0
- package/dist/elements/nymbal-category-nav.js +11 -0
- package/dist/elements/nymbal-category-nav.js.map +1 -0
- package/dist/elements/nymbal-checkout.d.ts +77 -0
- package/dist/elements/nymbal-checkout.js +15 -0
- package/dist/elements/nymbal-checkout.js.map +1 -0
- package/dist/elements/nymbal-image-gallery.d.ts +19 -0
- package/dist/elements/nymbal-image-gallery.js +10 -0
- package/dist/elements/nymbal-image-gallery.js.map +1 -0
- package/dist/elements/nymbal-login-form.d.ts +17 -0
- package/dist/elements/nymbal-login-form.js +12 -0
- package/dist/elements/nymbal-login-form.js.map +1 -0
- package/dist/elements/nymbal-mini-cart.d.ts +17 -0
- package/dist/elements/nymbal-mini-cart.js +10 -0
- package/dist/elements/nymbal-mini-cart.js.map +1 -0
- package/dist/elements/nymbal-order-summary.d.ts +20 -0
- package/dist/elements/nymbal-order-summary.js +11 -0
- package/dist/elements/nymbal-order-summary.js.map +1 -0
- package/dist/elements/nymbal-payment-element.d.ts +4 -0
- package/dist/elements/nymbal-payment-element.js +10 -0
- package/dist/elements/nymbal-payment-element.js.map +1 -0
- package/dist/elements/nymbal-product-card.d.ts +4 -0
- package/dist/elements/nymbal-product-card.js +12 -0
- package/dist/elements/nymbal-product-card.js.map +1 -0
- package/dist/elements/nymbal-product-filter.d.ts +32 -0
- package/dist/elements/nymbal-product-filter.js +10 -0
- package/dist/elements/nymbal-product-filter.js.map +1 -0
- package/dist/elements/nymbal-product-grid.d.ts +24 -0
- package/dist/elements/nymbal-product-grid.js +14 -0
- package/dist/elements/nymbal-product-grid.js.map +1 -0
- package/dist/elements/nymbal-product-price.d.ts +18 -0
- package/dist/elements/nymbal-product-price.js +12 -0
- package/dist/elements/nymbal-product-price.js.map +1 -0
- package/dist/elements/nymbal-profile-form.d.ts +19 -0
- package/dist/elements/nymbal-profile-form.js +12 -0
- package/dist/elements/nymbal-profile-form.js.map +1 -0
- package/dist/elements/nymbal-quantity-selector.d.ts +20 -0
- package/dist/elements/nymbal-quantity-selector.js +10 -0
- package/dist/elements/nymbal-quantity-selector.js.map +1 -0
- package/dist/elements/nymbal-register-form.d.ts +17 -0
- package/dist/elements/nymbal-register-form.js +12 -0
- package/dist/elements/nymbal-register-form.js.map +1 -0
- package/dist/elements/nymbal-review-form.d.ts +17 -0
- package/dist/elements/nymbal-review-form.js +12 -0
- package/dist/elements/nymbal-review-form.js.map +1 -0
- package/dist/elements/nymbal-review-list.d.ts +14 -0
- package/dist/elements/nymbal-review-list.js +10 -0
- package/dist/elements/nymbal-review-list.js.map +1 -0
- package/dist/elements/nymbal-search-bar.d.ts +19 -0
- package/dist/elements/nymbal-search-bar.js +10 -0
- package/dist/elements/nymbal-search-bar.js.map +1 -0
- package/dist/elements/nymbal-search-results.d.ts +29 -0
- package/dist/elements/nymbal-search-results.js +14 -0
- package/dist/elements/nymbal-search-results.js.map +1 -0
- package/dist/elements/nymbal-site-footer.d.ts +15 -0
- package/dist/elements/nymbal-site-footer.js +11 -0
- package/dist/elements/nymbal-site-footer.js.map +1 -0
- package/dist/elements/nymbal-site-header.d.ts +19 -0
- package/dist/elements/nymbal-site-header.js +11 -0
- package/dist/elements/nymbal-site-header.js.map +1 -0
- package/dist/elements/nymbal-sort-dropdown.d.ts +28 -0
- package/dist/elements/nymbal-sort-dropdown.js +10 -0
- package/dist/elements/nymbal-sort-dropdown.js.map +1 -0
- package/dist/elements/nymbal-stock-badge.d.ts +16 -0
- package/dist/elements/nymbal-stock-badge.js +11 -0
- package/dist/elements/nymbal-stock-badge.js.map +1 -0
- package/dist/elements/nymbal-toast.d.ts +18 -0
- package/dist/elements/nymbal-toast.js +10 -0
- package/dist/elements/nymbal-toast.js.map +1 -0
- package/dist/elements/nymbal-variant-selector.d.ts +16 -0
- package/dist/elements/nymbal-variant-selector.js +10 -0
- package/dist/elements/nymbal-variant-selector.js.map +1 -0
- package/dist/form-element-C-qo3gpD.d.ts +25 -0
- package/dist/hydrate-support.d.ts +2 -0
- package/dist/hydrate-support.js +3 -0
- package/dist/hydrate-support.js.map +1 -0
- package/dist/index.d.ts +37 -105
- package/dist/index.js +130 -1098
- package/dist/index.js.map +1 -1
- package/dist/nymbal-payment-element-Bt8EF42T.d.ts +51 -0
- package/dist/nymbal-product-card-C8HkE_rG.d.ts +47 -0
- package/dist/register-components.d.ts +46 -0
- package/dist/register-components.js +48 -0
- package/dist/register-components.js.map +1 -0
- package/dist/selected-variant-Dym0shlJ.d.ts +23 -0
- package/dist/ssr/index.d.ts +30 -0
- package/dist/ssr/index.js +143 -0
- package/dist/ssr/index.js.map +1 -0
- package/package.json +33 -6
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import '@lit-labs/ssr-client/lit-element-hydrate-support.js'
|
|
2
|
+
import {
|
|
3
|
+
NymbalElement,
|
|
4
|
+
baseStyles,
|
|
5
|
+
propertiesOf
|
|
6
|
+
} from "./chunk-EYDMJNNK.js";
|
|
7
|
+
|
|
8
|
+
// src/components/sort-dropdown.ts
|
|
9
|
+
import { css, html } from "lit";
|
|
10
|
+
import { DEFAULT_PRODUCT_SORT, PRODUCT_SORT_LABELS } from "@nymbalhq/types";
|
|
11
|
+
function parseKeys(value) {
|
|
12
|
+
if (!value) return [];
|
|
13
|
+
try {
|
|
14
|
+
const parsed = JSON.parse(value);
|
|
15
|
+
return Array.isArray(parsed) ? parsed.filter((k) => typeof k === "string") : [];
|
|
16
|
+
} catch (err) {
|
|
17
|
+
console.error("[nymbal-sort-dropdown] failed to parse sort-keys attribute", err);
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
var SCHEMA = {
|
|
22
|
+
tag: "nymbal-sort-dropdown",
|
|
23
|
+
reactName: "SortDropdown",
|
|
24
|
+
attributes: [
|
|
25
|
+
{ name: "sort-keys", prop: "sortKeys", type: "json" },
|
|
26
|
+
{ name: "value", prop: "value", type: "string" },
|
|
27
|
+
{ name: "default-sort", prop: "defaultSort", type: "string" }
|
|
28
|
+
],
|
|
29
|
+
events: [{ name: "nymbal:sort:changed", prop: "onSortChange", detail: "{ sort: string }" }],
|
|
30
|
+
parts: ["select"],
|
|
31
|
+
slots: [],
|
|
32
|
+
children: false
|
|
33
|
+
};
|
|
34
|
+
var NymbalSortDropdown = class extends NymbalElement {
|
|
35
|
+
static schema = SCHEMA;
|
|
36
|
+
static properties = propertiesOf(SCHEMA);
|
|
37
|
+
static styles = [
|
|
38
|
+
baseStyles,
|
|
39
|
+
css`
|
|
40
|
+
[part~='select'] {
|
|
41
|
+
display: block;
|
|
42
|
+
width: 100%;
|
|
43
|
+
min-width: 10rem;
|
|
44
|
+
padding: 0.5rem 0.75rem;
|
|
45
|
+
font: inherit;
|
|
46
|
+
font-size: var(--nymbal-font-size-sm, 0.875rem);
|
|
47
|
+
line-height: 1.5;
|
|
48
|
+
color: var(--nymbal-color-text, #18181b);
|
|
49
|
+
background-color: var(--nymbal-color-surface, #ffffff);
|
|
50
|
+
border: 1px solid var(--nymbal-color-border, #e4e4e7);
|
|
51
|
+
border-radius: var(--nymbal-radius-md, 0.375rem);
|
|
52
|
+
cursor: pointer;
|
|
53
|
+
appearance: none;
|
|
54
|
+
transition:
|
|
55
|
+
border-color var(--nymbal-transition-fast, 150ms ease),
|
|
56
|
+
box-shadow var(--nymbal-transition-fast, 150ms ease);
|
|
57
|
+
}
|
|
58
|
+
[part~='select']:focus {
|
|
59
|
+
outline: none;
|
|
60
|
+
border-color: var(--nymbal-color-accent, #2563eb);
|
|
61
|
+
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.15);
|
|
62
|
+
}
|
|
63
|
+
`
|
|
64
|
+
];
|
|
65
|
+
_wired = null;
|
|
66
|
+
/** A slotted `<select>`; a server's element has no light DOM to read, so it renders its own. */
|
|
67
|
+
slottedSelect() {
|
|
68
|
+
const children = this.children;
|
|
69
|
+
if (children === void 0) return null;
|
|
70
|
+
return [...children].find((c) => c instanceof HTMLSelectElement) ?? null;
|
|
71
|
+
}
|
|
72
|
+
select() {
|
|
73
|
+
return this.slottedSelect() ?? this.renderRoot?.querySelector('select[part~="select"]') ?? null;
|
|
74
|
+
}
|
|
75
|
+
/** The keys to offer, and the one shown: `value` when offered, else `default-sort`, else the first. */
|
|
76
|
+
choice() {
|
|
77
|
+
const keys = parseKeys(this.getAttribute("sort-keys"));
|
|
78
|
+
const wanted = this.getAttribute("value") ?? "";
|
|
79
|
+
const fallback = this.getAttribute("default-sort") ?? DEFAULT_PRODUCT_SORT;
|
|
80
|
+
const selected = keys.includes(wanted) ? wanted : keys.includes(fallback) ? fallback : keys[0];
|
|
81
|
+
return { keys, selected };
|
|
82
|
+
}
|
|
83
|
+
label(key) {
|
|
84
|
+
return PRODUCT_SORT_LABELS[key] ?? key;
|
|
85
|
+
}
|
|
86
|
+
/** Its own select renders its options, so a server render offers them too. */
|
|
87
|
+
render() {
|
|
88
|
+
if (this.slottedSelect() !== null) return html`<slot></slot>`;
|
|
89
|
+
const { keys, selected } = this.choice();
|
|
90
|
+
return html`<select part="select" data-testid="sort-dropdown" aria-label="Sort products">
|
|
91
|
+
${keys.map(
|
|
92
|
+
(key) => html`<option value=${key} ?selected=${key === selected}>${this.label(key)}</option>`
|
|
93
|
+
)}
|
|
94
|
+
</select>`;
|
|
95
|
+
}
|
|
96
|
+
updated() {
|
|
97
|
+
const select = this.select();
|
|
98
|
+
if (select === null) return;
|
|
99
|
+
if (this._wired !== select) {
|
|
100
|
+
select.addEventListener("change", this.handleChange);
|
|
101
|
+
this._wired = select;
|
|
102
|
+
}
|
|
103
|
+
const { keys, selected } = this.choice();
|
|
104
|
+
if (select === this.slottedSelect()) {
|
|
105
|
+
select.replaceChildren(
|
|
106
|
+
...keys.map((key) => {
|
|
107
|
+
const option = document.createElement("option");
|
|
108
|
+
option.value = key;
|
|
109
|
+
option.textContent = this.label(key);
|
|
110
|
+
return option;
|
|
111
|
+
})
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
if (selected !== void 0) select.value = selected;
|
|
115
|
+
}
|
|
116
|
+
handleChange = (e) => {
|
|
117
|
+
this.dispatchEvent(
|
|
118
|
+
new CustomEvent("nymbal:sort:changed", {
|
|
119
|
+
bubbles: true,
|
|
120
|
+
composed: true,
|
|
121
|
+
detail: { sort: e.target.value }
|
|
122
|
+
})
|
|
123
|
+
);
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export {
|
|
128
|
+
NymbalSortDropdown
|
|
129
|
+
};
|
|
130
|
+
//# sourceMappingURL=chunk-EBMA7JGV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/sort-dropdown.ts"],"sourcesContent":["import { css, html } from 'lit'\nimport { DEFAULT_PRODUCT_SORT, PRODUCT_SORT_LABELS, type ProductSortKey } from '@nymbalhq/types'\nimport { baseStyles, NymbalElement, propertiesOf, type ElementSchema } from '../base-element.js'\n\nfunction parseKeys(value: string | null): string[] {\n if (!value) return []\n try {\n const parsed: unknown = JSON.parse(value)\n return Array.isArray(parsed) ? parsed.filter((k): k is string => typeof k === 'string') : []\n } catch (err) {\n console.error('[nymbal-sort-dropdown] failed to parse sort-keys attribute', err)\n return []\n }\n}\n\nconst SCHEMA: ElementSchema = {\n tag: 'nymbal-sort-dropdown',\n reactName: 'SortDropdown',\n attributes: [\n { name: 'sort-keys', prop: 'sortKeys', type: 'json' },\n { name: 'value', prop: 'value', type: 'string' },\n { name: 'default-sort', prop: 'defaultSort', type: 'string' },\n ],\n events: [{ name: 'nymbal:sort:changed', prop: 'onSortChange', detail: '{ sort: string }' }],\n parts: ['select'],\n slots: [],\n children: false,\n}\n\n/**\n * <nymbal-sort-dropdown sort-keys='[…]' value default-sort>: one option per sort key the store reports, in its order,\n * labelled from PRODUCT_SORT_LABELS. A `value` the keys lack shows `default-sort`. Choosing an option emits\n * `nymbal:sort:changed` with `{ sort }`. A `<select>` child (a server render's, so the sort works before JS) is slotted\n * and is the one it fills; with none, it renders its own `part=\"select\"`.\n */\nexport class NymbalSortDropdown extends NymbalElement {\n static override schema = SCHEMA\n static override properties = propertiesOf(SCHEMA)\n\n static override styles = [\n baseStyles,\n css`\n [part~='select'] {\n display: block;\n width: 100%;\n min-width: 10rem;\n padding: 0.5rem 0.75rem;\n font: inherit;\n font-size: var(--nymbal-font-size-sm, 0.875rem);\n line-height: 1.5;\n color: var(--nymbal-color-text, #18181b);\n background-color: var(--nymbal-color-surface, #ffffff);\n border: 1px solid var(--nymbal-color-border, #e4e4e7);\n border-radius: var(--nymbal-radius-md, 0.375rem);\n cursor: pointer;\n appearance: none;\n transition:\n border-color var(--nymbal-transition-fast, 150ms ease),\n box-shadow var(--nymbal-transition-fast, 150ms ease);\n }\n [part~='select']:focus {\n outline: none;\n border-color: var(--nymbal-color-accent, #2563eb);\n box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.15);\n }\n `,\n ]\n\n private _wired: HTMLSelectElement | null = null\n\n /** A slotted `<select>`; a server's element has no light DOM to read, so it renders its own. */\n private slottedSelect(): HTMLSelectElement | null {\n const children = this.children as HTMLCollection | undefined\n if (children === undefined) return null\n return [...children].find((c): c is HTMLSelectElement => c instanceof HTMLSelectElement) ?? null\n }\n\n private select(): HTMLSelectElement | null {\n return this.slottedSelect() ?? this.renderRoot?.querySelector('select[part~=\"select\"]') ?? null\n }\n\n /** The keys to offer, and the one shown: `value` when offered, else `default-sort`, else the first. */\n private choice(): { keys: string[]; selected: string | undefined } {\n const keys = parseKeys(this.getAttribute('sort-keys'))\n const wanted = this.getAttribute('value') ?? ''\n const fallback = this.getAttribute('default-sort') ?? DEFAULT_PRODUCT_SORT\n const selected = keys.includes(wanted) ? wanted : keys.includes(fallback) ? fallback : keys[0]\n return { keys, selected }\n }\n\n private label(key: string): string {\n return PRODUCT_SORT_LABELS[key as ProductSortKey] ?? key\n }\n\n /** Its own select renders its options, so a server render offers them too. */\n protected override render() {\n if (this.slottedSelect() !== null) return html`<slot></slot>`\n const { keys, selected } = this.choice()\n return html`<select part=\"select\" data-testid=\"sort-dropdown\" aria-label=\"Sort products\">\n ${keys.map(\n (key) => html`<option value=${key} ?selected=${key === selected}>${this.label(key)}</option>`,\n )}\n </select>`\n }\n\n protected override updated(): void {\n const select = this.select()\n if (select === null) return\n if (this._wired !== select) {\n select.addEventListener('change', this.handleChange)\n this._wired = select\n }\n\n const { keys, selected } = this.choice()\n if (select === this.slottedSelect()) {\n select.replaceChildren(\n ...keys.map((key) => {\n const option = document.createElement('option')\n option.value = key\n option.textContent = this.label(key)\n return option\n }),\n )\n }\n if (selected !== undefined) select.value = selected\n }\n\n private handleChange = (e: Event): void => {\n this.dispatchEvent(\n new CustomEvent('nymbal:sort:changed', {\n bubbles: true,\n composed: true,\n detail: { sort: (e.target as HTMLSelectElement).value },\n }),\n )\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import '@lit-labs/ssr-client/lit-element-hydrate-support.js'
|
|
2
|
+
|
|
3
|
+
// src/registry.ts
|
|
4
|
+
var CLIENT_KEY = /* @__PURE__ */ Symbol.for("nymbal.client");
|
|
5
|
+
function isStore(value) {
|
|
6
|
+
return typeof value === "object" && value !== null && typeof value.subscribe === "function" && typeof value.getState === "function";
|
|
7
|
+
}
|
|
8
|
+
var settledStores = /* @__PURE__ */ new WeakSet();
|
|
9
|
+
function registerClient(client) {
|
|
10
|
+
;
|
|
11
|
+
globalThis[CLIENT_KEY] = client;
|
|
12
|
+
for (const store of Object.values(client)) {
|
|
13
|
+
if (!isStore(store)) continue;
|
|
14
|
+
store.subscribe(() => {
|
|
15
|
+
if (store.getState().loading !== true) settledStores.add(store);
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function hasSettled(store) {
|
|
20
|
+
return settledStores.has(store);
|
|
21
|
+
}
|
|
22
|
+
function hasClient() {
|
|
23
|
+
return globalThis[CLIENT_KEY] !== void 0;
|
|
24
|
+
}
|
|
25
|
+
function getClient() {
|
|
26
|
+
const client = globalThis[CLIENT_KEY];
|
|
27
|
+
if (!client) {
|
|
28
|
+
throw new Error("NymbalClient not registered. Call registerClient() before using components.");
|
|
29
|
+
}
|
|
30
|
+
return client;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// src/define.ts
|
|
34
|
+
function define(...elements) {
|
|
35
|
+
for (const element of elements) {
|
|
36
|
+
element.beforeDefine?.();
|
|
37
|
+
if (element.uses !== void 0) define(...element.uses);
|
|
38
|
+
if (!customElements.get(element.schema.tag)) {
|
|
39
|
+
customElements.define(element.schema.tag, element);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/base-element.ts
|
|
45
|
+
import {
|
|
46
|
+
css,
|
|
47
|
+
LitElement
|
|
48
|
+
} from "lit";
|
|
49
|
+
var TYPES = {
|
|
50
|
+
string: String,
|
|
51
|
+
number: Number,
|
|
52
|
+
boolean: Boolean,
|
|
53
|
+
// JSON attributes are parsed where they are read, so a malformed value is reported by the element that needs it.
|
|
54
|
+
json: String
|
|
55
|
+
};
|
|
56
|
+
function propertiesOf(schema) {
|
|
57
|
+
return Object.fromEntries(
|
|
58
|
+
schema.attributes.map((a) => [a.prop, { attribute: a.name, type: TYPES[a.type] }])
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
var baseStyles = css`
|
|
62
|
+
*,
|
|
63
|
+
*::before,
|
|
64
|
+
*::after {
|
|
65
|
+
box-sizing: border-box;
|
|
66
|
+
}
|
|
67
|
+
`;
|
|
68
|
+
function parsedJson(element, name, value) {
|
|
69
|
+
if (value === void 0 || value === null || value === "") return void 0;
|
|
70
|
+
if (typeof value !== "string") return value;
|
|
71
|
+
try {
|
|
72
|
+
return JSON.parse(value);
|
|
73
|
+
} catch (err) {
|
|
74
|
+
console.error(`[${element.localName}] failed to parse the ${name} attribute`, err);
|
|
75
|
+
return void 0;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
var NymbalElement = class extends LitElement {
|
|
79
|
+
/** Defines this element, and those it renders, unless its tag is already defined (see `define`). */
|
|
80
|
+
static define() {
|
|
81
|
+
define(this);
|
|
82
|
+
}
|
|
83
|
+
_unsubs = [];
|
|
84
|
+
/** Set while the element's first render takes over a server's shadow root (see `readsStores`). */
|
|
85
|
+
_serverRender = false;
|
|
86
|
+
get client() {
|
|
87
|
+
return getClient();
|
|
88
|
+
}
|
|
89
|
+
/** Whether a client is registered: never on a server. Without one the element subscribes to no store. */
|
|
90
|
+
get hasClient() {
|
|
91
|
+
return hasClient();
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Whether this render reads the client's stores. Not on a server, and not in the first render of an element taking
|
|
95
|
+
* over a server's shadow root: hydration needs the server's render (a store still loading would change it), so that
|
|
96
|
+
* render is the server's, and the element renders again from its stores straight after.
|
|
97
|
+
*/
|
|
98
|
+
get readsStores() {
|
|
99
|
+
return this.hasClient && !this._serverRender;
|
|
100
|
+
}
|
|
101
|
+
/** The SDK stores the element renders from; it re-renders on each change while connected. */
|
|
102
|
+
stores() {
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* A store `stores()` named has settled (an update that is not `loading`): the properties a server rendered this
|
|
107
|
+
* element from give way to it. An update while loading leaves them showing, so a count does not flip to 0 and back.
|
|
108
|
+
*/
|
|
109
|
+
storeSettled(_store) {
|
|
110
|
+
}
|
|
111
|
+
connectedCallback() {
|
|
112
|
+
super.connectedCallback();
|
|
113
|
+
if (!this.hasClient) return;
|
|
114
|
+
for (const store of this.stores()) {
|
|
115
|
+
this._unsubs.push(
|
|
116
|
+
store.subscribe(() => {
|
|
117
|
+
if (store.getState().loading !== true) this.storeSettled(store);
|
|
118
|
+
this.requestUpdate();
|
|
119
|
+
})
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
if (!this._serverRender) this.takeSettledStores();
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* A store that settled before this element connected sends it no update, so the properties a server gave (a page
|
|
126
|
+
* restored from a router's cache after the cart changed elsewhere) give way to it here.
|
|
127
|
+
*/
|
|
128
|
+
takeSettledStores() {
|
|
129
|
+
for (const store of this.stores()) if (hasSettled(store)) this.storeSettled(store);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* React inserts a soft navigation's server HTML with `innerHTML`, which leaves the declarative shadow root an unparsed
|
|
133
|
+
* template: it becomes this element's shadow root here, before Lit looks for one, so the element hydrates it rather
|
|
134
|
+
* than rendering a second. A parsed declarative shadow root (a full load) is already `shadowRoot`.
|
|
135
|
+
*/
|
|
136
|
+
createRenderRoot() {
|
|
137
|
+
if (this.shadowRoot === null) {
|
|
138
|
+
const template = [...this.children].find(
|
|
139
|
+
(c) => c instanceof HTMLTemplateElement && c.getAttribute("shadowrootmode") === "open"
|
|
140
|
+
);
|
|
141
|
+
if (template !== void 0) {
|
|
142
|
+
const root = this.attachShadow(this.constructor.shadowRootOptions);
|
|
143
|
+
const markup = template.innerHTML;
|
|
144
|
+
if (typeof root.setHTMLUnsafe === "function") root.setHTMLUnsafe(markup);
|
|
145
|
+
else root.innerHTML = markup;
|
|
146
|
+
template.remove();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
this._serverRender = this.shadowRoot !== null;
|
|
150
|
+
return super.createRenderRoot();
|
|
151
|
+
}
|
|
152
|
+
update(changed) {
|
|
153
|
+
super.update(changed);
|
|
154
|
+
if (!this._serverRender) return;
|
|
155
|
+
this._serverRender = false;
|
|
156
|
+
if (this.hasClient) this.takeSettledStores();
|
|
157
|
+
this.willUpdate(changed);
|
|
158
|
+
super.update(changed);
|
|
159
|
+
}
|
|
160
|
+
disconnectedCallback() {
|
|
161
|
+
super.disconnectedCallback();
|
|
162
|
+
for (const fn of this._unsubs) fn();
|
|
163
|
+
this._unsubs = [];
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
export {
|
|
168
|
+
registerClient,
|
|
169
|
+
getClient,
|
|
170
|
+
define,
|
|
171
|
+
propertiesOf,
|
|
172
|
+
baseStyles,
|
|
173
|
+
parsedJson,
|
|
174
|
+
NymbalElement
|
|
175
|
+
};
|
|
176
|
+
//# sourceMappingURL=chunk-EYDMJNNK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/registry.ts","../src/define.ts","../src/base-element.ts"],"sourcesContent":["import type { NymbalClient } from '@nymbalhq/sdk'\n\nconst CLIENT_KEY = Symbol.for('nymbal.client')\n\ninterface Store {\n subscribe(fn: () => void): () => void\n getState(): unknown\n}\n\nfunction isStore(value: unknown): value is Store {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as Store).subscribe === 'function' &&\n typeof (value as Store).getState === 'function'\n )\n}\n\n/**\n * The registered clients' stores that have had a settled update (one that is not `loading`). A store starts at\n * `loading: false` before anything loaded it, so its state alone cannot tell an element connecting later whether the\n * store's data has replaced what a server rendered.\n */\nconst settledStores = new WeakSet<object>()\n\nexport function registerClient(client: NymbalClient): void {\n ;(globalThis as Record<symbol, unknown>)[CLIENT_KEY] = client\n for (const store of Object.values(client)) {\n if (!isStore(store)) continue\n store.subscribe(() => {\n if ((store.getState() as { loading?: unknown }).loading !== true) settledStores.add(store)\n })\n }\n}\n\n/** Whether a registered client's store has had a settled update since the client was registered. */\nexport function hasSettled(store: object): boolean {\n return settledStores.has(store)\n}\n\n/** Whether a client is registered: never on a server, which renders elements from their properties. */\nexport function hasClient(): boolean {\n return (globalThis as Record<symbol, unknown>)[CLIENT_KEY] !== undefined\n}\n\nexport function getClient(): NymbalClient {\n const client = (globalThis as Record<symbol, unknown>)[CLIENT_KEY] as NymbalClient | undefined\n if (!client) {\n throw new Error('NymbalClient not registered. Call registerClient() before using components.')\n }\n return client\n}\n","import type { ElementSchema } from './base-element.js'\n\n/** A Nymbal element class, as `define` takes it. */\nexport interface NymbalElementClass extends CustomElementConstructor {\n schema: ElementSchema\n /** The Nymbal elements it renders in its own shadow root, which must be defined for it to work. */\n uses?: readonly NymbalElementClass[]\n /** What the element needs registered before it runs; called whether or not its tag is still free. */\n beforeDefine?(): void\n}\n\n/**\n * Defines each element under its schema's tag, with the elements it renders, except a tag that is already defined:\n * an element the project defined first under a Nymbal tag wins.\n */\nexport function define(...elements: NymbalElementClass[]): void {\n for (const element of elements) {\n element.beforeDefine?.()\n if (element.uses !== undefined) define(...element.uses)\n if (!customElements.get(element.schema.tag)) {\n customElements.define(element.schema.tag, element)\n }\n }\n}\n","import './hydrate-support.js'\nimport {\n css,\n LitElement,\n type PropertyDeclaration,\n type PropertyDeclarations,\n type PropertyValues,\n} from 'lit'\nimport type { NymbalClient } from '@nymbalhq/sdk'\nimport { getClient, hasClient, hasSettled } from './registry.js'\nimport { define, type NymbalElementClass } from './define.js'\n\n/** How an attribute's value reads: the React wrapper types its prop from this, and JSON travels stringified. */\nexport type ElementAttributeType = 'string' | 'number' | 'boolean' | 'json'\n\n/**\n * An element's public interface, declared once on the class. Its Lit properties are derived from `attributes`, and the\n * React wrappers are generated from it, so neither can disagree with the element.\n */\nexport interface ElementSchema {\n tag: string\n /** The React wrapper's component name. */\n reactName: string\n /** `name` is the HTML attribute, `prop` the React prop and the Lit property. */\n attributes: Array<{ name: string; prop: string; type: ElementAttributeType }>\n /** `name` is the DOM event, `prop` the React handler, `detail` the TypeScript type of its detail. */\n events: Array<{ name: string; prop: string; detail: string }>\n /** Every `part` the element renders, for `::part()` styling. */\n parts: string[]\n /** Every named `slot` it renders, each with its default content as the fallback. */\n slots: string[]\n /** Whether the React wrapper passes children through. */\n children: boolean\n}\n\nconst TYPES: Record<ElementAttributeType, PropertyDeclaration['type']> = {\n string: String,\n number: Number,\n boolean: Boolean,\n // JSON attributes are parsed where they are read, so a malformed value is reported by the element that needs it.\n json: String,\n}\n\n/** The Lit property declarations for a schema's attributes. */\nexport function propertiesOf(schema: ElementSchema): PropertyDeclarations {\n return Object.fromEntries(\n schema.attributes.map((a) => [a.prop, { attribute: a.name, type: TYPES[a.type] }]),\n )\n}\n\n/** A page's reset does not reach a shadow root, so each element brings the one it relied on in the light DOM. */\nexport const baseStyles = css`\n *,\n *::before,\n *::after {\n box-sizing: border-box;\n }\n`\n\nexport interface Subscribable {\n subscribe(fn: () => void): () => void\n getState(): unknown\n}\n\n/** A JSON property's value: parsed when it came from the attribute, undefined when unset or malformed (reported). */\nexport function parsedJson<T>(element: Element, name: string, value: unknown): T | undefined {\n if (value === undefined || value === null || value === '') return undefined\n if (typeof value !== 'string') return value as T\n try {\n return JSON.parse(value) as T\n } catch (err) {\n console.error(`[${element.localName}] failed to parse the ${name} attribute`, err)\n return undefined\n }\n}\n\nexport abstract class NymbalElement extends LitElement {\n declare static schema: ElementSchema\n\n /** Defines this element, and those it renders, unless its tag is already defined (see `define`). */\n static define(this: NymbalElementClass): void {\n define(this)\n }\n\n private _unsubs: Array<() => void> = []\n /** Set while the element's first render takes over a server's shadow root (see `readsStores`). */\n private _serverRender = false\n\n protected get client(): NymbalClient {\n return getClient()\n }\n\n /** Whether a client is registered: never on a server. Without one the element subscribes to no store. */\n protected get hasClient(): boolean {\n return hasClient()\n }\n\n /**\n * Whether this render reads the client's stores. Not on a server, and not in the first render of an element taking\n * over a server's shadow root: hydration needs the server's render (a store still loading would change it), so that\n * render is the server's, and the element renders again from its stores straight after.\n */\n protected get readsStores(): boolean {\n return this.hasClient && !this._serverRender\n }\n\n /** The SDK stores the element renders from; it re-renders on each change while connected. */\n protected stores(): Subscribable[] {\n return []\n }\n\n /**\n * A store `stores()` named has settled (an update that is not `loading`): the properties a server rendered this\n * element from give way to it. An update while loading leaves them showing, so a count does not flip to 0 and back.\n */\n protected storeSettled(_store: Subscribable): void {}\n\n override connectedCallback(): void {\n super.connectedCallback()\n if (!this.hasClient) return\n for (const store of this.stores()) {\n this._unsubs.push(\n store.subscribe(() => {\n if ((store.getState() as { loading?: unknown }).loading !== true) this.storeSettled(store)\n this.requestUpdate()\n }),\n )\n }\n // A hydrating element keeps the server's properties for its first render (see `update`).\n if (!this._serverRender) this.takeSettledStores()\n }\n\n /**\n * A store that settled before this element connected sends it no update, so the properties a server gave (a page\n * restored from a router's cache after the cart changed elsewhere) give way to it here.\n */\n private takeSettledStores(): void {\n for (const store of this.stores()) if (hasSettled(store)) this.storeSettled(store)\n }\n\n /**\n * React inserts a soft navigation's server HTML with `innerHTML`, which leaves the declarative shadow root an unparsed\n * template: it becomes this element's shadow root here, before Lit looks for one, so the element hydrates it rather\n * than rendering a second. A parsed declarative shadow root (a full load) is already `shadowRoot`.\n */\n protected override createRenderRoot(): HTMLElement | DocumentFragment {\n if (this.shadowRoot === null) {\n const template = [...this.children].find(\n (c): c is HTMLTemplateElement =>\n c instanceof HTMLTemplateElement && c.getAttribute('shadowrootmode') === 'open',\n )\n if (template !== undefined) {\n const root = this.attachShadow((this.constructor as typeof LitElement).shadowRootOptions)\n const markup = template.innerHTML\n if (typeof root.setHTMLUnsafe === 'function') root.setHTMLUnsafe(markup)\n else root.innerHTML = markup\n template.remove()\n }\n }\n this._serverRender = this.shadowRoot !== null\n return super.createRenderRoot()\n }\n\n protected override update(changed: PropertyValues): void {\n super.update(changed)\n if (!this._serverRender) return\n // The render above hydrated the server's; this one, in the same update, is from the stores. A scheduled update\n // here would be a second update per element, which Lit warns of.\n this._serverRender = false\n if (this.hasClient) this.takeSettledStores()\n this.willUpdate(changed)\n super.update(changed)\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback()\n for (const fn of this._unsubs) fn()\n this._unsubs = []\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import '@lit-labs/ssr-client/lit-element-hydrate-support.js'
|
|
2
|
+
import {
|
|
3
|
+
navigateOnClick
|
|
4
|
+
} from "./chunk-LO2EW5YP.js";
|
|
5
|
+
import {
|
|
6
|
+
NymbalElement,
|
|
7
|
+
baseStyles,
|
|
8
|
+
parsedJson,
|
|
9
|
+
propertiesOf
|
|
10
|
+
} from "./chunk-EYDMJNNK.js";
|
|
11
|
+
|
|
12
|
+
// src/components/site-footer.ts
|
|
13
|
+
import { css, html, nothing } from "lit";
|
|
14
|
+
var SCHEMA = {
|
|
15
|
+
tag: "nymbal-site-footer",
|
|
16
|
+
reactName: "SiteFooter",
|
|
17
|
+
// `[{ heading, links: [{ label, href }], text? }]`; `year` for the copyright line (this year when absent).
|
|
18
|
+
attributes: [
|
|
19
|
+
{ name: "columns", prop: "columns", type: "json" },
|
|
20
|
+
{ name: "year", prop: "year", type: "number" }
|
|
21
|
+
],
|
|
22
|
+
events: [],
|
|
23
|
+
parts: ["footer", "column", "heading", "link", "legal"],
|
|
24
|
+
slots: [],
|
|
25
|
+
children: false
|
|
26
|
+
};
|
|
27
|
+
var NymbalSiteFooter = class extends NymbalElement {
|
|
28
|
+
static schema = SCHEMA;
|
|
29
|
+
static properties = propertiesOf(SCHEMA);
|
|
30
|
+
static styles = [
|
|
31
|
+
baseStyles,
|
|
32
|
+
css`
|
|
33
|
+
:host {
|
|
34
|
+
display: block;
|
|
35
|
+
margin-top: auto;
|
|
36
|
+
font-family: var(--nymbal-font-family, system-ui, -apple-system, sans-serif);
|
|
37
|
+
}
|
|
38
|
+
[part~='footer'] {
|
|
39
|
+
background-color: var(--nymbal-color-primary, #18181b);
|
|
40
|
+
color: var(--nymbal-color-text-inverse, #ffffff);
|
|
41
|
+
}
|
|
42
|
+
.inner {
|
|
43
|
+
max-width: 72rem;
|
|
44
|
+
margin-inline: auto;
|
|
45
|
+
padding: 3rem var(--nymbal-spacing-md, 1rem) var(--nymbal-spacing-lg, 1.5rem);
|
|
46
|
+
}
|
|
47
|
+
.columns {
|
|
48
|
+
display: grid;
|
|
49
|
+
grid-template-columns: 1fr;
|
|
50
|
+
gap: var(--nymbal-spacing-xl, 2rem);
|
|
51
|
+
margin-bottom: var(--nymbal-spacing-xl, 2rem);
|
|
52
|
+
}
|
|
53
|
+
@media (min-width: 641px) {
|
|
54
|
+
.inner {
|
|
55
|
+
padding-inline: var(--nymbal-spacing-lg, 1.5rem);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
@media (min-width: 641px) {
|
|
59
|
+
.columns {
|
|
60
|
+
grid-template-columns: repeat(3, 1fr);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
[part~='heading'] {
|
|
64
|
+
margin: 0 0 var(--nymbal-spacing-md, 1rem);
|
|
65
|
+
line-height: 1.2;
|
|
66
|
+
font-size: var(--nymbal-font-size-sm, 0.875rem);
|
|
67
|
+
font-weight: var(--nymbal-font-weight-bold, 700);
|
|
68
|
+
text-transform: uppercase;
|
|
69
|
+
letter-spacing: 0.05em;
|
|
70
|
+
color: var(--nymbal-color-text-inverse, #ffffff);
|
|
71
|
+
}
|
|
72
|
+
ul {
|
|
73
|
+
display: flex;
|
|
74
|
+
flex-direction: column;
|
|
75
|
+
gap: var(--nymbal-spacing-sm, 0.5rem);
|
|
76
|
+
margin: 0;
|
|
77
|
+
padding: 0;
|
|
78
|
+
list-style: none;
|
|
79
|
+
}
|
|
80
|
+
[part~='link'] {
|
|
81
|
+
font-size: var(--nymbal-font-size-sm, 0.875rem);
|
|
82
|
+
color: var(--nymbal-color-text-inverse, #ffffff);
|
|
83
|
+
opacity: 0.7;
|
|
84
|
+
text-decoration: none;
|
|
85
|
+
transition: opacity var(--nymbal-transition-fast, 150ms ease);
|
|
86
|
+
}
|
|
87
|
+
[part~='link']:hover {
|
|
88
|
+
opacity: 1;
|
|
89
|
+
}
|
|
90
|
+
.text {
|
|
91
|
+
max-width: 28ch;
|
|
92
|
+
margin: 0;
|
|
93
|
+
font-size: var(--nymbal-font-size-sm, 0.875rem);
|
|
94
|
+
line-height: 1.6;
|
|
95
|
+
opacity: 0.7;
|
|
96
|
+
}
|
|
97
|
+
[part~='legal'] {
|
|
98
|
+
margin: 0;
|
|
99
|
+
padding-top: var(--nymbal-spacing-lg, 1.5rem);
|
|
100
|
+
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
|
101
|
+
font-size: var(--nymbal-font-size-xs, 0.75rem);
|
|
102
|
+
opacity: 0.5;
|
|
103
|
+
}
|
|
104
|
+
`
|
|
105
|
+
];
|
|
106
|
+
render() {
|
|
107
|
+
const columns = parsedJson(this, "columns", this.columns);
|
|
108
|
+
const year = this.year === void 0 || Number.isNaN(this.year) ? (/* @__PURE__ */ new Date()).getFullYear() : this.year;
|
|
109
|
+
return html`
|
|
110
|
+
<footer part="footer" data-testid="site-footer">
|
|
111
|
+
<div class="inner">
|
|
112
|
+
<div class="columns">
|
|
113
|
+
${(Array.isArray(columns) ? columns : []).map(
|
|
114
|
+
(column) => html`
|
|
115
|
+
<div part="column">
|
|
116
|
+
<h2 part="heading">${column.heading}</h2>
|
|
117
|
+
${column.text === void 0 ? nothing : html`<p class="text">${column.text}</p>`}
|
|
118
|
+
${(column.links ?? []).length === 0 ? nothing : html`<ul>
|
|
119
|
+
${column.links.map(
|
|
120
|
+
(link) => html`<li>
|
|
121
|
+
<a part="link" href=${link.href} @click=${navigateOnClick}
|
|
122
|
+
>${link.label}</a
|
|
123
|
+
>
|
|
124
|
+
</li>`
|
|
125
|
+
)}
|
|
126
|
+
</ul>`}
|
|
127
|
+
</div>
|
|
128
|
+
`
|
|
129
|
+
)}
|
|
130
|
+
</div>
|
|
131
|
+
<p part="legal">© ${year} All rights reserved.</p>
|
|
132
|
+
</div>
|
|
133
|
+
</footer>
|
|
134
|
+
`;
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export {
|
|
139
|
+
NymbalSiteFooter
|
|
140
|
+
};
|
|
141
|
+
//# sourceMappingURL=chunk-FIF6GQ6U.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/site-footer.ts"],"sourcesContent":["import { css, html, nothing } from 'lit'\nimport {\n baseStyles,\n NymbalElement,\n parsedJson,\n propertiesOf,\n type ElementSchema,\n} from '../base-element.js'\nimport { navigateOnClick } from '../navigate.js'\n\nconst SCHEMA: ElementSchema = {\n tag: 'nymbal-site-footer',\n reactName: 'SiteFooter',\n // `[{ heading, links: [{ label, href }], text? }]`; `year` for the copyright line (this year when absent).\n attributes: [\n { name: 'columns', prop: 'columns', type: 'json' },\n { name: 'year', prop: 'year', type: 'number' },\n ],\n events: [],\n parts: ['footer', 'column', 'heading', 'link', 'legal'],\n slots: [],\n children: false,\n}\n\ninterface Column {\n heading: string\n links?: Array<{ label: string; href: string }>\n text?: string\n}\n\n/** The store's footer: columns of links (or a line of text) under their headings, and the copyright line. */\nexport class NymbalSiteFooter extends NymbalElement {\n static override schema = SCHEMA\n static override properties = propertiesOf(SCHEMA)\n static override styles = [\n baseStyles,\n css`\n :host {\n display: block;\n margin-top: auto;\n font-family: var(--nymbal-font-family, system-ui, -apple-system, sans-serif);\n }\n [part~='footer'] {\n background-color: var(--nymbal-color-primary, #18181b);\n color: var(--nymbal-color-text-inverse, #ffffff);\n }\n .inner {\n max-width: 72rem;\n margin-inline: auto;\n padding: 3rem var(--nymbal-spacing-md, 1rem) var(--nymbal-spacing-lg, 1.5rem);\n }\n .columns {\n display: grid;\n grid-template-columns: 1fr;\n gap: var(--nymbal-spacing-xl, 2rem);\n margin-bottom: var(--nymbal-spacing-xl, 2rem);\n }\n @media (min-width: 641px) {\n .inner {\n padding-inline: var(--nymbal-spacing-lg, 1.5rem);\n }\n }\n @media (min-width: 641px) {\n .columns {\n grid-template-columns: repeat(3, 1fr);\n }\n }\n [part~='heading'] {\n margin: 0 0 var(--nymbal-spacing-md, 1rem);\n line-height: 1.2;\n font-size: var(--nymbal-font-size-sm, 0.875rem);\n font-weight: var(--nymbal-font-weight-bold, 700);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n color: var(--nymbal-color-text-inverse, #ffffff);\n }\n ul {\n display: flex;\n flex-direction: column;\n gap: var(--nymbal-spacing-sm, 0.5rem);\n margin: 0;\n padding: 0;\n list-style: none;\n }\n [part~='link'] {\n font-size: var(--nymbal-font-size-sm, 0.875rem);\n color: var(--nymbal-color-text-inverse, #ffffff);\n opacity: 0.7;\n text-decoration: none;\n transition: opacity var(--nymbal-transition-fast, 150ms ease);\n }\n [part~='link']:hover {\n opacity: 1;\n }\n .text {\n max-width: 28ch;\n margin: 0;\n font-size: var(--nymbal-font-size-sm, 0.875rem);\n line-height: 1.6;\n opacity: 0.7;\n }\n [part~='legal'] {\n margin: 0;\n padding-top: var(--nymbal-spacing-lg, 1.5rem);\n border-top: 1px solid rgba(255, 255, 255, 0.1);\n font-size: var(--nymbal-font-size-xs, 0.75rem);\n opacity: 0.5;\n }\n `,\n ]\n\n declare columns: unknown\n declare year: number | undefined\n\n protected override render() {\n const columns = parsedJson<Column[]>(this, 'columns', this.columns)\n const year =\n this.year === undefined || Number.isNaN(this.year) ? new Date().getFullYear() : this.year\n return html`\n <footer part=\"footer\" data-testid=\"site-footer\">\n <div class=\"inner\">\n <div class=\"columns\">\n ${(Array.isArray(columns) ? columns : []).map(\n (column) => html`\n <div part=\"column\">\n <h2 part=\"heading\">${column.heading}</h2>\n ${column.text === undefined ? nothing : html`<p class=\"text\">${column.text}</p>`}\n ${(column.links ?? []).length === 0\n ? nothing\n : html`<ul>\n ${column.links!.map(\n (link) =>\n html`<li>\n <a part=\"link\" href=${link.href} @click=${navigateOnClick}\n >${link.label}</a\n >\n </li>`,\n )}\n </ul>`}\n </div>\n `,\n )}\n </div>\n <p part=\"legal\">© ${year} All rights reserved.</p>\n </div>\n </footer>\n `\n }\n}\n"],"mappings":";;;;;;;;;;;;AAAA,SAAS,KAAK,MAAM,eAAe;AAUnC,IAAM,SAAwB;AAAA,EAC5B,KAAK;AAAA,EACL,WAAW;AAAA;AAAA,EAEX,YAAY;AAAA,IACV,EAAE,MAAM,WAAW,MAAM,WAAW,MAAM,OAAO;AAAA,IACjD,EAAE,MAAM,QAAQ,MAAM,QAAQ,MAAM,SAAS;AAAA,EAC/C;AAAA,EACA,QAAQ,CAAC;AAAA,EACT,OAAO,CAAC,UAAU,UAAU,WAAW,QAAQ,OAAO;AAAA,EACtD,OAAO,CAAC;AAAA,EACR,UAAU;AACZ;AASO,IAAM,mBAAN,cAA+B,cAAc;AAAA,EAClD,OAAgB,SAAS;AAAA,EACzB,OAAgB,aAAa,aAAa,MAAM;AAAA,EAChD,OAAgB,SAAS;AAAA,IACvB;AAAA,IACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAyEF;AAAA,EAKmB,SAAS;AAC1B,UAAM,UAAU,WAAqB,MAAM,WAAW,KAAK,OAAO;AAClE,UAAM,OACJ,KAAK,SAAS,UAAa,OAAO,MAAM,KAAK,IAAI,KAAI,oBAAI,KAAK,GAAE,YAAY,IAAI,KAAK;AACvF,WAAO;AAAA;AAAA;AAAA;AAAA,eAII,MAAM,QAAQ,OAAO,IAAI,UAAU,CAAC,GAAG;AAAA,MACxC,CAAC,WAAW;AAAA;AAAA,uCAEa,OAAO,OAAO;AAAA,oBACjC,OAAO,SAAS,SAAY,UAAU,uBAAuB,OAAO,IAAI,MAAM;AAAA,qBAC7E,OAAO,SAAS,CAAC,GAAG,WAAW,IAC9B,UACA;AAAA,0BACI,OAAO,MAAO;AAAA,QACd,CAAC,SACC;AAAA,oDACwB,KAAK,IAAI,WAAW,eAAe;AAAA,mCACpD,KAAK,KAAK;AAAA;AAAA;AAAA,MAGrB,CAAC;AAAA,4BACG;AAAA;AAAA;AAAA,IAGhB,CAAC;AAAA;AAAA,mCAEsB,IAAI;AAAA;AAAA;AAAA;AAAA,EAIrC;AACF;","names":[]}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import '@lit-labs/ssr-client/lit-element-hydrate-support.js'
|
|
2
|
+
|
|
3
|
+
// src/view-styles.ts
|
|
4
|
+
import { css } from "lit";
|
|
5
|
+
var buttonStyles = css`
|
|
6
|
+
.btn {
|
|
7
|
+
display: inline-flex;
|
|
8
|
+
align-items: center;
|
|
9
|
+
justify-content: center;
|
|
10
|
+
gap: var(--nymbal-spacing-xs, 0.25rem);
|
|
11
|
+
padding: var(--nymbal-spacing-sm, 0.5rem) var(--nymbal-spacing-md, 1rem);
|
|
12
|
+
font-family: inherit;
|
|
13
|
+
font-size: var(--nymbal-font-size-sm, 0.875rem);
|
|
14
|
+
font-weight: var(--nymbal-font-weight-medium, 500);
|
|
15
|
+
line-height: 1.4;
|
|
16
|
+
text-decoration: none;
|
|
17
|
+
border: 1px solid transparent;
|
|
18
|
+
border-radius: var(--nymbal-radius-md, 0.5rem);
|
|
19
|
+
cursor: pointer;
|
|
20
|
+
transition:
|
|
21
|
+
background-color var(--nymbal-transition-fast, 150ms) ease,
|
|
22
|
+
border-color var(--nymbal-transition-fast, 150ms) ease;
|
|
23
|
+
}
|
|
24
|
+
.btn:focus-visible {
|
|
25
|
+
outline: 2px solid var(--nymbal-color-accent, #2563eb);
|
|
26
|
+
outline-offset: 2px;
|
|
27
|
+
}
|
|
28
|
+
.btn:disabled {
|
|
29
|
+
opacity: 0.5;
|
|
30
|
+
cursor: not-allowed;
|
|
31
|
+
}
|
|
32
|
+
.btn-primary {
|
|
33
|
+
background-color: var(--nymbal-color-primary, #18181b);
|
|
34
|
+
color: var(--nymbal-color-text-inverse, #ffffff);
|
|
35
|
+
border-color: var(--nymbal-color-primary, #18181b);
|
|
36
|
+
}
|
|
37
|
+
.btn-primary:hover {
|
|
38
|
+
background-color: var(--nymbal-color-primary-hover, #27272a);
|
|
39
|
+
}
|
|
40
|
+
.btn-secondary {
|
|
41
|
+
background-color: transparent;
|
|
42
|
+
color: var(--nymbal-color-primary, #18181b);
|
|
43
|
+
border-color: var(--nymbal-color-border, #e4e4e7);
|
|
44
|
+
}
|
|
45
|
+
.btn-secondary:hover {
|
|
46
|
+
background-color: var(--nymbal-color-border, #e4e4e7);
|
|
47
|
+
}
|
|
48
|
+
`;
|
|
49
|
+
var fieldStyles = css`
|
|
50
|
+
:host {
|
|
51
|
+
display: block;
|
|
52
|
+
font-family: var(--nymbal-font-family, system-ui, -apple-system, sans-serif);
|
|
53
|
+
color: var(--nymbal-color-text, #18181b);
|
|
54
|
+
}
|
|
55
|
+
[part~='field'] {
|
|
56
|
+
margin-bottom: var(--nymbal-spacing-md, 1rem);
|
|
57
|
+
}
|
|
58
|
+
[part~='label'] {
|
|
59
|
+
display: block;
|
|
60
|
+
font-size: var(--nymbal-font-size-sm, 0.875rem);
|
|
61
|
+
font-weight: var(--nymbal-font-weight-medium, 500);
|
|
62
|
+
margin-bottom: var(--nymbal-spacing-xs, 0.25rem);
|
|
63
|
+
}
|
|
64
|
+
[part~='input'] {
|
|
65
|
+
display: block;
|
|
66
|
+
width: 100%;
|
|
67
|
+
padding: var(--nymbal-spacing-sm, 0.5rem) var(--nymbal-spacing-md, 1rem);
|
|
68
|
+
font: inherit;
|
|
69
|
+
font-size: var(--nymbal-font-size-sm, 0.875rem);
|
|
70
|
+
line-height: 1.5;
|
|
71
|
+
color: var(--nymbal-color-text, #18181b);
|
|
72
|
+
background-color: var(--nymbal-color-surface, #ffffff);
|
|
73
|
+
border: 1px solid var(--nymbal-color-border, #e4e4e7);
|
|
74
|
+
border-radius: var(--nymbal-radius-md, 0.5rem);
|
|
75
|
+
}
|
|
76
|
+
[part~='input']:focus {
|
|
77
|
+
outline: none;
|
|
78
|
+
border-color: var(--nymbal-color-accent, #2563eb);
|
|
79
|
+
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.15);
|
|
80
|
+
}
|
|
81
|
+
[part~='input']:disabled {
|
|
82
|
+
opacity: 0.6;
|
|
83
|
+
cursor: not-allowed;
|
|
84
|
+
}
|
|
85
|
+
[part~='error'] {
|
|
86
|
+
margin-bottom: var(--nymbal-spacing-md, 1rem);
|
|
87
|
+
padding: var(--nymbal-spacing-sm, 0.5rem) var(--nymbal-spacing-md, 1rem);
|
|
88
|
+
font-size: var(--nymbal-font-size-sm, 0.875rem);
|
|
89
|
+
color: var(--nymbal-color-error, #dc2626);
|
|
90
|
+
background: #fef2f2;
|
|
91
|
+
border: 1px solid var(--nymbal-color-error, #dc2626);
|
|
92
|
+
border-radius: var(--nymbal-radius-md, 0.5rem);
|
|
93
|
+
}
|
|
94
|
+
[part~='message'] {
|
|
95
|
+
margin-top: var(--nymbal-spacing-md, 1rem);
|
|
96
|
+
font-size: var(--nymbal-font-size-sm, 0.875rem);
|
|
97
|
+
color: var(--nymbal-color-success, #16a34a);
|
|
98
|
+
}
|
|
99
|
+
[part~='submit'] {
|
|
100
|
+
width: 100%;
|
|
101
|
+
margin-top: var(--nymbal-spacing-sm, 0.5rem);
|
|
102
|
+
}
|
|
103
|
+
`;
|
|
104
|
+
|
|
105
|
+
export {
|
|
106
|
+
buttonStyles,
|
|
107
|
+
fieldStyles
|
|
108
|
+
};
|
|
109
|
+
//# sourceMappingURL=chunk-GHXTJ2CF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/view-styles.ts"],"sourcesContent":["import { css } from 'lit'\n\n/** The primary and secondary buttons the page views render, from the same tokens the templates' `.btn` reads. */\nexport const buttonStyles = css`\n .btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--nymbal-spacing-xs, 0.25rem);\n padding: var(--nymbal-spacing-sm, 0.5rem) var(--nymbal-spacing-md, 1rem);\n font-family: inherit;\n font-size: var(--nymbal-font-size-sm, 0.875rem);\n font-weight: var(--nymbal-font-weight-medium, 500);\n line-height: 1.4;\n text-decoration: none;\n border: 1px solid transparent;\n border-radius: var(--nymbal-radius-md, 0.5rem);\n cursor: pointer;\n transition:\n background-color var(--nymbal-transition-fast, 150ms) ease,\n border-color var(--nymbal-transition-fast, 150ms) ease;\n }\n .btn:focus-visible {\n outline: 2px solid var(--nymbal-color-accent, #2563eb);\n outline-offset: 2px;\n }\n .btn:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n .btn-primary {\n background-color: var(--nymbal-color-primary, #18181b);\n color: var(--nymbal-color-text-inverse, #ffffff);\n border-color: var(--nymbal-color-primary, #18181b);\n }\n .btn-primary:hover {\n background-color: var(--nymbal-color-primary-hover, #27272a);\n }\n .btn-secondary {\n background-color: transparent;\n color: var(--nymbal-color-primary, #18181b);\n border-color: var(--nymbal-color-border, #e4e4e7);\n }\n .btn-secondary:hover {\n background-color: var(--nymbal-color-border, #e4e4e7);\n }\n`\n\n/** Labelled fields, error and notice text, as the templates' `.form-group` styled them. */\nexport const fieldStyles = css`\n :host {\n display: block;\n font-family: var(--nymbal-font-family, system-ui, -apple-system, sans-serif);\n color: var(--nymbal-color-text, #18181b);\n }\n [part~='field'] {\n margin-bottom: var(--nymbal-spacing-md, 1rem);\n }\n [part~='label'] {\n display: block;\n font-size: var(--nymbal-font-size-sm, 0.875rem);\n font-weight: var(--nymbal-font-weight-medium, 500);\n margin-bottom: var(--nymbal-spacing-xs, 0.25rem);\n }\n [part~='input'] {\n display: block;\n width: 100%;\n padding: var(--nymbal-spacing-sm, 0.5rem) var(--nymbal-spacing-md, 1rem);\n font: inherit;\n font-size: var(--nymbal-font-size-sm, 0.875rem);\n line-height: 1.5;\n color: var(--nymbal-color-text, #18181b);\n background-color: var(--nymbal-color-surface, #ffffff);\n border: 1px solid var(--nymbal-color-border, #e4e4e7);\n border-radius: var(--nymbal-radius-md, 0.5rem);\n }\n [part~='input']:focus {\n outline: none;\n border-color: var(--nymbal-color-accent, #2563eb);\n box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.15);\n }\n [part~='input']:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n [part~='error'] {\n margin-bottom: var(--nymbal-spacing-md, 1rem);\n padding: var(--nymbal-spacing-sm, 0.5rem) var(--nymbal-spacing-md, 1rem);\n font-size: var(--nymbal-font-size-sm, 0.875rem);\n color: var(--nymbal-color-error, #dc2626);\n background: #fef2f2;\n border: 1px solid var(--nymbal-color-error, #dc2626);\n border-radius: var(--nymbal-radius-md, 0.5rem);\n }\n [part~='message'] {\n margin-top: var(--nymbal-spacing-md, 1rem);\n font-size: var(--nymbal-font-size-sm, 0.875rem);\n color: var(--nymbal-color-success, #16a34a);\n }\n [part~='submit'] {\n width: 100%;\n margin-top: var(--nymbal-spacing-sm, 0.5rem);\n }\n`\n"],"mappings":";;;AAAA,SAAS,WAAW;AAGb,IAAM,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA8CrB,IAAM,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;","names":[]}
|