@f-ewald/components 1.8.0 → 1.10.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/README.md CHANGED
@@ -50,9 +50,11 @@ import "@f-ewald/components/roman-numeral.js";
50
50
  | `<app-shell>` | [API reference](https://f-ewald.github.io/components/docs/app-shell.html) |
51
51
  | `<app-sidebar>` | [API reference](https://f-ewald.github.io/components/docs/app-sidebar.html) |
52
52
  | `<autocomplete-input>` | [API reference](https://f-ewald.github.io/components/docs/autocomplete-input.html) |
53
+ | `<button-group>` | [API reference](https://f-ewald.github.io/components/docs/button-group.html) |
53
54
  | `<calendar-entry>` | [API reference](https://f-ewald.github.io/components/docs/calendar-entry.html) |
54
55
  | `<calendar-month>` | [API reference](https://f-ewald.github.io/components/docs/calendar-month.html) |
55
56
  | `<calendar-year>` | [API reference](https://f-ewald.github.io/components/docs/calendar-year.html) |
57
+ | `<card-grid>` | [API reference](https://f-ewald.github.io/components/docs/card-grid.html) |
56
58
  | `<chat-message>` | [API reference](https://f-ewald.github.io/components/docs/chat-message.html) |
57
59
  | `<confirm-dialog>` | [API reference](https://f-ewald.github.io/components/docs/confirm-dialog.html) |
58
60
  | `<content-divider>` | [API reference](https://f-ewald.github.io/components/docs/content-divider.html) |
@@ -72,6 +74,7 @@ import "@f-ewald/components/roman-numeral.js";
72
74
  | `<kanban-card>` | [API reference](https://f-ewald.github.io/components/docs/kanban-card.html) |
73
75
  | `<kanban-column>` | [API reference](https://f-ewald.github.io/components/docs/kanban-column.html) |
74
76
  | `<kbd-hint>` | [API reference](https://f-ewald.github.io/components/docs/kbd-hint.html) |
77
+ | `<link-card>` | [API reference](https://f-ewald.github.io/components/docs/link-card.html) |
75
78
  | `<live-timer>` | [API reference](https://f-ewald.github.io/components/docs/live-timer.html) |
76
79
  | `<map-circle>` | [API reference](https://f-ewald.github.io/components/docs/map-circle.html) |
77
80
  | `<map-pin>` | [API reference](https://f-ewald.github.io/components/docs/map-pin.html) |
@@ -1877,6 +1877,121 @@
1877
1877
  }
1878
1878
  ]
1879
1879
  },
1880
+ {
1881
+ "kind": "javascript-module",
1882
+ "path": "src/button-group.ts",
1883
+ "declarations": [
1884
+ {
1885
+ "kind": "class",
1886
+ "description": "Single-select segmented control — a strip of buttons joined into one\nshared-border shape, for a small, persistent set of mutually exclusive\nchoices (a view switcher, a theme picker) where the *currently selected*\noption should read as visually \"pressed,\" not just checked. For many\nshort, individually pill-shaped choices, use `radio-pills` instead. Wraps\nnative radio inputs for keyboard/a11y and fires `change` rather than\nrelying on form submission.",
1887
+ "name": "ButtonGroup",
1888
+ "members": [
1889
+ {
1890
+ "kind": "field",
1891
+ "name": "options",
1892
+ "type": {
1893
+ "text": "ButtonGroupOption[]"
1894
+ },
1895
+ "default": "[]",
1896
+ "description": "Options to render, one segment each."
1897
+ },
1898
+ {
1899
+ "kind": "field",
1900
+ "name": "value",
1901
+ "type": {
1902
+ "text": "string"
1903
+ },
1904
+ "default": "\"\"",
1905
+ "description": "Currently selected value.",
1906
+ "attribute": "value"
1907
+ },
1908
+ {
1909
+ "kind": "field",
1910
+ "name": "disabled",
1911
+ "type": {
1912
+ "text": "boolean"
1913
+ },
1914
+ "default": "false",
1915
+ "description": "Disables every native radio in the group.",
1916
+ "attribute": "disabled"
1917
+ },
1918
+ {
1919
+ "kind": "field",
1920
+ "name": "#name",
1921
+ "privacy": "private",
1922
+ "readonly": true,
1923
+ "default": "`button-group-${++instanceCount}`"
1924
+ },
1925
+ {
1926
+ "kind": "method",
1927
+ "name": "_onChange",
1928
+ "privacy": "private",
1929
+ "parameters": [
1930
+ {
1931
+ "name": "value",
1932
+ "type": {
1933
+ "text": "string"
1934
+ }
1935
+ }
1936
+ ]
1937
+ }
1938
+ ],
1939
+ "events": [
1940
+ {
1941
+ "name": "change",
1942
+ "type": {
1943
+ "text": "CustomEvent"
1944
+ },
1945
+ "description": "A segment was selected; detail: { value }."
1946
+ }
1947
+ ],
1948
+ "attributes": [
1949
+ {
1950
+ "name": "value",
1951
+ "type": {
1952
+ "text": "string"
1953
+ },
1954
+ "default": "\"\"",
1955
+ "description": "Currently selected value.",
1956
+ "fieldName": "value"
1957
+ },
1958
+ {
1959
+ "name": "disabled",
1960
+ "type": {
1961
+ "text": "boolean"
1962
+ },
1963
+ "default": "false",
1964
+ "description": "Disables every native radio in the group.",
1965
+ "fieldName": "disabled"
1966
+ }
1967
+ ],
1968
+ "superclass": {
1969
+ "name": "LitElement",
1970
+ "package": "lit"
1971
+ },
1972
+ "tagName": "button-group",
1973
+ "customElement": true
1974
+ }
1975
+ ],
1976
+ "exports": [
1977
+ {
1978
+ "kind": "js",
1979
+ "name": "ButtonGroup",
1980
+ "declaration": {
1981
+ "name": "ButtonGroup",
1982
+ "module": "src/button-group.ts"
1983
+ }
1984
+ },
1985
+ {
1986
+ "kind": "custom-element-definition",
1987
+ "name": "button-group",
1988
+ "declaration": {
1989
+ "name": "ButtonGroup",
1990
+ "module": "src/button-group.ts"
1991
+ }
1992
+ }
1993
+ ]
1994
+ },
1880
1995
  {
1881
1996
  "kind": "javascript-module",
1882
1997
  "path": "src/calendar-entry.ts",
@@ -2545,6 +2660,48 @@
2545
2660
  }
2546
2661
  ]
2547
2662
  },
2663
+ {
2664
+ "kind": "javascript-module",
2665
+ "path": "src/card-grid.ts",
2666
+ "declarations": [
2667
+ {
2668
+ "kind": "class",
2669
+ "description": "A responsive auto-filling grid shell for `link-card` (or any card-shaped\ncontent) — each slotted child becomes a grid item, wrapping to a new row\nonce the container is too narrow for another `15rem` column.",
2670
+ "name": "CardGrid",
2671
+ "slots": [
2672
+ {
2673
+ "description": "`link-card` elements (or other card-shaped content).",
2674
+ "name": ""
2675
+ }
2676
+ ],
2677
+ "members": [],
2678
+ "superclass": {
2679
+ "name": "LitElement",
2680
+ "package": "lit"
2681
+ },
2682
+ "tagName": "card-grid",
2683
+ "customElement": true
2684
+ }
2685
+ ],
2686
+ "exports": [
2687
+ {
2688
+ "kind": "js",
2689
+ "name": "CardGrid",
2690
+ "declaration": {
2691
+ "name": "CardGrid",
2692
+ "module": "src/card-grid.ts"
2693
+ }
2694
+ },
2695
+ {
2696
+ "kind": "custom-element-definition",
2697
+ "name": "card-grid",
2698
+ "declaration": {
2699
+ "name": "CardGrid",
2700
+ "module": "src/card-grid.ts"
2701
+ }
2702
+ }
2703
+ ]
2704
+ },
2548
2705
  {
2549
2706
  "kind": "javascript-module",
2550
2707
  "path": "src/chat-message.ts",
@@ -6122,6 +6279,22 @@
6122
6279
  "module": "./radio-pills.js"
6123
6280
  }
6124
6281
  },
6282
+ {
6283
+ "kind": "js",
6284
+ "name": "ButtonGroup",
6285
+ "declaration": {
6286
+ "name": "ButtonGroup",
6287
+ "module": "./button-group.js"
6288
+ }
6289
+ },
6290
+ {
6291
+ "kind": "js",
6292
+ "name": "ButtonGroupOption",
6293
+ "declaration": {
6294
+ "name": "ButtonGroupOption",
6295
+ "module": "./button-group.js"
6296
+ }
6297
+ },
6125
6298
  {
6126
6299
  "kind": "js",
6127
6300
  "name": "UiButton",
@@ -6490,6 +6663,30 @@
6490
6663
  "module": "./text-area.js"
6491
6664
  }
6492
6665
  },
6666
+ {
6667
+ "kind": "js",
6668
+ "name": "LinkCard",
6669
+ "declaration": {
6670
+ "name": "LinkCard",
6671
+ "module": "./link-card.js"
6672
+ }
6673
+ },
6674
+ {
6675
+ "kind": "js",
6676
+ "name": "LinkCardStatus",
6677
+ "declaration": {
6678
+ "name": "LinkCardStatus",
6679
+ "module": "./link-card.js"
6680
+ }
6681
+ },
6682
+ {
6683
+ "kind": "js",
6684
+ "name": "CardGrid",
6685
+ "declaration": {
6686
+ "name": "CardGrid",
6687
+ "module": "./card-grid.js"
6688
+ }
6689
+ },
6493
6690
  {
6494
6691
  "kind": "js",
6495
6692
  "name": "formatDuration",
@@ -7501,6 +7698,149 @@
7501
7698
  }
7502
7699
  ]
7503
7700
  },
7701
+ {
7702
+ "kind": "javascript-module",
7703
+ "path": "src/link-card.ts",
7704
+ "declarations": [
7705
+ {
7706
+ "kind": "class",
7707
+ "description": "A single linked-resource tile — logo (or an initial-letter fallback),\nheading, optional description, and an optional reachability status dot.\nRenders as a real `<a>` (opening in a new tab) when `href` is set, or a\nnon-interactive `<div>` otherwise. Meant to be laid out inside\n`card-grid`, mirroring how `gallery-item` pairs with `photo-gallery`.",
7708
+ "name": "LinkCard",
7709
+ "members": [
7710
+ {
7711
+ "kind": "field",
7712
+ "name": "heading",
7713
+ "type": {
7714
+ "text": "string"
7715
+ },
7716
+ "default": "\"\"",
7717
+ "description": "Card title.",
7718
+ "attribute": "heading"
7719
+ },
7720
+ {
7721
+ "kind": "field",
7722
+ "name": "description",
7723
+ "type": {
7724
+ "text": "string"
7725
+ },
7726
+ "default": "\"\"",
7727
+ "description": "Optional supporting text shown below the heading.",
7728
+ "attribute": "description"
7729
+ },
7730
+ {
7731
+ "kind": "field",
7732
+ "name": "href",
7733
+ "type": {
7734
+ "text": "string"
7735
+ },
7736
+ "default": "\"\"",
7737
+ "description": "Destination URL. When set, the card renders as a link (opened in a new tab); when unset, a non-interactive tile.",
7738
+ "attribute": "href"
7739
+ },
7740
+ {
7741
+ "kind": "field",
7742
+ "name": "logo",
7743
+ "type": {
7744
+ "text": "string"
7745
+ },
7746
+ "default": "\"\"",
7747
+ "description": "Logo image URL. Falls back to the first letter of `heading` if unset or it fails to load.",
7748
+ "attribute": "logo"
7749
+ },
7750
+ {
7751
+ "kind": "field",
7752
+ "name": "status",
7753
+ "type": {
7754
+ "text": "LinkCardStatus"
7755
+ },
7756
+ "default": "\"\"",
7757
+ "description": "Reachability state for the corner status dot. `\"\"` (default) renders no dot.",
7758
+ "attribute": "status"
7759
+ },
7760
+ {
7761
+ "kind": "field",
7762
+ "name": "_imgError",
7763
+ "type": {
7764
+ "text": "boolean"
7765
+ },
7766
+ "privacy": "private",
7767
+ "default": "false"
7768
+ }
7769
+ ],
7770
+ "attributes": [
7771
+ {
7772
+ "name": "heading",
7773
+ "type": {
7774
+ "text": "string"
7775
+ },
7776
+ "default": "\"\"",
7777
+ "description": "Card title.",
7778
+ "fieldName": "heading"
7779
+ },
7780
+ {
7781
+ "name": "description",
7782
+ "type": {
7783
+ "text": "string"
7784
+ },
7785
+ "default": "\"\"",
7786
+ "description": "Optional supporting text shown below the heading.",
7787
+ "fieldName": "description"
7788
+ },
7789
+ {
7790
+ "name": "href",
7791
+ "type": {
7792
+ "text": "string"
7793
+ },
7794
+ "default": "\"\"",
7795
+ "description": "Destination URL. When set, the card renders as a link (opened in a new tab); when unset, a non-interactive tile.",
7796
+ "fieldName": "href"
7797
+ },
7798
+ {
7799
+ "name": "logo",
7800
+ "type": {
7801
+ "text": "string"
7802
+ },
7803
+ "default": "\"\"",
7804
+ "description": "Logo image URL. Falls back to the first letter of `heading` if unset or it fails to load.",
7805
+ "fieldName": "logo"
7806
+ },
7807
+ {
7808
+ "name": "status",
7809
+ "type": {
7810
+ "text": "LinkCardStatus"
7811
+ },
7812
+ "default": "\"\"",
7813
+ "description": "Reachability state for the corner status dot. `\"\"` (default) renders no dot.",
7814
+ "fieldName": "status"
7815
+ }
7816
+ ],
7817
+ "superclass": {
7818
+ "name": "LitElement",
7819
+ "package": "lit"
7820
+ },
7821
+ "tagName": "link-card",
7822
+ "customElement": true
7823
+ }
7824
+ ],
7825
+ "exports": [
7826
+ {
7827
+ "kind": "js",
7828
+ "name": "LinkCard",
7829
+ "declaration": {
7830
+ "name": "LinkCard",
7831
+ "module": "src/link-card.ts"
7832
+ }
7833
+ },
7834
+ {
7835
+ "kind": "custom-element-definition",
7836
+ "name": "link-card",
7837
+ "declaration": {
7838
+ "name": "LinkCard",
7839
+ "module": "src/link-card.ts"
7840
+ }
7841
+ }
7842
+ ]
7843
+ },
7504
7844
  {
7505
7845
  "kind": "javascript-module",
7506
7846
  "path": "src/live-timer.ts",
@@ -10874,6 +11214,16 @@
10874
11214
  "default": "\"\"",
10875
11215
  "description": "Fill color override; falls back to the `--ui-success` token.",
10876
11216
  "attribute": "color"
11217
+ },
11218
+ {
11219
+ "kind": "field",
11220
+ "name": "trackColor",
11221
+ "type": {
11222
+ "text": "string"
11223
+ },
11224
+ "default": "\"\"",
11225
+ "description": "Track (inactive portion) background override; falls back to the `--ui-surface-muted` token.",
11226
+ "attribute": "track-color"
10877
11227
  }
10878
11228
  ],
10879
11229
  "attributes": [
@@ -10903,6 +11253,15 @@
10903
11253
  "default": "\"\"",
10904
11254
  "description": "Fill color override; falls back to the `--ui-success` token.",
10905
11255
  "fieldName": "color"
11256
+ },
11257
+ {
11258
+ "name": "track-color",
11259
+ "type": {
11260
+ "text": "string"
11261
+ },
11262
+ "default": "\"\"",
11263
+ "description": "Track (inactive portion) background override; falls back to the `--ui-surface-muted` token.",
11264
+ "fieldName": "trackColor"
10906
11265
  }
10907
11266
  ],
10908
11267
  "superclass": {
@@ -0,0 +1,38 @@
1
+ import type { TemplateResult } from "lit";
2
+ import { LitElement } from "lit";
3
+ export interface ButtonGroupOption {
4
+ value: string;
5
+ label: string;
6
+ /** Optional pre-rendered icon template displayed before the label. */
7
+ icon?: TemplateResult;
8
+ }
9
+ /**
10
+ * Single-select segmented control — a strip of buttons joined into one
11
+ * shared-border shape, for a small, persistent set of mutually exclusive
12
+ * choices (a view switcher, a theme picker) where the *currently selected*
13
+ * option should read as visually "pressed," not just checked. For many
14
+ * short, individually pill-shaped choices, use `radio-pills` instead. Wraps
15
+ * native radio inputs for keyboard/a11y and fires `change` rather than
16
+ * relying on form submission.
17
+ *
18
+ * @element button-group
19
+ * @fires change - A segment was selected; detail: { value }.
20
+ */
21
+ export declare class ButtonGroup extends LitElement {
22
+ #private;
23
+ static styles: import("lit").CSSResult[];
24
+ /** Options to render, one segment each. */
25
+ options: ButtonGroupOption[];
26
+ /** Currently selected value. */
27
+ value: string;
28
+ /** Disables every native radio in the group. */
29
+ disabled: boolean;
30
+ private _onChange;
31
+ render(): TemplateResult<1>;
32
+ }
33
+ declare global {
34
+ interface HTMLElementTagNameMap {
35
+ "button-group": ButtonGroup;
36
+ }
37
+ }
38
+ //# sourceMappingURL=button-group.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"button-group.d.ts","sourceRoot":"","sources":["../src/button-group.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,KAAK,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAsB,MAAM,KAAK,CAAC;AAKrD,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,IAAI,CAAC,EAAE,cAAc,CAAC;CACvB;AAID;;;;;;;;;;;GAWG;AACH,qBACa,WAAY,SAAQ,UAAU;;IACzC,OAAgB,MAAM,4BA8FpB;IAEF,2CAA2C;IACX,OAAO,EAAE,iBAAiB,EAAE,CAAM;IAClE,gCAAgC;IACpB,KAAK,SAAM;IACvB,gDAAgD;IACnB,QAAQ,UAAS;IAI9C,OAAO,CAAC,SAAS;IAQR,MAAM,sBAsBd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,cAAc,EAAE,WAAW,CAAC;KAC7B;CACF"}
@@ -0,0 +1,170 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
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
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { LitElement, css, html, nothing } from "lit";
8
+ import { customElement, property } from "lit/decorators.js";
9
+ import { repeat } from "lit/directives/repeat.js";
10
+ import { tokens } from "./tokens.js";
11
+ let instanceCount = 0;
12
+ /**
13
+ * Single-select segmented control — a strip of buttons joined into one
14
+ * shared-border shape, for a small, persistent set of mutually exclusive
15
+ * choices (a view switcher, a theme picker) where the *currently selected*
16
+ * option should read as visually "pressed," not just checked. For many
17
+ * short, individually pill-shaped choices, use `radio-pills` instead. Wraps
18
+ * native radio inputs for keyboard/a11y and fires `change` rather than
19
+ * relying on form submission.
20
+ *
21
+ * @element button-group
22
+ * @fires change - A segment was selected; detail: { value }.
23
+ */
24
+ let ButtonGroup = class ButtonGroup extends LitElement {
25
+ constructor() {
26
+ super(...arguments);
27
+ /** Options to render, one segment each. */
28
+ this.options = [];
29
+ /** Currently selected value. */
30
+ this.value = "";
31
+ /** Disables every native radio in the group. */
32
+ this.disabled = false;
33
+ this.#name = `button-group-${++instanceCount}`;
34
+ }
35
+ static { this.styles = [
36
+ tokens,
37
+ css `
38
+ :host {
39
+ display: inline-block;
40
+ }
41
+ .group {
42
+ display: flex;
43
+ border: 1px solid var(--ui-border, #e2e8f0);
44
+ border-radius: var(--ui-radius-sm, 0.25rem);
45
+ overflow: hidden;
46
+ }
47
+ .segment {
48
+ position: relative;
49
+ display: flex;
50
+ flex: 1 1 auto;
51
+ align-items: center;
52
+ justify-content: center;
53
+ gap: 0.25rem;
54
+ height: 2rem;
55
+ box-sizing: border-box;
56
+ padding: 0.5rem 0.75rem;
57
+ border-left: 1px solid var(--ui-border, #e2e8f0);
58
+ cursor: pointer;
59
+ font-family: var(
60
+ --ui-font,
61
+ ui-sans-serif,
62
+ system-ui,
63
+ sans-serif,
64
+ "Apple Color Emoji",
65
+ "Segoe UI Emoji",
66
+ "Segoe UI Symbol",
67
+ "Noto Color Emoji"
68
+ );
69
+ font-size: var(--ui-font-size-sm, 0.75rem);
70
+ font-weight: var(--ui-font-weight-medium, 500);
71
+ line-height: var(--ui-line-height-tight, 1.25);
72
+ color: var(--ui-text, #0f172a);
73
+ white-space: nowrap;
74
+ }
75
+ .segment:first-child {
76
+ border-left: none;
77
+ border-top-left-radius: var(--ui-radius-sm, 0.25rem);
78
+ border-bottom-left-radius: var(--ui-radius-sm, 0.25rem);
79
+ }
80
+ .segment:last-child {
81
+ border-top-right-radius: var(--ui-radius-sm, 0.25rem);
82
+ border-bottom-right-radius: var(--ui-radius-sm, 0.25rem);
83
+ }
84
+ .segment:has(input:checked) {
85
+ background: var(--ui-primary, #4f46e5);
86
+ color: var(--ui-on-accent, #ffffff);
87
+ }
88
+ .segment:not(:has(input:checked)):hover {
89
+ background: var(--ui-surface-muted, #f8fafc);
90
+ }
91
+ .segment input {
92
+ /* Fills the whole segment (not visually-hidden-offscreen) so a real
93
+ click anywhere in the segment — including a test driver clicking
94
+ the input by its accessible role — lands on the input itself,
95
+ not just via native <label> click delegation. */
96
+ position: absolute;
97
+ inset: 0;
98
+ margin: 0;
99
+ opacity: 0;
100
+ cursor: pointer;
101
+ }
102
+ /* Clipped by the group's own overflow:hidden unless promoted above its
103
+ neighbors — the focus ring needs a stacking context of its own. */
104
+ .segment:has(input:focus-visible) {
105
+ z-index: 1;
106
+ outline: none;
107
+ box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));
108
+ }
109
+ .segment:has(input:disabled) {
110
+ cursor: not-allowed;
111
+ opacity: 0.6;
112
+ }
113
+ @media (forced-colors: active) {
114
+ .segment:has(input:focus-visible) {
115
+ outline: 2px solid CanvasText;
116
+ outline-offset: 2px;
117
+ box-shadow: none;
118
+ }
119
+ .segment:has(input:checked) {
120
+ background: Highlight;
121
+ color: HighlightText;
122
+ }
123
+ .segment:has(input:disabled) {
124
+ color: GrayText;
125
+ opacity: 1;
126
+ }
127
+ }
128
+ `,
129
+ ]; }
130
+ #name;
131
+ _onChange(value) {
132
+ if (this.disabled)
133
+ return;
134
+ this.value = value;
135
+ this.dispatchEvent(new CustomEvent("change", { detail: { value }, bubbles: true, composed: true }));
136
+ }
137
+ render() {
138
+ return html `
139
+ <div class="group">
140
+ ${repeat(this.options, (opt) => opt.value, (opt) => html `
141
+ <label class="segment">
142
+ <input
143
+ type="radio"
144
+ name=${this.#name}
145
+ ?checked=${this.value === opt.value}
146
+ ?disabled=${this.disabled}
147
+ @change=${() => this._onChange(opt.value)}
148
+ />
149
+ ${opt.icon ?? nothing}
150
+ <span>${opt.label}</span>
151
+ </label>
152
+ `)}
153
+ </div>
154
+ `;
155
+ }
156
+ };
157
+ __decorate([
158
+ property({ attribute: false })
159
+ ], ButtonGroup.prototype, "options", void 0);
160
+ __decorate([
161
+ property()
162
+ ], ButtonGroup.prototype, "value", void 0);
163
+ __decorate([
164
+ property({ type: Boolean })
165
+ ], ButtonGroup.prototype, "disabled", void 0);
166
+ ButtonGroup = __decorate([
167
+ customElement("button-group")
168
+ ], ButtonGroup);
169
+ export { ButtonGroup };
170
+ //# sourceMappingURL=button-group.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"button-group.js","sourceRoot":"","sources":["../src/button-group.ts"],"names":[],"mappings":";;;;;;AACA,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AASrC,IAAI,aAAa,GAAG,CAAC,CAAC;AAEtB;;;;;;;;;;;GAWG;AAEI,IAAM,WAAW,GAAjB,MAAM,WAAY,SAAQ,UAAU;IAApC;;QAiGL,2CAA2C;QACX,YAAO,GAAwB,EAAE,CAAC;QAClE,gCAAgC;QACpB,UAAK,GAAG,EAAE,CAAC;QACvB,gDAAgD;QACnB,aAAQ,GAAG,KAAK,CAAC;QAErC,UAAK,GAAG,gBAAgB,EAAE,aAAa,EAAE,CAAC;IAiCrD,CAAC;aAxIiB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA2FF;KACF,AA9FqB,CA8FpB;IASO,KAAK,CAAqC;IAE3C,SAAS,CAAC,KAAa;QAC7B,IAAI,IAAI,CAAC,QAAQ;YAAE,OAAO;QAC1B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,aAAa,CAChB,IAAI,WAAW,CAAC,QAAQ,EAAE,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAChF,CAAC;IACJ,CAAC;IAEQ,MAAM;QACb,OAAO,IAAI,CAAA;;UAEL,MAAM,CACN,IAAI,CAAC,OAAO,EACZ,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,EAClB,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAA;;;;uBAIA,IAAI,CAAC,KAAK;2BACN,IAAI,CAAC,KAAK,KAAK,GAAG,CAAC,KAAK;4BACvB,IAAI,CAAC,QAAQ;0BACf,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC;;gBAEzC,GAAG,CAAC,IAAI,IAAI,OAAO;sBACb,GAAG,CAAC,KAAK;;WAEpB,CACF;;KAEJ,CAAC;IACJ,CAAC;CACF,CAAA;AAvCiC;IAA/B,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;4CAAmC;AAEtD;IAAX,QAAQ,EAAE;0CAAY;AAEM;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;6CAAkB;AAtGnC,WAAW;IADvB,aAAa,CAAC,cAAc,CAAC;GACjB,WAAW,CAyIvB","sourcesContent":["import type { TemplateResult } from \"lit\";\nimport { LitElement, css, html, nothing } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { repeat } from \"lit/directives/repeat.js\";\nimport { tokens } from \"./tokens.js\";\n\nexport interface ButtonGroupOption {\n value: string;\n label: string;\n /** Optional pre-rendered icon template displayed before the label. */\n icon?: TemplateResult;\n}\n\nlet instanceCount = 0;\n\n/**\n * Single-select segmented control — a strip of buttons joined into one\n * shared-border shape, for a small, persistent set of mutually exclusive\n * choices (a view switcher, a theme picker) where the *currently selected*\n * option should read as visually \"pressed,\" not just checked. For many\n * short, individually pill-shaped choices, use `radio-pills` instead. Wraps\n * native radio inputs for keyboard/a11y and fires `change` rather than\n * relying on form submission.\n *\n * @element button-group\n * @fires change - A segment was selected; detail: { value }.\n */\n@customElement(\"button-group\")\nexport class ButtonGroup extends LitElement {\n static override styles = [\n tokens,\n css`\n :host {\n display: inline-block;\n }\n .group {\n display: flex;\n border: 1px solid var(--ui-border, #e2e8f0);\n border-radius: var(--ui-radius-sm, 0.25rem);\n overflow: hidden;\n }\n .segment {\n position: relative;\n display: flex;\n flex: 1 1 auto;\n align-items: center;\n justify-content: center;\n gap: 0.25rem;\n height: 2rem;\n box-sizing: border-box;\n padding: 0.5rem 0.75rem;\n border-left: 1px solid var(--ui-border, #e2e8f0);\n cursor: pointer;\n font-family: var(\n --ui-font,\n ui-sans-serif,\n system-ui,\n sans-serif,\n \"Apple Color Emoji\",\n \"Segoe UI Emoji\",\n \"Segoe UI Symbol\",\n \"Noto Color Emoji\"\n );\n font-size: var(--ui-font-size-sm, 0.75rem);\n font-weight: var(--ui-font-weight-medium, 500);\n line-height: var(--ui-line-height-tight, 1.25);\n color: var(--ui-text, #0f172a);\n white-space: nowrap;\n }\n .segment:first-child {\n border-left: none;\n border-top-left-radius: var(--ui-radius-sm, 0.25rem);\n border-bottom-left-radius: var(--ui-radius-sm, 0.25rem);\n }\n .segment:last-child {\n border-top-right-radius: var(--ui-radius-sm, 0.25rem);\n border-bottom-right-radius: var(--ui-radius-sm, 0.25rem);\n }\n .segment:has(input:checked) {\n background: var(--ui-primary, #4f46e5);\n color: var(--ui-on-accent, #ffffff);\n }\n .segment:not(:has(input:checked)):hover {\n background: var(--ui-surface-muted, #f8fafc);\n }\n .segment input {\n /* Fills the whole segment (not visually-hidden-offscreen) so a real\n click anywhere in the segment — including a test driver clicking\n the input by its accessible role — lands on the input itself,\n not just via native <label> click delegation. */\n position: absolute;\n inset: 0;\n margin: 0;\n opacity: 0;\n cursor: pointer;\n }\n /* Clipped by the group's own overflow:hidden unless promoted above its\n neighbors — the focus ring needs a stacking context of its own. */\n .segment:has(input:focus-visible) {\n z-index: 1;\n outline: none;\n box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));\n }\n .segment:has(input:disabled) {\n cursor: not-allowed;\n opacity: 0.6;\n }\n @media (forced-colors: active) {\n .segment:has(input:focus-visible) {\n outline: 2px solid CanvasText;\n outline-offset: 2px;\n box-shadow: none;\n }\n .segment:has(input:checked) {\n background: Highlight;\n color: HighlightText;\n }\n .segment:has(input:disabled) {\n color: GrayText;\n opacity: 1;\n }\n }\n `,\n ];\n\n /** Options to render, one segment each. */\n @property({ attribute: false }) options: ButtonGroupOption[] = [];\n /** Currently selected value. */\n @property() value = \"\";\n /** Disables every native radio in the group. */\n @property({ type: Boolean }) disabled = false;\n\n readonly #name = `button-group-${++instanceCount}`;\n\n private _onChange(value: string) {\n if (this.disabled) return;\n this.value = value;\n this.dispatchEvent(\n new CustomEvent(\"change\", { detail: { value }, bubbles: true, composed: true }),\n );\n }\n\n override render() {\n return html`\n <div class=\"group\">\n ${repeat(\n this.options,\n (opt) => opt.value,\n (opt) => html`\n <label class=\"segment\">\n <input\n type=\"radio\"\n name=${this.#name}\n ?checked=${this.value === opt.value}\n ?disabled=${this.disabled}\n @change=${() => this._onChange(opt.value)}\n />\n ${opt.icon ?? nothing}\n <span>${opt.label}</span>\n </label>\n `,\n )}\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"button-group\": ButtonGroup;\n }\n}\n"]}
@@ -0,0 +1,19 @@
1
+ import { LitElement } from "lit";
2
+ /**
3
+ * A responsive auto-filling grid shell for `link-card` (or any card-shaped
4
+ * content) — each slotted child becomes a grid item, wrapping to a new row
5
+ * once the container is too narrow for another `15rem` column.
6
+ *
7
+ * @element card-grid
8
+ * @slot - `link-card` elements (or other card-shaped content).
9
+ */
10
+ export declare class CardGrid extends LitElement {
11
+ static styles: import("lit").CSSResult[];
12
+ render(): import("lit-html").TemplateResult<1>;
13
+ }
14
+ declare global {
15
+ interface HTMLElementTagNameMap {
16
+ "card-grid": CardGrid;
17
+ }
18
+ }
19
+ //# sourceMappingURL=card-grid.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"card-grid.d.ts","sourceRoot":"","sources":["../src/card-grid.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAI5C;;;;;;;GAOG;AACH,qBACa,QAAS,SAAQ,UAAU;IACtC,OAAgB,MAAM,4BAYpB;IAEO,MAAM,yCAMd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,WAAW,EAAE,QAAQ,CAAC;KACvB;CACF"}