@timmo001/groundwork-ui 0.9.9 → 0.9.10

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.
@@ -16560,6 +16560,529 @@
16560
16560
  }
16561
16561
  ]
16562
16562
  },
16563
+ {
16564
+ "kind": "javascript-module",
16565
+ "path": "src/gw-sheet.ts",
16566
+ "declarations": [
16567
+ {
16568
+ "kind": "class",
16569
+ "description": "A panel of filters, settings or details that slides in from an edge of the\nscreen over the page, rather than taking a place in its layout. With\n`side=\"auto\"` it comes from the start edge, full height, and from the\nbottom on narrow screens.\n\nIt's on the Popover API, in the top layer, and closes on Esc, a click\noutside or its close button, whose chevron points the way it leaves. Open\nit with `show()`, or `toggle()` from the button that opens it. Its content\nscrolls under a header with the heading and an `actions` slot, such as a\nClear button.",
16570
+ "name": "GwSheet",
16571
+ "cssProperties": [
16572
+ {
16573
+ "description": "Its width from the start or end edge. Defaults to `20rem`.",
16574
+ "name": "--gw-sheet-width"
16575
+ },
16576
+ {
16577
+ "description": "Its greatest height from the bottom. Defaults to `85vh`.",
16578
+ "name": "--gw-sheet-height"
16579
+ }
16580
+ ],
16581
+ "cssParts": [
16582
+ {
16583
+ "description": "The sheet's surface.",
16584
+ "name": "surface"
16585
+ },
16586
+ {
16587
+ "description": "The row with the close button, heading and actions.",
16588
+ "name": "header"
16589
+ },
16590
+ {
16591
+ "description": "The heading.",
16592
+ "name": "heading"
16593
+ },
16594
+ {
16595
+ "description": "The content.",
16596
+ "name": "body"
16597
+ }
16598
+ ],
16599
+ "slots": [
16600
+ {
16601
+ "description": "The content, which scrolls.",
16602
+ "name": ""
16603
+ },
16604
+ {
16605
+ "description": "Buttons in the header, after the heading.",
16606
+ "name": "actions"
16607
+ }
16608
+ ],
16609
+ "members": [
16610
+ {
16611
+ "kind": "field",
16612
+ "name": "properties",
16613
+ "type": {
16614
+ "text": "Properties"
16615
+ },
16616
+ "static": true,
16617
+ "default": "{ heading: {}, side: { reflect: true }, }",
16618
+ "inheritedFrom": {
16619
+ "name": "GwElement",
16620
+ "module": "src/element.ts"
16621
+ }
16622
+ },
16623
+ {
16624
+ "kind": "field",
16625
+ "name": "styles",
16626
+ "type": {
16627
+ "text": "readonly CSSStyleSheet[]"
16628
+ },
16629
+ "static": true,
16630
+ "default": "[ base, css` [popover] { position: fixed; box-sizing: border-box; flex-direction: column; padding: 0; margin: 0; overflow: hidden; color: var(--gw-color-text-primary); background: var(--gw-color-surface-overlay); border: 0 solid var(--gw-color-border-normal); box-shadow: var(--gw-shadow-l); transition: translate var(--gw-motion-normal) var(--gw-motion-easing), display var(--gw-motion-normal) allow-discrete, overlay var(--gw-motion-normal) allow-discrete; } [popover]:popover-open { display: flex; translate: 0 0; } [popover]::backdrop { background: rgb(0 0 0 / 40%); } /* Full height from the start or end edge */ [data-side=\"start\"], [data-side=\"end\"] { inset: 0 auto; width: min(var(--gw-sheet-width, 20rem), 100vw); height: 100%; max-height: none; } [data-side=\"start\"] { inset-inline-start: 0; border-inline-end-width: max(var(--gw-border-width), 1px); translate: -100% 0; } [data-side=\"end\"] { inset-inline-end: 0; border-inline-start-width: max(var(--gw-border-width), 1px); translate: 100% 0; } :host(:dir(rtl)) [data-side=\"start\"] { translate: 100% 0; } :host(:dir(rtl)) [data-side=\"end\"] { translate: -100% 0; } /* Up from the bottom, with room for the phone's home bar */ [data-side=\"bottom\"] { inset: auto 0 0; width: 100%; max-width: none; max-height: var(--gw-sheet-height, 85vh); padding-block-end: env(safe-area-inset-bottom); border-block-start-width: max(var(--gw-border-width), 1px); border-start-start-radius: var(--gw-radius-panel); border-start-end-radius: var(--gw-radius-panel); translate: 0 100%; } @starting-style { [data-side=\"start\"]:popover-open { translate: -100% 0; } [data-side=\"end\"]:popover-open { translate: 100% 0; } [data-side=\"bottom\"]:popover-open { translate: 0 100%; } } header { display: flex; flex: none; gap: var(--gw-space-2); align-items: center; padding: var(--gw-space-2) var(--gw-space-3); border-block-end: var(--gw-border-width) solid var(--gw-color-border-quiet); } h2 { flex: 1; min-width: 0; margin: 0; font-size: var(--gw-font-size-m); font-weight: var(--gw-font-weight-strong); color: var(--gw-color-text-strong); } .body { display: flex; flex: 1; flex-direction: column; gap: var(--gw-space-3); min-height: 0; padding: var(--gw-space-4); /* It scrolls down only, so content reaching into its padding never makes it scroll sideways */ overflow: hidden auto; overscroll-behavior: contain; scrollbar-width: thin; } `, ]",
16631
+ "inheritedFrom": {
16632
+ "name": "GwElement",
16633
+ "module": "src/element.ts"
16634
+ }
16635
+ },
16636
+ {
16637
+ "kind": "field",
16638
+ "name": "heading",
16639
+ "type": {
16640
+ "text": "string"
16641
+ },
16642
+ "default": "\"\""
16643
+ },
16644
+ {
16645
+ "kind": "field",
16646
+ "name": "side",
16647
+ "type": {
16648
+ "text": "\"auto\" | Side"
16649
+ },
16650
+ "default": "\"auto\""
16651
+ },
16652
+ {
16653
+ "kind": "field",
16654
+ "name": "#heading",
16655
+ "privacy": "private",
16656
+ "readonly": true
16657
+ },
16658
+ {
16659
+ "kind": "field",
16660
+ "name": "#closeButton",
16661
+ "privacy": "private",
16662
+ "readonly": true
16663
+ },
16664
+ {
16665
+ "kind": "field",
16666
+ "name": "#surface",
16667
+ "privacy": "private",
16668
+ "type": {
16669
+ "text": "HTMLDivElement"
16670
+ },
16671
+ "readonly": true
16672
+ },
16673
+ {
16674
+ "kind": "field",
16675
+ "name": "#narrow",
16676
+ "privacy": "private",
16677
+ "readonly": true
16678
+ },
16679
+ {
16680
+ "kind": "field",
16681
+ "name": "#returnFocus",
16682
+ "privacy": "private",
16683
+ "type": {
16684
+ "text": "HTMLElement | undefined"
16685
+ }
16686
+ },
16687
+ {
16688
+ "kind": "field",
16689
+ "name": "open",
16690
+ "description": "Whether it's open.",
16691
+ "readonly": true
16692
+ },
16693
+ {
16694
+ "kind": "method",
16695
+ "name": "show"
16696
+ },
16697
+ {
16698
+ "kind": "method",
16699
+ "name": "close"
16700
+ },
16701
+ {
16702
+ "kind": "method",
16703
+ "name": "toggle",
16704
+ "description": "Opens it, or closes it if it's open, as the button that opens it would."
16705
+ },
16706
+ {
16707
+ "kind": "method",
16708
+ "name": "#edge",
16709
+ "privacy": "private",
16710
+ "return": {
16711
+ "type": {
16712
+ "text": "Side"
16713
+ }
16714
+ },
16715
+ "description": "The edge it comes from now, with `auto` settled by the screen's width."
16716
+ },
16717
+ {
16718
+ "kind": "field",
16719
+ "name": "#place",
16720
+ "privacy": "private",
16721
+ "readonly": true
16722
+ },
16723
+ {
16724
+ "kind": "field",
16725
+ "name": "#onToggle",
16726
+ "privacy": "private",
16727
+ "readonly": true
16728
+ },
16729
+ {
16730
+ "kind": "method",
16731
+ "name": "create",
16732
+ "privacy": "protected",
16733
+ "parameters": [
16734
+ {
16735
+ "name": "root",
16736
+ "type": {
16737
+ "text": "ShadowRoot"
16738
+ }
16739
+ }
16740
+ ],
16741
+ "description": "Builds the shadow root's content once, on first connection.",
16742
+ "inheritedFrom": {
16743
+ "name": "GwElement",
16744
+ "module": "src/element.ts"
16745
+ }
16746
+ },
16747
+ {
16748
+ "kind": "method",
16749
+ "name": "update",
16750
+ "privacy": "protected",
16751
+ "parameters": [
16752
+ {
16753
+ "name": "_changed",
16754
+ "type": {
16755
+ "text": "ReadonlySet<string>"
16756
+ }
16757
+ }
16758
+ ],
16759
+ "description": "Sets what the changed properties affect.",
16760
+ "inheritedFrom": {
16761
+ "name": "GwElement",
16762
+ "module": "src/element.ts"
16763
+ }
16764
+ },
16765
+ {
16766
+ "kind": "field",
16767
+ "name": "shadow",
16768
+ "type": {
16769
+ "text": "boolean"
16770
+ },
16771
+ "static": true,
16772
+ "default": "true",
16773
+ "description": "Whether to attach a shadow root. Light DOM elements set this false.",
16774
+ "inheritedFrom": {
16775
+ "name": "GwElement",
16776
+ "module": "src/element.ts"
16777
+ }
16778
+ },
16779
+ {
16780
+ "kind": "field",
16781
+ "name": "shadowRootOptions",
16782
+ "type": {
16783
+ "text": "ShadowRootInit"
16784
+ },
16785
+ "static": true,
16786
+ "default": "{ mode: \"open\" }",
16787
+ "description": "How the shadow root is attached.",
16788
+ "inheritedFrom": {
16789
+ "name": "GwElement",
16790
+ "module": "src/element.ts"
16791
+ }
16792
+ },
16793
+ {
16794
+ "kind": "method",
16795
+ "name": "define",
16796
+ "static": true,
16797
+ "parameters": [
16798
+ {
16799
+ "name": "tag",
16800
+ "type": {
16801
+ "text": "string"
16802
+ }
16803
+ }
16804
+ ],
16805
+ "description": "Adds the property accessors, then defines the element.",
16806
+ "inheritedFrom": {
16807
+ "name": "GwElement",
16808
+ "module": "src/element.ts"
16809
+ }
16810
+ },
16811
+ {
16812
+ "kind": "field",
16813
+ "name": "#values",
16814
+ "privacy": "private",
16815
+ "readonly": true,
16816
+ "default": "new Map<string, PropertyValue>()",
16817
+ "inheritedFrom": {
16818
+ "name": "GwElement",
16819
+ "module": "src/element.ts"
16820
+ }
16821
+ },
16822
+ {
16823
+ "kind": "field",
16824
+ "name": "#defaults",
16825
+ "privacy": "private",
16826
+ "readonly": true,
16827
+ "default": "new Map<string, PropertyValue>()",
16828
+ "inheritedFrom": {
16829
+ "name": "GwElement",
16830
+ "module": "src/element.ts"
16831
+ }
16832
+ },
16833
+ {
16834
+ "kind": "field",
16835
+ "name": "#changed",
16836
+ "privacy": "private",
16837
+ "readonly": true,
16838
+ "default": "new Set<string>()",
16839
+ "inheritedFrom": {
16840
+ "name": "GwElement",
16841
+ "module": "src/element.ts"
16842
+ }
16843
+ },
16844
+ {
16845
+ "kind": "field",
16846
+ "name": "#unreflected",
16847
+ "privacy": "private",
16848
+ "readonly": true,
16849
+ "default": "new Set<string>()",
16850
+ "inheritedFrom": {
16851
+ "name": "GwElement",
16852
+ "module": "src/element.ts"
16853
+ }
16854
+ },
16855
+ {
16856
+ "kind": "field",
16857
+ "name": "#created",
16858
+ "privacy": "private",
16859
+ "type": {
16860
+ "text": "boolean"
16861
+ },
16862
+ "default": "false",
16863
+ "inheritedFrom": {
16864
+ "name": "GwElement",
16865
+ "module": "src/element.ts"
16866
+ }
16867
+ },
16868
+ {
16869
+ "kind": "field",
16870
+ "name": "#queued",
16871
+ "privacy": "private",
16872
+ "type": {
16873
+ "text": "boolean"
16874
+ },
16875
+ "default": "false",
16876
+ "inheritedFrom": {
16877
+ "name": "GwElement",
16878
+ "module": "src/element.ts"
16879
+ }
16880
+ },
16881
+ {
16882
+ "kind": "field",
16883
+ "name": "#pending",
16884
+ "privacy": "private",
16885
+ "type": {
16886
+ "text": "Promise<void> | undefined"
16887
+ },
16888
+ "inheritedFrom": {
16889
+ "name": "GwElement",
16890
+ "module": "src/element.ts"
16891
+ }
16892
+ },
16893
+ {
16894
+ "kind": "field",
16895
+ "name": "#reflecting",
16896
+ "privacy": "private",
16897
+ "type": {
16898
+ "text": "boolean"
16899
+ },
16900
+ "default": "false",
16901
+ "inheritedFrom": {
16902
+ "name": "GwElement",
16903
+ "module": "src/element.ts"
16904
+ }
16905
+ },
16906
+ {
16907
+ "kind": "field",
16908
+ "name": "#resolveUpdated",
16909
+ "privacy": "private",
16910
+ "type": {
16911
+ "text": "() => void"
16912
+ },
16913
+ "inheritedFrom": {
16914
+ "name": "GwElement",
16915
+ "module": "src/element.ts"
16916
+ }
16917
+ },
16918
+ {
16919
+ "kind": "field",
16920
+ "name": "#updated",
16921
+ "privacy": "private",
16922
+ "readonly": true,
16923
+ "default": "new Promise<void>((resolve) => { this.#resolveUpdated = resolve; })",
16924
+ "inheritedFrom": {
16925
+ "name": "GwElement",
16926
+ "module": "src/element.ts"
16927
+ }
16928
+ },
16929
+ {
16930
+ "kind": "field",
16931
+ "name": "renderRoot",
16932
+ "type": {
16933
+ "text": "ShadowRoot | HTMLElement"
16934
+ },
16935
+ "readonly": true,
16936
+ "description": "The shadow root, or the element itself for a light DOM element.",
16937
+ "inheritedFrom": {
16938
+ "name": "GwElement",
16939
+ "module": "src/element.ts"
16940
+ }
16941
+ },
16942
+ {
16943
+ "kind": "field",
16944
+ "name": "#own",
16945
+ "privacy": "private",
16946
+ "type": {
16947
+ "text": "typeof GwElement"
16948
+ },
16949
+ "readonly": true,
16950
+ "description": "The element's own class, for its static properties and styles.",
16951
+ "default": "new.target",
16952
+ "inheritedFrom": {
16953
+ "name": "GwElement",
16954
+ "module": "src/element.ts"
16955
+ }
16956
+ },
16957
+ {
16958
+ "kind": "field",
16959
+ "name": "updateComplete",
16960
+ "description": "Resolves once the pending update has run, or the first one before it connects.",
16961
+ "readonly": true,
16962
+ "inheritedFrom": {
16963
+ "name": "GwElement",
16964
+ "module": "src/element.ts"
16965
+ }
16966
+ },
16967
+ {
16968
+ "kind": "method",
16969
+ "name": "defaultOf",
16970
+ "privacy": "protected",
16971
+ "parameters": [
16972
+ {
16973
+ "name": "name",
16974
+ "type": {
16975
+ "text": "string"
16976
+ }
16977
+ }
16978
+ ],
16979
+ "description": "The value a property returns to when its attribute is removed. Defaults\nto the value it was given in the constructor.",
16980
+ "inheritedFrom": {
16981
+ "name": "GwElement",
16982
+ "module": "src/element.ts"
16983
+ }
16984
+ },
16985
+ {
16986
+ "kind": "method",
16987
+ "name": "requestUpdate",
16988
+ "parameters": [
16989
+ {
16990
+ "name": "names",
16991
+ "type": {
16992
+ "text": "readonly string[]"
16993
+ }
16994
+ }
16995
+ ],
16996
+ "description": "Asks for an update with the given properties marked as changed.",
16997
+ "inheritedFrom": {
16998
+ "name": "GwElement",
16999
+ "module": "src/element.ts"
17000
+ }
17001
+ },
17002
+ {
17003
+ "kind": "method",
17004
+ "name": "#flush",
17005
+ "privacy": "private",
17006
+ "inheritedFrom": {
17007
+ "name": "GwElement",
17008
+ "module": "src/element.ts"
17009
+ }
17010
+ },
17011
+ {
17012
+ "kind": "method",
17013
+ "name": "#setProperty",
17014
+ "privacy": "private",
17015
+ "parameters": [
17016
+ {
17017
+ "name": "name",
17018
+ "type": {
17019
+ "text": "string"
17020
+ }
17021
+ },
17022
+ {
17023
+ "name": "options",
17024
+ "type": {
17025
+ "text": "PropertyOptions"
17026
+ }
17027
+ },
17028
+ {
17029
+ "name": "value",
17030
+ "type": {
17031
+ "text": "PropertyValue"
17032
+ }
17033
+ }
17034
+ ],
17035
+ "inheritedFrom": {
17036
+ "name": "GwElement",
17037
+ "module": "src/element.ts"
17038
+ }
17039
+ }
17040
+ ],
17041
+ "events": [
17042
+ {
17043
+ "type": {
17044
+ "text": "Event"
17045
+ },
17046
+ "description": "When it closes.",
17047
+ "name": "close"
17048
+ }
17049
+ ],
17050
+ "attributes": [
17051
+ {
17052
+ "type": {
17053
+ "text": "string"
17054
+ },
17055
+ "description": "The heading beside the close button.",
17056
+ "name": "heading"
17057
+ },
17058
+ {
17059
+ "type": {
17060
+ "text": "\"auto\"|\"start\"|\"end\"|\"bottom\""
17061
+ },
17062
+ "description": "The edge it slides in from. `auto` is the start edge, or the bottom on narrow screens. Defaults to `auto`.",
17063
+ "name": "side"
17064
+ }
17065
+ ],
17066
+ "superclass": {
17067
+ "name": "GwElement",
17068
+ "module": "/src/element.js"
17069
+ },
17070
+ "tagName": "gw-sheet",
17071
+ "customElement": true,
17072
+ "summary": "A panel that slides in from an edge of the screen."
17073
+ }
17074
+ ],
17075
+ "exports": [
17076
+ {
17077
+ "kind": "js",
17078
+ "name": "GwSheet",
17079
+ "declaration": {
17080
+ "name": "GwSheet",
17081
+ "module": "src/gw-sheet.ts"
17082
+ }
17083
+ }
17084
+ ]
17085
+ },
16563
17086
  {
16564
17087
  "kind": "javascript-module",
16565
17088
  "path": "src/gw-skeleton.ts",
@@ -20371,6 +20894,14 @@
20371
20894
  "module": "src/gw-setting-row.js"
20372
20895
  }
