@inglorious/ui 8.6.0 → 9.0.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.
Files changed (37) hide show
  1. package/package.json +1 -1
  2. package/src/controls/button/template.js +4 -4
  3. package/src/controls/combobox/style.css +14 -7
  4. package/src/controls/combobox/template.js +12 -2
  5. package/src/controls/rating/style.css +26 -4
  6. package/src/controls/rating/template.js +29 -12
  7. package/src/controls/switch/template.js +3 -1
  8. package/src/data-display/tooltip/style.css +22 -14
  9. package/src/examples/dashboard/primitive-content.js +6 -6
  10. package/src/examples/dashboard/style.css +11 -5
  11. package/src/feedback/dialog/dialog.stories.js +9 -0
  12. package/src/feedback/dialog/style.css +13 -12
  13. package/src/feedback/dialog/template.js +63 -18
  14. package/src/feedback/progress/style.css +47 -23
  15. package/src/feedback/progress/template.js +18 -11
  16. package/src/feedback/snackbar/style.css +13 -0
  17. package/src/feedback/snackbar/template.js +7 -0
  18. package/src/navigation/drawer/drawer.stories.js +3 -7
  19. package/src/navigation/drawer/style.css +59 -18
  20. package/src/navigation/drawer/template.js +137 -80
  21. package/src/navigation/menu/menu.stories.js +15 -0
  22. package/src/navigation/menu/style.css +19 -0
  23. package/src/navigation/menu/template.js +59 -6
  24. package/src/navigation/speed-dial/style.css +32 -5
  25. package/src/navigation/speed-dial/template.js +41 -12
  26. package/src/navigation/tabs/template.js +52 -4
  27. package/src/shared/anchor.js +11 -0
  28. package/src/shared/top-layer.js +83 -0
  29. package/src/surfaces/accordion/style.css +14 -7
  30. package/src/surfaces/accordion/template.js +27 -20
  31. package/types/controls/rating.d.ts +4 -0
  32. package/types/feedback/dialog.d.ts +3 -1
  33. package/types/feedback/progress.d.ts +2 -0
  34. package/types/navigation/drawer.d.ts +8 -3
  35. package/types/navigation/menu.d.ts +17 -0
  36. package/types/navigation/speed-dial.d.ts +5 -0
  37. package/types/surfaces/accordion.d.ts +2 -0
@@ -24,41 +24,65 @@
24
24
  }
25
25
  }
26
26
 
