@f-ewald/components 1.16.0 → 1.17.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.
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
- import { LitElement, css, html } from "lit";
7
+ import { LitElement, css, html, nothing } from "lit";
8
8
  import { customElement, property, state } from "lit/decorators.js";
9
9
  import { ref } from "lit/directives/ref.js";
10
10
  import { tokens } from "./tokens.js";
@@ -17,7 +17,11 @@ import { tokens } from "./tokens.js";
17
17
  * Renders a native `<input type="checkbox">` wrapped in a `<label>`, styled
18
18
  * via `:has()` on the wrapping label (matching `radio-pills`/`radio-cards`)
19
19
  * rather than styling the native input directly; the checkbox itself renders
20
- * at `1rem`, matching the existing radio-input convention.
20
+ * at `1rem`, matching the existing radio-input convention. An optional
21
+ * pre-rendered `icon` (matching `form-select`'s per-option icon convention)
22
+ * renders between the box and the label, inside the same clickable `<label>`
23
+ * — for a row that pairs a checkbox with an icon/swatch and needs the whole
24
+ * row, icon included, to stay one click target.
21
25
  *
22
26
  * @element ui-checkbox
23
27
  * @fires change - The checkbox was toggled by the user, in either direction;
@@ -38,6 +42,10 @@ let UiCheckbox = class UiCheckbox extends LitElement {
38
42
  this.name = "";
39
43
  /** Visible label text rendered next to the box. */
40
44
  this.label = "";
45
+ /** Pre-rendered icon template displayed between the box and the label, e.g. `iconPencil(14)` from this package's icon set. */
46
+ this.icon = null;
47
+ /** Square icon size in pixels — 14 (inline icon size) by default. */
48
+ this.iconSize = 14;
41
49
  this._formDisabled = false;
42
50
  this.#internals = this.attachInternals();
43
51
  this.#defaultChecked = false;
@@ -91,6 +99,16 @@ let UiCheckbox = class UiCheckbox extends LitElement {
91
99
  .required-mark {
92
100
  color: var(--ui-danger, #dc2626);
93
101
  }
102
+ .checkbox-icon {
103
+ display: inline-flex;
104
+ width: var(--checkbox-icon-size);
105
+ height: var(--checkbox-icon-size);
106
+ flex: 0 0 var(--checkbox-icon-size);
107
+ }
108
+ .checkbox-icon > svg {
109
+ width: 100%;
110
+ height: 100%;
111
+ }
94
112
  .sr-only {
95
113
  position: absolute;
96
114
  width: 1px;
@@ -189,6 +207,11 @@ let UiCheckbox = class UiCheckbox extends LitElement {
189
207
  ?required=${this.required}
190
208
  @change=${(e) => this.#onChange(e)}
191
209
  />
210
+ ${this.icon
211
+ ? html `<span class="checkbox-icon" style=${`--checkbox-icon-size: ${this.iconSize}px`} aria-hidden="true"
212
+ >${this.icon}</span
213
+ >`
214
+ : nothing}
192
215
  <span
193
216
  >${this.label}${this.required
194
217
  ? html `<span class="required-mark" aria-hidden="true"> *</span><span class="sr-only">
@@ -218,6 +241,12 @@ __decorate([
218
241
  __decorate([
219
242
  property()
220
243
  ], UiCheckbox.prototype, "label", void 0);
244
+ __decorate([
245
+ property({ attribute: false })
246
+ ], UiCheckbox.prototype, "icon", void 0);
247
+ __decorate([
248
+ property({ type: Number })
249
+ ], UiCheckbox.prototype, "iconSize", void 0);
221
250
  __decorate([
222
251
  state()
223
252
  ], UiCheckbox.prototype, "_formDisabled", void 0);
@@ -1 +1 @@
1
- {"version":3,"file":"ui-checkbox.js","sourceRoot":"","sources":["../src/ui-checkbox.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, css, html, type PropertyValues } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport { ref } from \"lit/directives/ref.js\";\nimport { tokens } from \"./tokens.js\";\n\n/**\n * A form-associated boolean checkbox, usable standalone or inside a native\n * `<form>`. Submits `name=on` when checked (matching native\n * `<input type=\"checkbox\">` semantics) and participates fully in form\n * `reset()`, ancestor `<fieldset disabled>`, and `required` validity.\n *\n * Renders a native `<input type=\"checkbox\">` wrapped in a `<label>`, styled\n * via `:has()` on the wrapping label (matching `radio-pills`/`radio-cards`)\n * rather than styling the native input directly; the checkbox itself renders\n * at `1rem`, matching the existing radio-input convention.\n *\n * @element ui-checkbox\n * @fires change - The checkbox was toggled by the user, in either direction;\n * detail: `{ checked }`. Programmatic `checked` assignments do not fire it.\n */\n@customElement(\"ui-checkbox\")\nexport class UiCheckbox extends LitElement {\n static formAssociated = true;\n\n static override styles = [\n tokens,\n css`\n :host {\n display: inline-block;\n }\n .checkbox {\n display: inline-flex;\n align-items: center;\n gap: 0.5rem;\n min-height: 2rem;\n cursor: pointer;\n font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\");\n font-size: var(--ui-font-size-sm, 0.75rem);\n line-height: var(--ui-line-height-tight, 1.25);\n color: var(--ui-text, #0f172a);\n }\n .checkbox input {\n width: 1rem;\n height: 1rem;\n margin: 0;\n accent-color: var(--ui-primary, #4f46e5);\n cursor: pointer;\n }\n .checkbox:has(input:focus-visible) {\n outline: none;\n }\n .checkbox:has(input:focus-visible) input {\n outline: none;\n box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));\n border-radius: var(--ui-radius-sm, 0.25rem);\n }\n .checkbox:has(input:disabled) {\n cursor: not-allowed;\n opacity: 0.6;\n }\n .checkbox:has(input:disabled) input {\n cursor: not-allowed;\n }\n .required-mark {\n color: var(--ui-danger, #dc2626);\n }\n .sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip: rect(0 0 0 0);\n clip-path: inset(50%);\n white-space: nowrap;\n border: 0;\n }\n @media (forced-colors: active) {\n .checkbox:has(input:focus-visible) input {\n outline: 2px solid CanvasText;\n outline-offset: 2px;\n box-shadow: none;\n }\n .checkbox:has(input:disabled) {\n color: GrayText;\n opacity: 1;\n }\n }\n `,\n ];\n\n /** Whether the box is checked. */\n @property({ type: Boolean }) checked = false;\n /** Visual \"partial selection\" state; cleared on the next user interaction. */\n @property({ type: Boolean }) indeterminate = false;\n /** Disables interaction; merged with an ancestor `<fieldset disabled>`. */\n @property({ type: Boolean }) disabled = false;\n /** Marks the control invalid via `ElementInternals` while unchecked. */\n @property({ type: Boolean }) required = false;\n /** Form field name; submitted as `name=on` only while checked. */\n @property() name = \"\";\n /** Visible label text rendered next to the box. */\n @property() label = \"\";\n\n @state() private _formDisabled = false;\n\n #internals = this.attachInternals();\n #defaultChecked = false;\n #inputEl: HTMLInputElement | null = null;\n\n /** Whether the host or an ancestor fieldset currently disables the control. */\n get #isDisabled(): boolean {\n return this.disabled || this._formDisabled;\n }\n\n protected override willUpdate(_changed: PropertyValues): void {\n if (!this.hasUpdated) this.#defaultChecked = this.checked;\n }\n\n protected override updated(changed: PropertyValues): void {\n if (changed.has(\"checked\") || changed.has(\"name\")) this.#syncFormValue();\n if (changed.has(\"checked\") || changed.has(\"required\") || changed.has(\"disabled\") || changed.has(\"_formDisabled\")) {\n this.#syncValidity();\n }\n if (changed.has(\"indeterminate\") && this.#inputEl) {\n this.#inputEl.indeterminate = this.indeterminate;\n }\n }\n\n /** Restores the checked state captured at first render, per the form contract. */\n formResetCallback(): void {\n this.checked = this.#defaultChecked;\n this.indeterminate = false;\n }\n\n /** Mirrors an ancestor fieldset's disabled state. */\n formDisabledCallback(disabled: boolean): void {\n this._formDisabled = disabled;\n }\n\n /** Restores the checked state from saved form state (bfcache/autofill). */\n formStateRestoreCallback(state: string | File | FormData | null): void {\n if (typeof state !== \"string\") return;\n this.checked = state === \"on\";\n }\n\n #syncFormValue(): void {\n if (!this.name || !this.checked) {\n this.#internals.setFormValue(null);\n return;\n }\n this.#internals.setFormValue(\"on\");\n }\n\n #syncValidity(): void {\n if (this.#isDisabled) {\n this.#internals.setValidity({});\n return;\n }\n if (this.required && !this.checked) {\n this.#internals.setValidity(\n { valueMissing: true },\n \"Please check this box to continue.\",\n this.#inputEl ?? undefined,\n );\n } else {\n this.#internals.setValidity({});\n }\n }\n\n #onChange(e: Event): void {\n const input = e.target as HTMLInputElement;\n this.checked = input.checked;\n this.indeterminate = false;\n this.dispatchEvent(\n new CustomEvent(\"change\", { detail: { checked: this.checked }, bubbles: true, composed: true }),\n );\n }\n\n #onInputRef = (el: Element | undefined): void => {\n this.#inputEl = (el as HTMLInputElement | undefined) ?? null;\n if (this.#inputEl) this.#inputEl.indeterminate = this.indeterminate;\n };\n\n override render() {\n return html`\n <label class=\"checkbox\">\n <input\n ${ref(this.#onInputRef)}\n type=\"checkbox\"\n .checked=${this.checked}\n ?disabled=${this.#isDisabled}\n ?required=${this.required}\n @change=${(e: Event) => this.#onChange(e)}\n />\n <span\n >${this.label}${this.required\n ? html`<span class=\"required-mark\" aria-hidden=\"true\"> *</span><span class=\"sr-only\">\n (required)</span\n >`\n : \"\"}</span\n >\n </label>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ui-checkbox\": UiCheckbox;\n }\n}\n"]}
1
+ {"version":3,"file":"ui-checkbox.js","sourceRoot":"","sources":["../src/ui-checkbox.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, css, html, nothing, type PropertyValues, type TemplateResult } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport { ref } from \"lit/directives/ref.js\";\nimport { tokens } from \"./tokens.js\";\n\n/**\n * A form-associated boolean checkbox, usable standalone or inside a native\n * `<form>`. Submits `name=on` when checked (matching native\n * `<input type=\"checkbox\">` semantics) and participates fully in form\n * `reset()`, ancestor `<fieldset disabled>`, and `required` validity.\n *\n * Renders a native `<input type=\"checkbox\">` wrapped in a `<label>`, styled\n * via `:has()` on the wrapping label (matching `radio-pills`/`radio-cards`)\n * rather than styling the native input directly; the checkbox itself renders\n * at `1rem`, matching the existing radio-input convention. An optional\n * pre-rendered `icon` (matching `form-select`'s per-option icon convention)\n * renders between the box and the label, inside the same clickable `<label>`\n * — for a row that pairs a checkbox with an icon/swatch and needs the whole\n * row, icon included, to stay one click target.\n *\n * @element ui-checkbox\n * @fires change - The checkbox was toggled by the user, in either direction;\n * detail: `{ checked }`. Programmatic `checked` assignments do not fire it.\n */\n@customElement(\"ui-checkbox\")\nexport class UiCheckbox extends LitElement {\n static formAssociated = true;\n\n static override styles = [\n tokens,\n css`\n :host {\n display: inline-block;\n }\n .checkbox {\n display: inline-flex;\n align-items: center;\n gap: 0.5rem;\n min-height: 2rem;\n cursor: pointer;\n font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\");\n font-size: var(--ui-font-size-sm, 0.75rem);\n line-height: var(--ui-line-height-tight, 1.25);\n color: var(--ui-text, #0f172a);\n }\n .checkbox input {\n width: 1rem;\n height: 1rem;\n margin: 0;\n accent-color: var(--ui-primary, #4f46e5);\n cursor: pointer;\n }\n .checkbox:has(input:focus-visible) {\n outline: none;\n }\n .checkbox:has(input:focus-visible) input {\n outline: none;\n box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));\n border-radius: var(--ui-radius-sm, 0.25rem);\n }\n .checkbox:has(input:disabled) {\n cursor: not-allowed;\n opacity: 0.6;\n }\n .checkbox:has(input:disabled) input {\n cursor: not-allowed;\n }\n .required-mark {\n color: var(--ui-danger, #dc2626);\n }\n .checkbox-icon {\n display: inline-flex;\n width: var(--checkbox-icon-size);\n height: var(--checkbox-icon-size);\n flex: 0 0 var(--checkbox-icon-size);\n }\n .checkbox-icon > svg {\n width: 100%;\n height: 100%;\n }\n .sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip: rect(0 0 0 0);\n clip-path: inset(50%);\n white-space: nowrap;\n border: 0;\n }\n @media (forced-colors: active) {\n .checkbox:has(input:focus-visible) input {\n outline: 2px solid CanvasText;\n outline-offset: 2px;\n box-shadow: none;\n }\n .checkbox:has(input:disabled) {\n color: GrayText;\n opacity: 1;\n }\n }\n `,\n ];\n\n /** Whether the box is checked. */\n @property({ type: Boolean }) checked = false;\n /** Visual \"partial selection\" state; cleared on the next user interaction. */\n @property({ type: Boolean }) indeterminate = false;\n /** Disables interaction; merged with an ancestor `<fieldset disabled>`. */\n @property({ type: Boolean }) disabled = false;\n /** Marks the control invalid via `ElementInternals` while unchecked. */\n @property({ type: Boolean }) required = false;\n /** Form field name; submitted as `name=on` only while checked. */\n @property() name = \"\";\n /** Visible label text rendered next to the box. */\n @property() label = \"\";\n /** Pre-rendered icon template displayed between the box and the label, e.g. `iconPencil(14)` from this package's icon set. */\n @property({ attribute: false }) icon: TemplateResult | null = null;\n /** Square icon size in pixels — 14 (inline icon size) by default. */\n @property({ type: Number }) iconSize = 14;\n\n @state() private _formDisabled = false;\n\n #internals = this.attachInternals();\n #defaultChecked = false;\n #inputEl: HTMLInputElement | null = null;\n\n /** Whether the host or an ancestor fieldset currently disables the control. */\n get #isDisabled(): boolean {\n return this.disabled || this._formDisabled;\n }\n\n protected override willUpdate(_changed: PropertyValues): void {\n if (!this.hasUpdated) this.#defaultChecked = this.checked;\n }\n\n protected override updated(changed: PropertyValues): void {\n if (changed.has(\"checked\") || changed.has(\"name\")) this.#syncFormValue();\n if (changed.has(\"checked\") || changed.has(\"required\") || changed.has(\"disabled\") || changed.has(\"_formDisabled\")) {\n this.#syncValidity();\n }\n if (changed.has(\"indeterminate\") && this.#inputEl) {\n this.#inputEl.indeterminate = this.indeterminate;\n }\n }\n\n /** Restores the checked state captured at first render, per the form contract. */\n formResetCallback(): void {\n this.checked = this.#defaultChecked;\n this.indeterminate = false;\n }\n\n /** Mirrors an ancestor fieldset's disabled state. */\n formDisabledCallback(disabled: boolean): void {\n this._formDisabled = disabled;\n }\n\n /** Restores the checked state from saved form state (bfcache/autofill). */\n formStateRestoreCallback(state: string | File | FormData | null): void {\n if (typeof state !== \"string\") return;\n this.checked = state === \"on\";\n }\n\n #syncFormValue(): void {\n if (!this.name || !this.checked) {\n this.#internals.setFormValue(null);\n return;\n }\n this.#internals.setFormValue(\"on\");\n }\n\n #syncValidity(): void {\n if (this.#isDisabled) {\n this.#internals.setValidity({});\n return;\n }\n if (this.required && !this.checked) {\n this.#internals.setValidity(\n { valueMissing: true },\n \"Please check this box to continue.\",\n this.#inputEl ?? undefined,\n );\n } else {\n this.#internals.setValidity({});\n }\n }\n\n #onChange(e: Event): void {\n const input = e.target as HTMLInputElement;\n this.checked = input.checked;\n this.indeterminate = false;\n this.dispatchEvent(\n new CustomEvent(\"change\", { detail: { checked: this.checked }, bubbles: true, composed: true }),\n );\n }\n\n #onInputRef = (el: Element | undefined): void => {\n this.#inputEl = (el as HTMLInputElement | undefined) ?? null;\n if (this.#inputEl) this.#inputEl.indeterminate = this.indeterminate;\n };\n\n override render() {\n return html`\n <label class=\"checkbox\">\n <input\n ${ref(this.#onInputRef)}\n type=\"checkbox\"\n .checked=${this.checked}\n ?disabled=${this.#isDisabled}\n ?required=${this.required}\n @change=${(e: Event) => this.#onChange(e)}\n />\n ${this.icon\n ? html`<span class=\"checkbox-icon\" style=${`--checkbox-icon-size: ${this.iconSize}px`} aria-hidden=\"true\"\n >${this.icon}</span\n >`\n : nothing}\n <span\n >${this.label}${this.required\n ? html`<span class=\"required-mark\" aria-hidden=\"true\"> *</span><span class=\"sr-only\">\n (required)</span\n >`\n : \"\"}</span\n >\n </label>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"ui-checkbox\": UiCheckbox;\n }\n}\n"]}
@@ -0,0 +1,62 @@
1
+ # `<mapbox-map>`
2
+
3
+ A thin, generic wrapper around a `mapboxgl.Map` — construction, access
4
+ token, style loading/switching, and container resizing only. It carries no
5
+ domain logic: no layer registry, no click-handler system, no markers or
6
+ popups. A consumer registers its own sources/layers/handlers against the
7
+ `mapboxgl.Map` instance handed back on `map-ready`, the same instance
8
+ `mapbox-map` continues to own (this component never calls `map.remove()`
9
+ except on disconnect, so a consumer's own registrations survive style
10
+ reloads exactly as they would using `mapboxgl.Map` directly).
11
+
12
+ Deliberately does not construct the map until `styleUrl` is a non-empty
13
+ string — if a consumer knows the desired style only after an async
14
+ lookup (e.g. a saved user preference), delay setting `styleUrl` rather
15
+ than setting a default and swapping it later, which would visibly flash
16
+ the wrong basemap before the real one loads.
17
+
18
+ ## Install
19
+
20
+ ```js
21
+ import "@f-ewald/components/mapbox-map.js";
22
+ ```
23
+
24
+ ## Usage
25
+
26
+ ```html
27
+ <mapbox-map
28
+ access-token="pk.your-token"
29
+ style-url="mapbox://styles/mapbox/light-v11"
30
+ ></mapbox-map>
31
+ <script type="module">
32
+ document.querySelector("mapbox-map").addEventListener("map-ready", (e) => {
33
+ const map = e.detail.map; // the underlying mapboxgl.Map
34
+ map.addSource("mine", { type: "geojson", data: "/mine.geojson" });
35
+ map.addLayer({ id: "mine", type: "circle", source: "mine", paint: { "circle-color": "#4f46e5" } });
36
+ });
37
+ </script>
38
+ ```
39
+
40
+ ## Attributes / properties
41
+
42
+ | Property | Attribute | Type | Default | Description |
43
+ | --- | --- | --- | --- | --- |
44
+ | `accessToken` | `access-token` | `string` | `""` | Mapbox access token. Required before the map can be constructed. |
45
+ | `styleUrl` | `style-url` | `string` | `""` | Style URL (e.g. `mapbox://styles/mapbox/light-v11`). The map is not constructed until this is a non-empty string — see the class doc. |
46
+ | `center` | _(JS property only)_ | `[number, number]` | `[0, 0]` | Initial center as `[lng, lat]`. Only read at construction time. |
47
+ | `zoom` | `zoom` | `number` | `0` | Initial zoom level. Only read at construction time. |
48
+
49
+ ## Events
50
+
51
+ | Event | Description |
52
+ | --- | --- |
53
+ | `map-style-reloaded` | A subsequent `styleUrl` change finished loading its new style (sources/layers registered by a consumer via the `map-ready` instance do not survive a style change and must be re-registered — same behavior as calling `map.setStyle()` directly); detail: `{ map: mapboxgl.Map }`. |
54
+ | `map-ready` | The map (and, if `styleUrl` changed before the initial load finished, its final requested style) has finished loading; detail: `{ map: mapboxgl.Map }`. |
55
+
56
+ ## Slots
57
+
58
+ _None._
59
+
60
+ ## CSS custom properties
61
+
62
+ _None._
@@ -0,0 +1,59 @@
1
+ # `<range-slider>`
2
+
3
+ A form-associated numeric range slider, usable standalone or inside a
4
+ native `<form>`. Wraps a native `<input type="range">` (kept for its free
5
+ keyboard, drag, and screen-reader support) restyled to match this
6
+ package's track/fill visual language (`stat-meter`, `percent-bar-chart`)
7
+ instead of the browser-default appearance. Purely a value control — no
8
+ built-in label; wrap in `form-field` for a labeled field, or render a
9
+ value readout next to it (see the playground example), matching
10
+ `autocomplete-input`/`form-select`.
11
+
12
+ ## Install
13
+
14
+ ```js
15
+ import "@f-ewald/components/range-slider.js";
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ```html
21
+ <range-slider min="100" max="5000" step="50" value="1000"></range-slider>
22
+ <script type="module">
23
+ document.querySelector("range-slider").addEventListener("input", (e) => {
24
+ console.log(e.detail.value);
25
+ });
26
+ </script>
27
+ ```
28
+
29
+ ## Attributes / properties
30
+
31
+ | Property | Attribute | Type | Default | Description |
32
+ | --- | --- | --- | --- | --- |
33
+ | `min` | `min` | `number` | `0` | Minimum value. |
34
+ | `max` | `max` | `number` | `100` | Maximum value. |
35
+ | `step` | `step` | `number` | `1` | Step increment. |
36
+ | `value` | `value` | `number` | `0` | Current value. |
37
+ | `disabled` | `disabled` | `boolean` | `false` | Disables interaction; merged with an ancestor `<fieldset disabled>`. |
38
+ | `name` | `name` | `string` | `""` | Form field name. |
39
+
40
+ ## Events
41
+
42
+ | Event | Description |
43
+ | --- | --- |
44
+ | `input` | Fires continuously while dragging/typing; detail: `{ value }`. |
45
+ | `change` | Fires once the value is committed (drag released, arrow key released); detail: `{ value }`. |
46
+
47
+ ## Slots
48
+
49
+ _None._
50
+
51
+ ## CSS custom properties
52
+
53
+ | Custom property |
54
+ | --- |
55
+ | `--ui-focus-ring` |
56
+ | `--ui-primary` |
57
+ | `--ui-surface` |
58
+ | `--ui-surface-muted` |
59
+ | `--ui-text-muted` |
@@ -8,7 +8,11 @@ A form-associated boolean checkbox, usable standalone or inside a native
8
8
  Renders a native `<input type="checkbox">` wrapped in a `<label>`, styled
9
9
  via `:has()` on the wrapping label (matching `radio-pills`/`radio-cards`)
10
10
  rather than styling the native input directly; the checkbox itself renders
11
- at `1rem`, matching the existing radio-input convention.
11
+ at `1rem`, matching the existing radio-input convention. An optional
12
+ pre-rendered `icon` (matching `form-select`'s per-option icon convention)
13
+ renders between the box and the label, inside the same clickable `<label>`
14
+ — for a row that pairs a checkbox with an icon/swatch and needs the whole
15
+ row, icon included, to stay one click target.
12
16
 
13
17
  ## Install
14
18
 
@@ -21,6 +25,8 @@ import "@f-ewald/components/ui-checkbox.js";
21
25
  ```html
22
26
  <ui-checkbox label="Subscribe to updates"></ui-checkbox>
23
27
  <ui-checkbox name="terms" label="I agree to the terms" required></ui-checkbox>
28
+ <!-- .icon is set programmatically (a pre-rendered TemplateResult), not an attribute -->
29
+ <ui-checkbox label="Show list view"></ui-checkbox>
24
30
  ```
25
31
 
26
32
  ## Attributes / properties
@@ -33,6 +39,8 @@ import "@f-ewald/components/ui-checkbox.js";
33
39
  | `required` | `required` | `boolean` | `false` | Marks the control invalid via `ElementInternals` while unchecked. |
34
40
  | `name` | `name` | `string` | `""` | Form field name; submitted as `name=on` only while checked. |
35
41
  | `label` | `label` | `string` | `""` | Visible label text rendered next to the box. |
42
+ | `icon` | _(JS property only)_ | `TemplateResult | null` | `null` | Pre-rendered icon template displayed between the box and the label, e.g. `iconPencil(14)` from this package's icon set. |
43
+ | `iconSize` | `iconSize` | `number` | `14` | Square icon size in pixels — 14 (inline icon size) by default. |
36
44
 
37
45
  ## Events
38
46
 
package/llms.txt CHANGED
@@ -1082,6 +1082,44 @@ Example:
1082
1082
  <map-pin color="#22c55e" size="26" highlighted>🏠</map-pin>
1083
1083
  ```
1084
1084
 
1085
+ ## <mapbox-map>
1086
+
1087
+ A thin, generic wrapper around a `mapboxgl.Map` — construction, access
1088
+ token, style loading/switching, and container resizing only. It carries no
1089
+ domain logic: no layer registry, no click-handler system, no markers or
1090
+ popups. A consumer registers its own sources/layers/handlers against the
1091
+ `mapboxgl.Map` instance handed back on `map-ready`, the same instance
1092
+ `mapbox-map` continues to own (this component never calls `map.remove()`
1093
+ except on disconnect, so a consumer's own registrations survive style
1094
+ reloads exactly as they would using `mapboxgl.Map` directly).
1095
+
1096
+ Deliberately does not construct the map until `styleUrl` is a non-empty
1097
+ string — if a consumer knows the desired style only after an async
1098
+ lookup (e.g. a saved user preference), delay setting `styleUrl` rather
1099
+ than setting a default and swapping it later, which would visibly flash
1100
+ the wrong basemap before the real one loads.
1101
+
1102
+ Import: `import "@f-ewald/components/mapbox-map.js";`
1103
+
1104
+ Properties: `accessToken` (attribute `access-token`) : string, default ""; `styleUrl` (attribute `style-url`) : string, default ""; `center` (JS property only) : [number, number], default [0, 0]; `zoom` (attribute `zoom`) : number, default 0
1105
+ Events: `map-style-reloaded`, `map-ready`
1106
+ CSS custom properties: none
1107
+
1108
+ Example:
1109
+ ```html
1110
+ <mapbox-map
1111
+ access-token="pk.your-token"
1112
+ style-url="mapbox://styles/mapbox/light-v11"
1113
+ ></mapbox-map>
1114
+ <script type="module">
1115
+ document.querySelector("mapbox-map").addEventListener("map-ready", (e) => {
1116
+ const map = e.detail.map; // the underlying mapboxgl.Map
1117
+ map.addSource("mine", { type: "geojson", data: "/mine.geojson" });
1118
+ map.addLayer({ id: "mine", type: "circle", source: "mine", paint: { "circle-color": "#4f46e5" } });
1119
+ });
1120
+ </script>
1121
+ ```
1122
+
1085
1123
  ## <markdown-view>
1086
1124
 
1087
1125
  Renders a markdown string as sanitized, styled HTML — headings, lists,
@@ -1418,6 +1456,33 @@ Example:
1418
1456
  </script>
1419
1457
  ```
1420
1458
 
1459
+ ## <range-slider>
1460
+
1461
+ A form-associated numeric range slider, usable standalone or inside a
1462
+ native `<form>`. Wraps a native `<input type="range">` (kept for its free
1463
+ keyboard, drag, and screen-reader support) restyled to match this
1464
+ package's track/fill visual language (`stat-meter`, `percent-bar-chart`)
1465
+ instead of the browser-default appearance. Purely a value control — no
1466
+ built-in label; wrap in `form-field` for a labeled field, or render a
1467
+ value readout next to it (see the playground example), matching
1468
+ `autocomplete-input`/`form-select`.
1469
+
1470
+ Import: `import "@f-ewald/components/range-slider.js";`
1471
+
1472
+ Properties: `min` (attribute `min`) : number, default 0; `max` (attribute `max`) : number, default 100; `step` (attribute `step`) : number, default 1; `value` (attribute `value`) : number, default 0; `disabled` (attribute `disabled`) : boolean, default false; `name` (attribute `name`) : string, default ""
1473
+ Events: `input`, `change`
1474
+ CSS custom properties: `--ui-focus-ring`, `--ui-primary`, `--ui-surface`, `--ui-surface-muted`, `--ui-text-muted`
1475
+
1476
+ Example:
1477
+ ```html
1478
+ <range-slider min="100" max="5000" step="50" value="1000"></range-slider>
1479
+ <script type="module">
1480
+ document.querySelector("range-slider").addEventListener("input", (e) => {
1481
+ console.log(e.detail.value);
1482
+ });
1483
+ </script>
1484
+ ```
1485
+
1421
1486
  ## <relative-time>
1422
1487
 
1423
1488
  Inline relative-time display (e.g. "3 hours ago"). Accepts either a
@@ -1828,11 +1893,15 @@ A form-associated boolean checkbox, usable standalone or inside a native
1828
1893
  Renders a native `<input type="checkbox">` wrapped in a `<label>`, styled
1829
1894
  via `:has()` on the wrapping label (matching `radio-pills`/`radio-cards`)
1830
1895
  rather than styling the native input directly; the checkbox itself renders
1831
- at `1rem`, matching the existing radio-input convention.
1896
+ at `1rem`, matching the existing radio-input convention. An optional
1897
+ pre-rendered `icon` (matching `form-select`'s per-option icon convention)
1898
+ renders between the box and the label, inside the same clickable `<label>`
1899
+ — for a row that pairs a checkbox with an icon/swatch and needs the whole
1900
+ row, icon included, to stay one click target.
1832
1901
 
1833
1902
  Import: `import "@f-ewald/components/ui-checkbox.js";`
1834
1903
 
1835
- Properties: `checked` (attribute `checked`) : boolean, default false; `indeterminate` (attribute `indeterminate`) : boolean, default false; `disabled` (attribute `disabled`) : boolean, default false; `required` (attribute `required`) : boolean, default false; `name` (attribute `name`) : string, default ""; `label` (attribute `label`) : string, default ""
1904
+ Properties: `checked` (attribute `checked`) : boolean, default false; `indeterminate` (attribute `indeterminate`) : boolean, default false; `disabled` (attribute `disabled`) : boolean, default false; `required` (attribute `required`) : boolean, default false; `name` (attribute `name`) : string, default ""; `label` (attribute `label`) : string, default ""; `icon` (JS property only) : TemplateResult | null, default null; `iconSize` (attribute `iconSize`) : number, default 14
1836
1905
  Events: `change`
1837
1906
  CSS custom properties: `--ui-danger`, `--ui-focus-ring`, `--ui-font`, `--ui-font-size-sm`, `--ui-line-height-tight`, `--ui-primary`, `--ui-radius-sm`, `--ui-text`
1838
1907
 
@@ -1840,6 +1909,8 @@ Example:
1840
1909
  ```html
1841
1910
  <ui-checkbox label="Subscribe to updates"></ui-checkbox>
1842
1911
  <ui-checkbox name="terms" label="I agree to the terms" required></ui-checkbox>
1912
+ <!-- .icon is set programmatically (a pre-rendered TemplateResult), not an attribute -->
1913
+ <ui-checkbox label="Show list view"></ui-checkbox>
1843
1914
  ```
1844
1915
 
1845
1916
  ## <user-avatar>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@f-ewald/components",
3
3
  "private": false,
4
- "version": "1.16.0",
4
+ "version": "1.17.0",
5
5
  "description": "A collection of universally usable web components for various tasks.",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",
@@ -65,6 +65,7 @@
65
65
  "d3-shape": "^3.2.0",
66
66
  "dompurify": "^3.4.12",
67
67
  "lit": "^3.3.3",
68
+ "mapbox-gl": "^3.9.0",
68
69
  "marked": "^18.0.7",
69
70
  "zod": "^4.4.3"
70
71
  },
@@ -79,6 +80,7 @@
79
80
  "@types/d3-array": "^3.2.2",
80
81
  "@types/d3-scale": "^4.0.9",
81
82
  "@types/d3-shape": "^3.1.8",
83
+ "@types/mapbox-gl": "^3.4.1",
82
84
  "@types/node": "^26.1.1",
83
85
  "heroicons": "^2.2.0",
84
86
  "tailwindcss": "^4.3.3",