20373
20896
  },
20897
+ {
20898
+ "kind": "js",
20899
+ "name": "*",
20900
+ "declaration": {
20901
+ "name": "*",
20902
+ "module": "src/gw-sheet.js"
20903
+ }
20904
+ },
20374
20905
  {
20375
20906
  "kind": "js",
20376
20907
  "name": "*",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timmo001/groundwork-ui",
3
- "version": "0.9.9",
3
+ "version": "0.9.10",
4
4
  "description": "Groundwork's framework-free web components, styled by the --gw-* tokens",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
package/src/events.ts CHANGED
@@ -35,6 +35,9 @@ export interface GwEventMap {
35
35
  "gw-select": {
36
36
  change: Event;
37
37
  };
38
+ "gw-sheet": {
39
+ close: Event;
40
+ };
38
41
  "gw-theme-gallery": {
39
42
  theme: CustomEvent<{ id: string }>;
40
43
  };
@@ -0,0 +1,307 @@
1
+ /**
2
+ * `gw-sheet`: a panel that slides in from an edge of the screen.
3
+ * Docs: https://groundwork.timmo.dev/components/overlays/#sheet
4
+ *
5
+ * @module
6
+ */
7
+ import { GwElement, css, h, setAttribute } from "./element.js";
8
+ import "./gw-button.js";
9
+ import { base } from "./styles.js";
10
+
11
+ // MDI's chevrons, pointing the way the sheet leaves when it closes
12
+ const chevrons = {
13
+ start: "M15.41,16.58L10.83,12L15.41,7.41L14,6L8,12L14,18L15.41,16.58Z",
14
+ end: "M8.59,16.58L13.17,12L8.59,7.41L10,6L16,12L10,18L8.59,16.58Z",
15
+ bottom: "M7.41,8.58L12,13.17L16.59,8.58L18,10L12,16L6,10L7.41,8.58Z",
16
+ } as const;
17
+
18
+ type Side = keyof typeof chevrons;
19
+
20
+ /** Below this width, `side="auto"` slides up from the bottom. */
21
+ const narrow = "(width < 40rem)";
22
+
23
+ /**
24
+ * A panel of filters, settings or details that slides in from an edge of the
25
+ * screen over the page, rather than taking a place in its layout. With
26
+ * `side="auto"` it comes from the start edge, full height, and from the
27
+ * bottom on narrow screens.
28
+ *
29
+ * It's on the Popover API, in the top layer, and closes on Esc, a click
30
+ * outside or its close button, whose chevron points the way it leaves. Open
31
+ * it with `show()`, or `toggle()` from the button that opens it. Its content
32
+ * scrolls under a header with the heading and an `actions` slot, such as a
33
+ * Clear button.
34
+ *
35
+ * @element gw-sheet
36
+ * @summary A panel that slides in from an edge of the screen.
37
+ *
38
+ * @attr {string} heading - The heading beside the close button.
39
+ * @attr {"auto"|"start"|"end"|"bottom"} side - The edge it slides in from. `auto` is the start edge, or the bottom on narrow screens. Defaults to `auto`.
40
+ *
41
+ * @slot - The content, which scrolls.
42
+ * @slot actions - Buttons in the header, after the heading.
43
+ *
44
+ * @csspart surface - The sheet's surface.
45
+ * @csspart header - The row with the close button, heading and actions.
46
+ * @csspart heading - The heading.
47
+ * @csspart body - The content.
48
+ *
49
+ * @cssprop --gw-sheet-width - Its width from the start or end edge. Defaults to `20rem`.
50
+ * @cssprop --gw-sheet-height - Its greatest height from the bottom. Defaults to `85vh`.
51
+ *
52
+ * @fires {Event} close - When it closes.
53
+ */
54
+ export class GwSheet extends GwElement {
55
+ static override properties = {
56
+ heading: {},
57
+ side: { reflect: true },
58
+ } as const;
59
+
60
+ static override styles = [
61
+ base,
62
+ css`
63
+ [popover] {
64
+ position: fixed;
65
+ box-sizing: border-box;
66
+ flex-direction: column;
67
+ padding: 0;
68
+ margin: 0;
69
+ overflow: hidden;
70
+ color: var(--gw-color-text-primary);
71
+ background: var(--gw-color-surface-overlay);
72
+ border: 0 solid var(--gw-color-border-normal);
73
+ box-shadow: var(--gw-shadow-l);
74
+ transition:
75
+ translate var(--gw-motion-normal) var(--gw-motion-easing),
76
+ display var(--gw-motion-normal) allow-discrete,
77
+ overlay var(--gw-motion-normal) allow-discrete;
78
+ }
79
+
80
+ [popover]:popover-open {
81
+ display: flex;
82
+ translate: 0 0;
83
+ }
84
+
85
+ [popover]::backdrop {
86
+ background: rgb(0 0 0 / 40%);
87
+ }
88
+
89
+ /* Full height from the start or end edge */
90
+ [data-side="start"],
91
+ [data-side="end"] {
92
+ inset: 0 auto;
93
+ width: min(var(--gw-sheet-width, 20rem), 100vw);
94
+ height: 100%;
95
+ max-height: none;
96
+ }
97
+
98
+ [data-side="start"] {
99
+ inset-inline-start: 0;
100
+ border-inline-end-width: max(var(--gw-border-width), 1px);
101
+ translate: -100% 0;
102
+ }
103
+
104
+ [data-side="end"] {
105
+ inset-inline-end: 0;
106
+ border-inline-start-width: max(var(--gw-border-width), 1px);
107
+ translate: 100% 0;
108
+ }
109
+
110
+ :host(:dir(rtl)) [data-side="start"] {
111
+ translate: 100% 0;
112
+ }
113
+
114
+ :host(:dir(rtl)) [data-side="end"] {
115
+ translate: -100% 0;
116
+ }
117
+
118
+ /* Up from the bottom, with room for the phone's home bar */
119
+ [data-side="bottom"] {
120
+ inset: auto 0 0;
121
+ width: 100%;
122
+ max-width: none;
123
+ max-height: var(--gw-sheet-height, 85vh);
124
+ padding-block-end: env(safe-area-inset-bottom);
125
+ border-block-start-width: max(var(--gw-border-width), 1px);
126
+ border-start-start-radius: var(--gw-radius-panel);
127
+ border-start-end-radius: var(--gw-radius-panel);
128
+ translate: 0 100%;
129
+ }
130
+
131
+ @starting-style {
132
+ [data-side="start"]:popover-open {
133
+ translate: -100% 0;
134
+ }
135
+
136
+ [data-side="end"]:popover-open {
137
+ translate: 100% 0;
138
+ }
139
+
140
+ [data-side="bottom"]:popover-open {
141
+ translate: 0 100%;
142
+ }
143
+ }
144
+
145
+ header {
146
+ display: flex;
147
+ flex: none;
148
+ gap: var(--gw-space-2);
149
+ align-items: center;
150
+ padding: var(--gw-space-2) var(--gw-space-3);
151
+ border-block-end: var(--gw-border-width) solid
152
+ var(--gw-color-border-quiet);
153
+ }
154
+
155
+ h2 {
156
+ flex: 1;
157
+ min-width: 0;
158
+ margin: 0;
159
+ font-size: var(--gw-font-size-m);
160
+ font-weight: var(--gw-font-weight-strong);
161
+ color: var(--gw-color-text-strong);
162
+ }
163
+
164
+ .body {
165
+ display: flex;
166
+ flex: 1;
167
+ flex-direction: column;
168
+ gap: var(--gw-space-3);
169
+ min-height: 0;
170
+ padding: var(--gw-space-4);
171
+ /* It scrolls down only, so content reaching into its padding never
172
+ makes it scroll sideways */
173
+ overflow: hidden auto;
174
+ overscroll-behavior: contain;
175
+ scrollbar-width: thin;
176
+ }
177
+ `,
178
+ ];
179
+
180
+ declare heading: string;
181
+ declare side: "auto" | Side;
182
+
183
+ readonly #heading = h("h2", { id: "heading", part: "heading" });
184
+
185
+ readonly #closeButton = h(
186
+ "gw-button",
187
+ {
188
+ variant: "neutral",
189
+ appearance: "plain",
190
+ size: "small",
191
+ labels: "hide",
192
+ },
193
+ "Close",
194
+ );
195
+
196
+ readonly #surface: HTMLDivElement = h(
197
+ "div",
198
+ {
199
+ popover: "auto",
200
+ role: "dialog",
201
+ tabindex: "-1",
202
+ part: "surface",
203
+ },
204
+ h(
205
+ "header",
206
+ { part: "header" },
207
+ this.#closeButton,
208
+ this.#heading,
209
+ h("slot", { name: "actions" }),
210
+ ),
211
+ h("div", { class: "body", part: "body" }, h("slot")),
212
+ );
213
+
214
+ readonly #narrow = window.matchMedia(narrow);
215
+
216
+ // Where focus was before it opened, to go back to when it closes
217
+ #returnFocus: HTMLElement | undefined;
218
+
219
+ constructor() {
220
+ super();
221
+ this.heading = "";
222
+ this.side = "auto";
223
+ }
224
+
225
+ /** Whether it's open. */
226
+ get open() {
227
+ return this.#surface.matches(":popover-open");
228
+ }
229
+
230
+ show() {
231
+ if (this.open) return;
232
+
233
+ const active = document.activeElement;
234
+
235
+ this.#returnFocus = active instanceof HTMLElement ? active : undefined;
236
+ this.#place();
237
+ this.#surface.showPopover();
238
+ }
239
+
240
+ close() {
241
+ if (this.open) this.#surface.hidePopover();
242
+ }
243
+
244
+ /** Opens it, or closes it if it's open, as the button that opens it would. */
245
+ toggle() {
246
+ if (this.open) this.close();
247
+ else this.show();
248
+ }
249
+
250
+ /** The edge it comes from now, with `auto` settled by the screen's width. */
251
+ #edge(): Side {
252
+ if (this.side !== "auto") return this.side;
253
+
254
+ return this.#narrow.matches ? "bottom" : "start";
255
+ }
256
+
257
+ readonly #place = () => {
258
+ const edge = this.#edge();
259
+
260
+ this.#surface.dataset.side = edge;
261
+ this.#closeButton.icon = chevrons[edge];
262
+ };
263
+
264
+ readonly #onToggle = (event: ToggleEvent) => {
265
+ if (event.newState === "open") {
266
+ this.#narrow.addEventListener("change", this.#place);
267
+ this.#closeButton.focus();
268
+
269
+ return;
270
+ }
271
+
272
+ this.#narrow.removeEventListener("change", this.#place);
273
+ this.#returnFocus?.focus();
274
+ this.#returnFocus = undefined;
275
+ this.dispatchEvent(new Event("close", { bubbles: true, composed: true }));
276
+ };
277
+
278
+ override disconnectedCallback() {
279
+ this.#narrow.removeEventListener("change", this.#place);
280
+ super.disconnectedCallback();
281
+ }
282
+
283
+ protected override create(root: ShadowRoot) {
284
+ this.#surface.addEventListener("toggle", this.#onToggle);
285
+ this.#closeButton.addEventListener("click", () => this.close());
286
+ root.append(this.#surface);
287
+ }
288
+
289
+ protected override update() {
290
+ this.#heading.textContent = this.heading;
291
+ setAttribute(
292
+ this.#surface,
293
+ "aria-labelledby",
294
+ this.heading ? "heading" : null,
295
+ );
296
+ this.#heading.hidden = !this.heading;
297
+ this.#place();
298
+ }
299
+ }
300
+
301
+ GwSheet.define("gw-sheet");
302
+
303
+ declare global {
304
+ interface HTMLElementTagNameMap {
305
+ "gw-sheet": GwSheet;
306
+ }
307
+ }
package/src/index.ts CHANGED
@@ -73,6 +73,8 @@ export * from "./gw-select.js";
73
73
 
74
74
  export * from "./gw-setting-row.js";
75
75
 
76
+ export * from "./gw-sheet.js";
77
+
76
78
  export * from "./gw-skeleton.js";
77
79
 
78
80
  export * from "./gw-spinner.js";