27
+ /* A native <progress>, restyled. Browsers draw its value through their own
28
+ pseudo-elements, so the look is set once here: the bar takes `color`, like
29
+ the color variants above, and an app adjusts the rest through
30
+ --iw-progress-height, --iw-progress-radius and --iw-progress-track-color. */
27
31
  .iw-progress-linear {
28
- width: 100%;
29
- }
30
-
31
- .iw-progress-linear-track {
32
- width: 100%;
33
- height: 8px;
34
- background: color-mix(in srgb, currentColor 24%, transparent);
35
- border-radius: var(--iw-radius-sm);
36
- overflow: hidden;
37
- }
38
-
39
- .iw-progress-linear-bar {
40
- display: block;
41
- height: 100%;
42
- background: linear-gradient(
32
+ --iw-progress-bar: linear-gradient(
43
33
  90deg,
44
34
  color-mix(in srgb, currentColor 84%, white),
45
35
  currentColor
46
36
  );
47
- transition: width 0.2s ease;
48
- min-width: 8px;
49
- }
37
+ --iw-progress-track: var(
38
+ --iw-progress-track-color,
39
+ color-mix(in srgb, currentColor 24%, transparent)
40
+ );
50
41
 
51
- .iw-progress-linear.iw-progress-indeterminate .iw-progress-linear-bar {
52
- width: 30%;
53
- animation: iw-progress-linear 1.4s ease-in-out infinite;
42
+ appearance: none;
43
+ display: block;
44
+ width: 100%;
45
+ height: var(--iw-progress-height, 8px);
46
+ border: 0;
47
+ border-radius: var(--iw-progress-radius, var(--iw-radius-sm));
48
+ overflow: hidden;
49
+ background: var(--iw-progress-track);
50
+
51
+ &::-webkit-progress-bar {
52
+ background: transparent;
53
+ }
54
+
55
+ &::-webkit-progress-value {
56
+ background: var(--iw-progress-bar);
57
+ border-radius: var(--iw-progress-radius, var(--iw-radius-sm));
58
+ transition: width 0.2s ease;
59
+ }
60
+
61
+ &::-moz-progress-bar {
62
+ background: var(--iw-progress-bar);
63
+ border-radius: var(--iw-progress-radius, var(--iw-radius-sm));
64
+ }
65
+
66
+ /* Indeterminate: no value to draw, so a sliver of the bar sweeps across
67
+ the track, painted on the element itself. */
68
+ &:indeterminate {
69
+ background:
70
+ var(--iw-progress-bar) no-repeat 0 0 / 30% 100%,
71
+ var(--iw-progress-track);
72
+ animation: iw-progress-linear 1.4s ease-in-out infinite;
73
+
74
+ &::-moz-progress-bar {
75
+ background: transparent;
76
+ }
77
+ }
54
78
  }
55
79
 
56
80
  @keyframes iw-progress-linear {
57
81
  0% {
58
- transform: translateX(-100%);
82
+ background-position-x: -50%;
59
83
  }
60
84
  100% {
61
- transform: translateX(350%);
85
+ background-position-x: 150%;
62
86
  }
63
87
  }
64
88
 
@@ -3,12 +3,14 @@
3
3
  * @typedef {import('@inglorious/web').TemplateResult} TemplateResult
4
4
  */
5
5
 
6
- import { html } from "@inglorious/web"
6
+ import { html, nothing } from "@inglorious/web"
7
7
  import { classMap } from "@inglorious/web/directives/class-map"
8
8
 
9
9
  /**
10
10
  * Renders a linear or circular progress indicator.
11
11
  * Indeterminate mode is used when no explicit value is provided.
12
+ * The linear bar is a native `<progress>`; the circle, which no native element
13
+ * draws, is an SVG with the same `progressbar` semantics.
12
14
  * @param {ProgressProps} props
13
15
  * @returns {TemplateResult}
14
16
  */
@@ -19,6 +21,7 @@ export function render(props) {
19
21
  size = 40,
20
22
  thickness = 4,
21
23
  color = "default",
24
+ ariaLabel,
22
25
  className = "",
23
26
  } = props
24
27
 
@@ -45,8 +48,13 @@ export function render(props) {
45
48
  ...extraClasses,
46
49
  })}
47
50
  style=${`width: ${size}px; height: ${size}px;`}
51
+ role="progressbar"
52
+ aria-label=${ariaLabel || nothing}
53
+ aria-valuemin="0"
54
+ aria-valuemax="100"
55
+ aria-valuenow=${progress ?? nothing}
48
56
  >
49
- <svg viewBox="0 0 ${size} ${size}">
57
+ <svg aria-hidden="true" viewBox="0 0 ${size} ${size}">
50
58
  <circle
51
59
  class="iw-progress-track"
52
60
  cx=${size / 2}
@@ -69,7 +77,10 @@ export function render(props) {
69
77
 
70
78
  const progress = value == null ? null : clamp(value)
71
79
 
72
- return html`<div
80
+ // An attribute, not a property: removing it is the only way back to
81
+ // indeterminate, which `:indeterminate` styles and assistive tech announces
82
+ // as busy.
83
+ return html`<progress
73
84
  class=${classMap({
74
85
  "iw-progress": true,
75
86
  "iw-progress-linear": true,
@@ -77,14 +88,10 @@ export function render(props) {
77
88
  "iw-progress-indeterminate": progress == null,
78
89
  ...extraClasses,
79
90
  })}
80
- >
81
- <div class="iw-progress-linear-track">
82
- <div
83
- class="iw-progress-linear-bar"
84
- style=${progress == null ? "" : `width: ${progress}%;`}
85
- ></div>
86
- </div>
87
- </div>`
91
+ max="100"
92
+ value=${progress ?? nothing}
93
+ aria-label=${ariaLabel || nothing}
94
+ ></progress>`
88
95
  }
89
96
 
90
97
  function clamp(value) {
@@ -10,6 +10,19 @@
10
10
  max-width: calc(100vw - 2 * var(--iw-snackbar-gap));
11
11
  }
12
12
 
13
+ /* A manual popover in the top layer. The browser centres a popover in the
14
+ viewport as a bordered box, so its insets and box are reset, with no
15
+ specificity so the positions below still set their own sides. */
16
+ :where(.iw-snackbar) {
17
+ inset: auto;
18
+ margin: 0;
19
+ padding: 0;
20
+ border: 0;
21
+ overflow: visible;
22
+ color: inherit;
23
+ background: transparent;
24
+ }
25
+
13
26
  .iw-snackbar-bottom-left,
14
27
  .iw-snackbar-bottom-center,
15
28
  .iw-snackbar-bottom-right {
@@ -5,10 +5,15 @@
5
5
 
6
6
  import { html } from "@inglorious/web"
7
7
  import { classMap } from "@inglorious/web/directives/class-map"
8
+ import { ref } from "@inglorious/web/directives/ref"
9
+
10
+ import { syncPopover } from "../../shared/top-layer.js"
8
11
 
9
12
  /**
10
13
  * Renders a transient snackbar message with optional action and dismiss control.
11
14
  * Position is configurable (for example bottom-left or top-right).
15
+ * A manual popover, so it shows in the top layer, above an open modal dialog,
16
+ * and only `isOpen` or its close button take it down.
12
17
  * @param {SnackbarProps} props
13
18
  * @returns {TemplateResult | null}
14
19
  */
@@ -39,6 +44,8 @@ export function render(props) {
39
44
  })}
40
45
  role="status"
41
46
  aria-live="polite"
47
+ popover="manual"
48
+ ${ref((element) => syncPopover(element, { isOpen: true }))}
42
49
  >
43
50
  <div class="iw-snackbar-content">
44
51
  <span class="iw-snackbar-message">${message}</span>
@@ -19,13 +19,9 @@ export default {
19
19
  },
20
20
  variant: {
21
21
  control: "select",
22
- options: ["temporary", "persistent", "permanent", "responsive"],
23
- description: "Drawer behavior model.",
24
- },
25
- breakpoint: {
26
- control: "select",
27
- options: ["sm", "md", "lg", "xl"],
28
- description: "Desktop breakpoint for responsive drawers.",
22
+ options: ["responsive", "temporary", "permanent"],
23
+ description:
24
+ "When the drawer overlays the page: below 64rem, always, or never.",
29
25
  },
30
26
  isCollapsed: {
31
27
  control: "boolean",
@@ -1,5 +1,16 @@
1
+ /* While it overlays the page the drawer is a popover in the top layer. The
2
+ browser styles every popover as a centred, bordered box hidden until it
3
+ opens, so those defaults are reset here: the drawer stays displayed while
4
+ closed, off-screen or docked, and slides rather than appears. */
1
5
  .iw-drawer {
6
+ display: flex;
7
+ flex-direction: column;
2
8
  width: 16rem;
9
+ height: auto;
10
+ margin: 0;
11
+ padding: 0;
12
+ border: 0;
13
+ overflow: visible;
3
14
  background: var(--iw-drawer-bg, var(--iw-color-surface));
4
15
  color: var(--iw-color-text);
5
16
  transition:
@@ -10,8 +21,7 @@
10
21
  max-width var(--iw-transition-fast);
11
22
 
12
23
  position: fixed;
13
- top: 0;
14
- bottom: 0;
24
+ inset-block: 0;
15
25
  z-index: var(--iw-z-modal, 1050);
16
26
 
17
27
  /* As tall as an app bar, so the line under a drawer and the app bar beside
@@ -105,33 +115,68 @@
105
115
  /* `left` and `right` are the start and end of the line: the left drawer
106
116
  sits on the right in a right-to-left page, and slides off that way. */
107
117
  .iw-drawer-left {
108
- inset-inline-start: 0;
118
+ inset-inline: 0 auto;
109
119
  border-inline-end: 1px solid var(--iw-color-border);
110
120
  transform: translateX(calc(-100% * var(--iw-dir-sign, 1)));
111
121
  }
112
122
 
113
123
  .iw-drawer-right {
114
- inset-inline-end: 0;
124
+ inset-inline: auto 0;
115
125
  border-inline-start: 1px solid var(--iw-color-border);
116
126
  transform: translateX(calc(100% * var(--iw-dir-sign, 1)));
117
127
  }
118
128
 
119
- @media (min-width: 0rem) {
120
- .iw-drawer {
121
- box-shadow: var(--iw-shadow-lg);
129
+ .iw-drawer::backdrop {
130
+ background: color-mix(in srgb, var(--iw-color-black) 65%, transparent);
131
+ backdrop-filter: blur(3px);
132
+ }
122
133
 
123
- &.iw-drawer-open {
124
- transform: translateX(0);
125
- }
134
+ .iw-drawer-no-backdrop::backdrop {
135
+ background: transparent;
136
+ backdrop-filter: none;
137
+ }
126
138
 
127
- .iw-drawer-footer {
128
- display: none;
129
- }
139
+ /* Overlaying: a temporary drawer at every width, a responsive one below
140
+ 64rem. Off-screen until open, raised, closed from its header. The collapse
141
+ toggle only makes sense for a rail, so the footer goes. */
142
+ .iw-drawer {
143
+ box-shadow: var(--iw-shadow-lg);
144
+
145
+ &.iw-drawer-open {
146
+ transform: translateX(0);
147
+ }
148
+
149
+ .iw-drawer-footer {
150
+ display: none;
151
+ }
152
+ }
153
+
154
+ /* Docked: a permanent drawer at every width, a responsive one from 64rem
155
+ (keep in step with DOCKED_QUERY in template.js). In place, flat, and
156
+ hidden only on request. */
157
+ .iw-drawer-permanent {
158
+ transform: none;
159
+ box-shadow: none;
160
+
161
+ &.iw-drawer-left.iw-drawer-hidden {
162
+ transform: translateX(calc(-100% * var(--iw-dir-sign, 1)));
163
+ }
164
+
165
+ &.iw-drawer-right.iw-drawer-hidden {
166
+ transform: translateX(calc(100% * var(--iw-dir-sign, 1)));
167
+ }
168
+
169
+ .iw-drawer-close {
170
+ display: none;
171
+ }
172
+
173
+ .iw-drawer-footer {
174
+ display: flex;
130
175
  }
131
176
  }
132
177
 
133
178
  @media (min-width: 64rem) {
134
- .iw-drawer {
179
+ .iw-drawer-responsive {
135
180
  transform: none;
136
181
  box-shadow: none;
137
182
 
@@ -143,10 +188,6 @@
143
188
  transform: translateX(calc(100% * var(--iw-dir-sign, 1)));
144
189
  }
145
190
 
146
- & ~ .iw-drawer-backdrop {
147
- display: none;
148
- }
149
-
150
191
  .iw-drawer-close {
151
192
  display: none;
152
193
  }
@@ -3,18 +3,38 @@
3
3
  * @typedef {import('@inglorious/web').TemplateResult} TemplateResult
4
4
  */
5
5
 
6
- import { html } from "@inglorious/web"
6
+ import { html, nothing } from "@inglorious/web"
7
+ import { classMap } from "@inglorious/web/directives/class-map"
8
+ import { ref } from "@inglorious/web/directives/ref"
7
9
  import { when } from "@inglorious/web/directives/when"
8
10
 
9
11
  import { Button } from "../../controls/button/index.js"
10
12
  import { Icon } from "../../data-display/icon/index.js"
11
- import { Backdrop } from "../../feedback/backdrop/index.js"
12
13
  import { Flex } from "../../layout/flex/index.js"
14
+ import { applyElementProps } from "../../shared/apply-element-props.js"
13
15
  import { close, leftPanelClose } from "../../shared/icons.js"
16
+ import { syncPopover } from "../../shared/top-layer.js"
17
+
18
+ /** Where a responsive drawer docks. Keep in step with style.css. */
19
+ const DOCKED_QUERY = "(min-width: 64rem)"
20
+
21
+ /**
22
+ * Latest props of each responsive drawer, so they can be re-synced when the
23
+ * viewport crosses the breakpoint without anything re-rendering.
24
+ * @type {Map<HTMLElement, DrawerProps>}
25
+ */
26
+ const responsiveDrawers = new Map()
27
+
28
+ /** @type {MediaQueryList | undefined} */
29
+ let dockedMedia
14
30
 
15
31
  /**
16
32
  * Renders a side drawer panel with optional header, body, and collapse controls.
17
- * Can show a backdrop when open and supports left/right anchoring.
33
+ * While it overlays the page it is a native popover: it sits in the top layer
34
+ * over a `::backdrop`, and Esc or a press outside it (the backdrop included)
35
+ * closes it.
36
+ * `variant` sets when it overlays: `responsive` below 64rem and docked above,
37
+ * `temporary` at every width, `permanent` never.
18
38
  * @param {DrawerProps} props
19
39
  * @returns {TemplateResult}
20
40
  */
@@ -25,6 +45,7 @@ export function render(props) {
25
45
  isOpen = false,
26
46
  isHidden = false,
27
47
  anchor = "left",
48
+ variant = "responsive",
28
49
  isCollapsed = false,
29
50
  hasBackdrop = true,
30
51
  title,
@@ -42,89 +63,125 @@ export function render(props) {
42
63
  .map((name) => [name, true]),
43
64
  )
44
65
 
45
- const drawerClassName = [
46
- "iw-drawer",
47
- `iw-drawer-${anchor}`,
48
- isOpen && "iw-drawer-open",
49
- isHidden && "iw-drawer-hidden",
50
- isCollapsed && "iw-drawer-collapsed",
51
- ...Object.keys(extraClasses),
52
- ]
53
- .filter(Boolean)
54
- .join(" ")
55
-
56
- return html`
57
- ${Flex.render({
58
- element: "aside",
59
- direction: "column",
60
- className: drawerClassName,
61
- children: [
62
- when(
63
- title || onClose,
64
- () => html`
65
- ${Flex.render({
66
- align: "center",
67
- justify: "between",
68
- gap: "sm",
69
- padding: "sm",
70
- className: "iw-drawer-header",
71
- children: [
72
- when(
73
- title,
74
- () => html`<div class="iw-drawer-title">${title}</div>`,
75
- ),
76
- when(onClose, () =>
77
- Button.render({
78
- color: "secondary",
79
- variant: "ghost",
80
- shape: "square",
81
- className: "iw-drawer-close",
82
- "aria-label": "Close",
83
- onClick: onClose,
84
- children: Icon.render({ icon: close, size: "lg" }),
85
- }),
86
- ),
87
- ],
88
- })}
89
- `,
90
- ),
66
+ const classes = {
67
+ "iw-drawer": true,
68
+ [`iw-drawer-${anchor}`]: true,
69
+ [`iw-drawer-${variant}`]: true,
70
+ "iw-drawer-open": isOpen,
71
+ "iw-drawer-hidden": isHidden,
72
+ "iw-drawer-collapsed": isCollapsed,
73
+ "iw-drawer-no-backdrop": !hasBackdrop,
74
+ ...extraClasses,
75
+ }
91
76
 
92
- Flex.render({
93
- direction: "column",
94
- className: "iw-drawer-body",
95
- children,
96
- }),
77
+ // Without onClose nothing would take isOpen down, so the browser is not
78
+ // allowed to dismiss it: a manual popover ignores Esc and outside presses.
79
+ const popover =
80
+ variant === "permanent" ? nothing : onClose ? "auto" : "manual"
97
81
 
98
- when(
99
- onCollapseToggle,
100
- () => html`
101
- ${Flex.render({
102
- justify: "end",
103
- padding: "sm",
104
- className: "iw-drawer-footer",
105
- children: Button.render({
82
+ return html`<aside
83
+ class=${classMap(classes)}
84
+ popover=${popover}
85
+ ${ref((element) => {
86
+ if (!element) return
87
+ applyElementProps(element, rest)
88
+ sync(element, { isOpen, variant, onClose })
89
+ })}
90
+ >
91
+ ${when(
92
+ title || onClose,
93
+ () => html`
94
+ ${Flex.render({
95
+ align: "center",
96
+ justify: "between",
97
+ gap: "sm",
98
+ padding: "sm",
99
+ className: "iw-drawer-header",
100
+ children: [
101
+ when(
102
+ title,
103
+ () => html`<div class="iw-drawer-title">${title}</div>`,
104
+ ),
105
+ when(onClose, () =>
106
+ Button.render({
106
107
  color: "secondary",
107
108
  variant: "ghost",
108
109
  shape: "square",
109
- className: "iw-drawer-toggler",
110
- "aria-label": isCollapsed
111
- ? "Expand sidebar"
112
- : "Collapse sidebar",
113
- onClick: onCollapseToggle,
114
- children: Icon.render({ icon: leftPanelClose, size: "lg" }),
110
+ className: "iw-drawer-close",
111
+ "aria-label": "Close",
112
+ onClick: onClose,
113
+ children: Icon.render({ icon: close, size: "lg" }),
115
114
  }),
116
- })}
117
- `,
118
- ),
119
- ],
120
- ...rest,
115
+ ),
116
+ ],
117
+ })}
118
+ `,
119
+ )}
120
+ ${Flex.render({
121
+ direction: "column",
122
+ className: "iw-drawer-body",
123
+ children,
121
124
  })}
122
- ${when(isOpen && hasBackdrop, () =>
123
- Backdrop.render({
124
- isOpen: true,
125
- className: "iw-drawer-backdrop",
126
- onClick: onClose,
127
- }),
125
+ ${when(
126
+ onCollapseToggle,
127
+ () => html`
128
+ ${Flex.render({
129
+ justify: "end",
130
+ padding: "sm",
131
+ className: "iw-drawer-footer",
132
+ children: Button.render({
133
+ color: "secondary",
134
+ variant: "ghost",
135
+ shape: "square",
136
+ className: "iw-drawer-toggler",
137
+ "aria-label": isCollapsed ? "Expand sidebar" : "Collapse sidebar",
138
+ onClick: onCollapseToggle,
139
+ children: Icon.render({ icon: leftPanelClose, size: "lg" }),
140
+ }),
141
+ })}
142
+ `,
128
143
  )}
129
- `
144
+ </aside>`
145
+ }
146
+
147
+ /**
148
+ * Shows the popover only while the drawer overlays the page, and remembers a
149
+ * responsive drawer so a breakpoint crossing can re-sync it.
150
+ * @param {HTMLElement} element
151
+ * @param {Pick<DrawerProps, "isOpen" | "variant" | "onClose">} props
152
+ */
153
+ function sync(element, props) {
154
+ if (props.variant === "responsive") {
155
+ watchDockedMedia()
156
+ responsiveDrawers.set(element, props)
157
+ } else {
158
+ responsiveDrawers.delete(element)
159
+ }
160
+
161
+ syncPopover(element, {
162
+ isOpen: props.isOpen && isOverlay(props.variant),
163
+ onClose: props.onClose,
164
+ })
165
+ }
166
+
167
+ function isOverlay(variant) {
168
+ if (variant === "permanent") return false
169
+ if (variant === "temporary") return true
170
+ return !dockedMedia?.matches
171
+ }
172
+
173
+ /**
174
+ * Listens once for the breakpoint. Widening the window docks an open
175
+ * responsive drawer, which then has to leave the top layer: a silent hide,
176
+ * since nothing asked the store to close it.
177
+ */
178
+ function watchDockedMedia() {
179
+ if (dockedMedia || !globalThis.matchMedia) return
180
+ dockedMedia = globalThis.matchMedia(DOCKED_QUERY)
181
+ dockedMedia.addEventListener("change", () => {
182
+ for (const [element, props] of responsiveDrawers) {
183
+ if (element.isConnected) sync(element, props)
184
+ else responsiveDrawers.delete(element)
185
+ }
186
+ })
130
187
  }
@@ -12,6 +12,11 @@ export default {
12
12
  },
13
13
  items: { control: "object", description: "Menu items and separators." },
14
14
  isDense: { control: "boolean", description: "Compact item spacing." },
15
+ trigger: {
16
+ control: "text",
17
+ description: "Content of a button that opens the menu as a popover.",
18
+ },
19
+ onToggle: { action: "onToggle" },
15
20
  onItemClick: { action: "onItemClick" },
16
21
  },
17
22
  parameters: {
@@ -34,3 +39,13 @@ export const Default = {
34
39
  ],
35
40
  },
36
41
  }
42
+
43
+ // Anchored to its trigger. Without onToggle wired to a store, the browser
44
+ // keeps the open state on its own.
45
+ export const WithTrigger = {
46
+ args: {
47
+ id: "story-menu",
48
+ trigger: "Actions",
49
+ items: Default.args.items,
50
+ },
51
+ }
@@ -3,12 +3,31 @@
3
3
  flex-direction: column;
4
4
  min-width: 12rem;
5
5
  padding: var(--iw-space-2);
6
+ color: var(--iw-color-text);
6
7
  background: var(--iw-color-surface);
7
8
  border: 1px solid var(--iw-color-border);
8
9
  border-radius: var(--iw-radius-md);
9
10
  box-shadow: var(--iw-shadow-lg);
10
11
  }
11
12
 
13
+ /* Anchored to its trigger: below it and aligned with its start, flipping up
14
+ or across when there is no room. The browser's popover box is centred in
15
+ the viewport, so its insets and margins are reset; its hidden state has to
16
+ be restated, because the display above outranks it. */
17
+ .iw-menu[popover] {
18
+ inset: auto;
19
+ margin: var(--iw-space-1) 0 0;
20
+ position-area: block-end span-inline-end;
21
+ position-try-fallbacks:
22
+ flip-block,
23
+ flip-inline,
24
+ flip-block flip-inline;
25
+
26
+ &:not(:popover-open) {
27
+ display: none;
28
+ }
29
+ }
30
+
12
31
  .iw-menu-item {
13
32
  border: 0 none;
14
33
  background: transparent;