@statistikzh/leu 0.19.2 → 0.20.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 (95) hide show
  1. package/.release-please-manifest.json +1 -1
  2. package/CHANGELOG.md +7 -0
  3. package/dist/Accordion.js +1 -1
  4. package/dist/Button.js +1 -1
  5. package/dist/ButtonGroup.js +1 -1
  6. package/dist/ChartWrapper.js +1 -1
  7. package/dist/Checkbox.js +1 -1
  8. package/dist/CheckboxGroup.js +1 -1
  9. package/dist/Chip.js +1 -1
  10. package/dist/ChipGroup.js +1 -1
  11. package/dist/ChipLink.js +1 -1
  12. package/dist/ChipRemovable.js +1 -1
  13. package/dist/ChipSelectable.js +1 -1
  14. package/dist/Dialog.js +1 -1
  15. package/dist/Dropdown.d.ts +1 -0
  16. package/dist/Dropdown.js +1 -1
  17. package/dist/FileInput.d.ts +2 -1
  18. package/dist/FileInput.js +1 -1
  19. package/dist/Icon.js +1 -1
  20. package/dist/Input.js +1 -1
  21. package/dist/{LeuElement-CcarVabH.js → LeuElement-C1c3TgrG.js} +1 -1
  22. package/dist/Menu.d.ts +2 -1
  23. package/dist/Menu.js +1 -1
  24. package/dist/MenuItem.js +1 -1
  25. package/dist/Message.js +1 -1
  26. package/dist/Pagination.js +1 -1
  27. package/dist/Placeholder.js +1 -1
  28. package/dist/Popup.d.ts +22 -46
  29. package/dist/Popup.js +40 -31
  30. package/dist/ProgressBar.js +1 -1
  31. package/dist/Radio.js +1 -1
  32. package/dist/RadioGroup.js +1 -1
  33. package/dist/Range.js +1 -1
  34. package/dist/ScrollTop.js +1 -1
  35. package/dist/Select.d.ts +3 -2
  36. package/dist/Select.js +1 -1
  37. package/dist/Spinner.js +1 -1
  38. package/dist/Table.js +1 -1
  39. package/dist/Tag.js +1 -1
  40. package/dist/VisuallyHidden.js +1 -1
  41. package/dist/components/file-input/FileInput.d.ts +2 -1
  42. package/dist/components/file-input/FileInput.d.ts.map +1 -1
  43. package/dist/components/menu/Menu.d.ts +2 -1
  44. package/dist/components/menu/Menu.d.ts.map +1 -1
  45. package/dist/components/popup/Popup.d.ts +21 -46
  46. package/dist/components/popup/Popup.d.ts.map +1 -1
  47. package/dist/components/popup/stories/popup.stories.d.ts +9 -0
  48. package/dist/components/popup/stories/popup.stories.d.ts.map +1 -1
  49. package/dist/components/select/Select.d.ts +2 -2
  50. package/dist/components/select/Select.d.ts.map +1 -1
  51. package/dist/index.js +1 -1
  52. package/dist/leu-accordion.js +1 -1
  53. package/dist/leu-button-group.js +1 -1
  54. package/dist/leu-button.js +1 -1
  55. package/dist/leu-chart-wrapper.js +1 -1
  56. package/dist/leu-checkbox-group.js +1 -1
  57. package/dist/leu-checkbox.js +1 -1
  58. package/dist/leu-chip-group.js +1 -1
  59. package/dist/leu-chip-link.js +1 -1
  60. package/dist/leu-chip-removable.js +1 -1
  61. package/dist/leu-chip-selectable.js +1 -1
  62. package/dist/leu-dialog.js +1 -1
  63. package/dist/leu-dropdown.d.ts +1 -0
  64. package/dist/leu-dropdown.js +1 -1
  65. package/dist/leu-file-input.js +1 -1
  66. package/dist/leu-icon.js +1 -1
  67. package/dist/leu-input.js +1 -1
  68. package/dist/leu-menu-item.js +1 -1
  69. package/dist/leu-menu.js +1 -1
  70. package/dist/leu-message.js +1 -1
  71. package/dist/leu-pagination.js +1 -1
  72. package/dist/leu-placeholder.js +1 -1
  73. package/dist/leu-popup.d.ts +1 -0
  74. package/dist/leu-popup.js +3 -1
  75. package/dist/leu-progress-bar.js +1 -1
  76. package/dist/leu-radio-group.js +1 -1
  77. package/dist/leu-radio.js +1 -1
  78. package/dist/leu-range.js +1 -1
  79. package/dist/leu-scroll-top.js +1 -1
  80. package/dist/leu-select.d.ts +1 -0
  81. package/dist/leu-select.js +1 -1
  82. package/dist/leu-spinner.js +1 -1
  83. package/dist/leu-table.js +1 -1
  84. package/dist/leu-tag.js +1 -1
  85. package/dist/leu-visually-hidden.js +1 -1
  86. package/dist/vscode.html-custom-data.json +16 -12
  87. package/dist/vue/index.d.ts +20 -26
  88. package/dist/web-types.json +39 -47
  89. package/package.json +1 -1
  90. package/src/components/file-input/FileInput.ts +2 -2
  91. package/src/components/menu/Menu.ts +2 -2
  92. package/src/components/popup/Popup.ts +49 -44
  93. package/src/components/popup/stories/popup.stories.ts +49 -0
  94. package/src/components/popup/test/popup.test.ts +39 -3
  95. package/src/components/select/Select.ts +2 -2
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://raw.githubusercontent.com/JetBrains/web-types/master/schema/web-types.json",
3
3
  "name": "@statistikzh/leu",
4
- "version": "0.19.2",
4
+ "version": "0.20.0",
5
5
  "description-markup": "markdown",
6
6
  "contributions": {
7
7
  "html": {
@@ -65,6 +65,33 @@
65
65
  "events": []
66
66
  }
67
67
  },
68
+ {
69
+ "name": "leu-button-group",
70
+ "description": "A radio input-like button group component.\nIt allows only one button to be active at a time.\n---\n\n\n### **Events:**\n - **input** - When the value of the group changes by clicking a button\n\n### **Slots:**\n - _default_ - Slot for the buttons",
71
+ "doc-url": "",
72
+ "attributes": [],
73
+ "slots": [{ "name": "", "description": "Slot for the buttons" }],
74
+ "events": [
75
+ {
76
+ "name": "input",
77
+ "description": "When the value of the group changes by clicking a button"
78
+ }
79
+ ],
80
+ "js": {
81
+ "properties": [
82
+ {
83
+ "name": "value",
84
+ "description": "The value of the currently selected (active) button"
85
+ }
86
+ ],
87
+ "events": [
88
+ {
89
+ "name": "input",
90
+ "description": "When the value of the group changes by clicking a button"
91
+ }
92
+ ]
93
+ }
94
+ },
68
95
  {
69
96
  "name": "leu-button",
70
97
  "description": "\n---\n\n\n### **Slots:**\n - **before** - The icon to display before the label\n- **after** - The icon to display after the label\n- _default_ - The label of the button or the icon if no label is set",
@@ -211,33 +238,6 @@
211
238
  "events": []
212
239
  }
213
240
  },
214
- {
215
- "name": "leu-button-group",
216
- "description": "A radio input-like button group component.\nIt allows only one button to be active at a time.\n---\n\n\n### **Events:**\n - **input** - When the value of the group changes by clicking a button\n\n### **Slots:**\n - _default_ - Slot for the buttons",
217
- "doc-url": "",
218
- "attributes": [],
219
- "slots": [{ "name": "", "description": "Slot for the buttons" }],
220
- "events": [
221
- {
222
- "name": "input",
223
- "description": "When the value of the group changes by clicking a button"
224
- }
225
- ],
226
- "js": {
227
- "properties": [
228
- {
229
- "name": "value",
230
- "description": "The value of the currently selected (active) button"
231
- }
232
- ],
233
- "events": [
234
- {
235
- "name": "input",
236
- "description": "When the value of the group changes by clicking a button"
237
- }
238
- ]
239
- }
240
- },
241
241
  {
242
242
  "name": "leu-chart-wrapper",
243
243
  "description": "A wrapper element for charts.\n---\n\n\n### **Slots:**\n - **title** - The title of the chart. Use a heading tag (h2-4) depending on your context.\n- **description** - A description of the chart. Content is wrapped in a `<p>` tag by the component.\n- **chart** - The actual chart\n- **caption** - A caption for the chart, e.g. a source or explanation of the data.\n- **download** - A download button or dropdown to export the chart in different formats.",
@@ -1211,10 +1211,7 @@
1211
1211
  "attributes": [
1212
1212
  {
1213
1213
  "name": "anchor",
1214
- "value": {
1215
- "type": "string | HTMLElement",
1216
- "default": "undefined"
1217
- }
1214
+ "value": { "type": "Element | string | VirtualElement" }
1218
1215
  },
1219
1216
  {
1220
1217
  "name": "active",
@@ -1222,7 +1219,7 @@
1222
1219
  },
1223
1220
  {
1224
1221
  "name": "placement",
1225
- "value": { "type": "Placement", "default": "undefined" }
1222
+ "value": { "type": "Placement | undefined" }
1226
1223
  },
1227
1224
  {
1228
1225
  "name": "flip",
@@ -1239,15 +1236,13 @@
1239
1236
  {
1240
1237
  "name": "matchSize",
1241
1238
  "value": {
1242
- "type": "\"width\" | \"height\" | \"both\"",
1243
- "default": "undefined"
1239
+ "type": "\"width\" | \"height\" | \"both\" | undefined"
1244
1240
  }
1245
1241
  },
1246
1242
  {
1247
1243
  "name": "autoSize",
1248
1244
  "value": {
1249
- "type": "\"width\" | \"height\" | \"both\"",
1250
- "default": "undefined"
1245
+ "type": "| \"width\"\n | \"height\"\n | \"both\" | undefined"
1251
1246
  }
1252
1247
  },
1253
1248
  {
@@ -1258,24 +1253,21 @@
1258
1253
  "events": [],
1259
1254
  "js": {
1260
1255
  "properties": [
1261
- { "name": "popupEl" },
1262
- { "name": "anchorEl", "type": "null" },
1263
- { "name": "cleanup" },
1256
+ { "name": "anchor", "type": "Element | string | VirtualElement" },
1257
+ { "name": "active", "type": "boolean" },
1258
+ { "name": "placement", "type": "Placement | undefined" },
1264
1259
  { "name": "flip", "type": "boolean" },
1265
1260
  { "name": "shift", "type": "boolean" },
1266
- { "name": "active", "type": "boolean" },
1267
- { "name": "placement", "type": "Placement" },
1261
+ { "name": "shiftPadding", "type": "number" },
1268
1262
  {
1269
1263
  "name": "matchSize",
1270
- "type": "\"width\" | \"height\" | \"both\""
1264
+ "type": "\"width\" | \"height\" | \"both\" | undefined"
1271
1265
  },
1272
1266
  {
1273
1267
  "name": "autoSize",
1274
- "type": "\"width\" | \"height\" | \"both\""
1268
+ "type": "| \"width\"\n | \"height\"\n | \"both\" | undefined"
1275
1269
  },
1276
- { "name": "shiftPadding", "type": "number" },
1277
- { "name": "autoSizePadding", "type": "number" },
1278
- { "name": "anchor", "type": "string | HTMLElement" }
1270
+ { "name": "autoSizePadding", "type": "number" }
1279
1271
  ],
1280
1272
  "events": []
1281
1273
  }
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "UI component library of the canton of zurich",
4
4
  "license": "MIT",
5
5
  "author": "statistikzh",
6
- "version": "0.19.2",
6
+ "version": "0.20.0",
7
7
  "type": "module",
8
8
  "main": "dist/index.js",
9
9
  "module": "dist/index.js",
@@ -1,4 +1,4 @@
1
- import { html, nothing } from "lit"
1
+ import { html, nothing, PropertyValues } from "lit"
2
2
  import { property, query, state } from "lit/decorators.js"
3
3
  import { ifDefined } from "lit/directives/if-defined.js"
4
4
  import { classMap } from "lit/directives/class-map.js"
@@ -82,7 +82,7 @@ export class LeuFileInput extends LeuElement {
82
82
  return this.getAttribute("name")
83
83
  }
84
84
 
85
- updated(changedProperties) {
85
+ updated(changedProperties: PropertyValues<this>) {
86
86
  if (
87
87
  changedProperties.has("files") ||
88
88
  changedProperties.has("disabled") ||
@@ -1,4 +1,4 @@
1
- import { html } from "lit"
1
+ import { html, PropertyValues } from "lit"
2
2
 
3
3
  import { LeuElement } from "../../lib/LeuElement.js"
4
4
 
@@ -142,7 +142,7 @@ export class LeuMenu extends LeuElement {
142
142
  this.setCurrentItem(0)
143
143
  }
144
144
 
145
- updated(changedProperties) {
145
+ updated(changedProperties: PropertyValues<this>) {
146
146
  if (changedProperties.has("selects")) {
147
147
  this._setMenuItemRoles()
148
148
  }
@@ -1,8 +1,10 @@
1
- import { html } from "lit"
1
+ import { html, PropertyValues } from "lit"
2
+ import { property } from "lit/decorators.js"
2
3
  import {
3
4
  autoUpdate,
4
5
  computePosition,
5
6
  flip,
7
+ Placement,
6
8
  shift,
7
9
  size,
8
10
  } from "@floating-ui/dom"
@@ -11,9 +13,19 @@ import { LeuElement } from "../../lib/LeuElement.js"
11
13
 
12
14
  import styles from "./popup.css"
13
15
 
14
- /**
15
- * @typedef {"top"|"top-start"|"top-end"|"bottom"|"bottom-start"|"bottom-end"|"left"|"left-start"|"left-end"|"right"|"right-start"|"right-end"} Placement
16
- */
16
+ export interface VirtualElement {
17
+ getBoundingClientRect: () => DOMRect
18
+ contextElement?: Element
19
+ }
20
+
21
+ function isVirtualElement(el: unknown): el is VirtualElement {
22
+ return (
23
+ el !== null &&
24
+ typeof el === "object" &&
25
+ "getBoundingClientRect" in el &&
26
+ ("contextElement" in el ? el instanceof Element : true)
27
+ )
28
+ }
17
29
 
18
30
  /**
19
31
  * @tagname leu-popup
@@ -26,50 +38,43 @@ export class LeuPopup extends LeuElement {
26
38
  delegatesFocus: true,
27
39
  }
28
40
 
29
- static properties = {
30
- anchor: {},
31
- active: { type: Boolean, reflect: true },
32
- placement: { type: String, reflect: true },
33
- flip: { type: Boolean, reflect: true },
34
- shift: { type: Boolean, reflect: true },
35
- shiftPadding: { type: Number, reflect: true },
36
- matchSize: { type: String, reflect: true },
37
- autoSize: { type: String, reflect: true },
38
- autoSizePadding: { type: Number, reflect: true },
39
- }
41
+ @property() anchor: Element | string | VirtualElement
40
42
 
41
- constructor() {
42
- super()
43
+ @property({ type: Boolean, reflect: true })
44
+ active: boolean = false
43
45
 
44
- this.anchorEl = null
45
- this.cleanup = undefined
46
- this.flip = false
47
- this.shift = false
46
+ @property({ type: String, reflect: true })
47
+ placement?: Placement
48
48
 
49
- this.active = false
49
+ @property({ type: Boolean, reflect: true })
50
+ flip: boolean = false
50
51
 
51
- /** @type {Placement} */
52
- this.placement = undefined
52
+ @property({ type: Boolean, reflect: true })
53
+ shift: boolean = false
53
54
 
54
- /** @type {"width" | "height" | "both"} */
55
- this.matchSize = undefined
55
+ @property({ type: Number, reflect: true })
56
+ shiftPadding: number = 0
56
57
 
57
- /** @type {"width" | "height" | "both"} */
58
- this.autoSize = undefined
58
+ @property({ type: String, reflect: true })
59
+ matchSize?: "width" | "height" | "both"
59
60
 
60
- this.shiftPadding = 0
61
- this.autoSizePadding = 0
61
+ @property({ type: String, reflect: true }) autoSize?:
62
+ | "width"
63
+ | "height"
64
+ | "both"
62
65
 
63
- /** @type {string | HTMLElement} */
64
- this.anchor = undefined
65
- }
66
+ @property({ type: Number, reflect: true }) autoSizePadding: number = 0
67
+
68
+ private anchorEl: Element | null
69
+
70
+ private cleanup: ReturnType<typeof autoUpdate> | undefined
66
71
 
67
72
  disconnectedCallback() {
68
73
  super.disconnectedCallback()
69
74
  this.stop()
70
75
  }
71
76
 
72
- updated(changedProperties) {
77
+ updated(changedProperties: PropertyValues<this>) {
73
78
  if (changedProperties.has("active")) {
74
79
  if (this.active) {
75
80
  this.start()
@@ -87,14 +92,11 @@ export class LeuPopup extends LeuElement {
87
92
  }
88
93
  }
89
94
 
90
- /**
91
- * @returns {HTMLElement | null}
92
- */
93
- get popupEl() {
94
- return this.renderRoot?.querySelector(".popup") ?? null
95
+ protected get popupEl() {
96
+ return this.renderRoot?.querySelector<HTMLDivElement>(".popup") ?? null
95
97
  }
96
98
 
97
- start() {
99
+ protected start() {
98
100
  if (!this.anchorEl || !this.active) return
99
101
 
100
102
  this.cleanup = autoUpdate(this.anchorEl, this.popupEl, () => {
@@ -102,14 +104,14 @@ export class LeuPopup extends LeuElement {
102
104
  })
103
105
  }
104
106
 
105
- stop() {
107
+ protected stop() {
106
108
  this.cleanup?.()
107
109
 
108
110
  this.style.removeProperty("--auto-size-available-width")
109
111
  this.style.removeProperty("--auto-size-available-height")
110
112
  }
111
113
 
112
- reposition() {
114
+ public reposition() {
113
115
  if (!this.anchorEl || !this.popupEl || !this.active) return
114
116
 
115
117
  const middleware = []
@@ -194,9 +196,12 @@ export class LeuPopup extends LeuElement {
194
196
 
195
197
  handleAnchorChange() {
196
198
  if (this.anchor && typeof this.anchor === "string") {
197
- const root = this.getRootNode()
199
+ const root = this.getRootNode() as Document | ShadowRoot
198
200
  this.anchorEl = root.getElementById(this.anchor)
199
- } else if (this.anchor instanceof HTMLElement) {
201
+ } else if (
202
+ this.anchor instanceof HTMLElement ||
203
+ isVirtualElement(this.anchor)
204
+ ) {
200
205
  this.anchorEl = this.anchor
201
206
  } else {
202
207
  this.anchorEl = this.querySelector("[slot=anchor]")
@@ -56,3 +56,52 @@ Regular.args = {
56
56
  flip: true,
57
57
  shift: true,
58
58
  }
59
+
60
+ export const VirtualElement = {
61
+ render: (args = {}) => html`
62
+ <leu-popup
63
+ ?active=${args.active}
64
+ ?flip=${args.flip}
65
+ ?shift=${args.shift}
66
+ placement=${ifDefined(args.placement)}
67
+ >
68
+ <div style=${styleMap(popupStyles)}>Popup content</div>
69
+ </leu-popup>
70
+ <script>
71
+ const body = document.body
72
+ const popup = document.querySelector("leu-popup")
73
+ let clientX = 0
74
+ let clientY = 0
75
+
76
+ body.style.height = "100vh"
77
+
78
+ popup.anchor = {
79
+ getBoundingClientRect() {
80
+ return {
81
+ width: 0,
82
+ height: 0,
83
+ x: clientX,
84
+ y: clientY,
85
+ top: clientY,
86
+ left: clientX,
87
+ right: clientX,
88
+ bottom: clientY,
89
+ }
90
+ },
91
+ }
92
+
93
+ body.addEventListener("mousemove", (event) => {
94
+ clientX = event.clientX
95
+ clientY = event.clientY
96
+
97
+ popup.reposition()
98
+ })
99
+ </script>
100
+ `,
101
+ args: {
102
+ active: true,
103
+ placement: "bottom-start",
104
+ flip: true,
105
+ shift: true,
106
+ },
107
+ }
@@ -2,9 +2,10 @@ import { html } from "lit"
2
2
  import { fixture, expect } from "@open-wc/testing"
3
3
 
4
4
  import "../leu-popup.js"
5
+ import { LeuPopup } from "../Popup.js"
5
6
 
6
7
  async function defaultFixture() {
7
- return fixture(html`
8
+ return fixture<LeuPopup>(html`
8
9
  <leu-popup
9
10
  ><div slot="anchor"></div>
10
11
  <p>Popup content</p></leu-popup
@@ -14,9 +15,9 @@ async function defaultFixture() {
14
15
 
15
16
  describe("LeuPopup", () => {
16
17
  it("is a defined element", async () => {
17
- const el = await customElements.get("leu-popup")
18
+ const el = customElements.get("leu-popup")
18
19
 
19
- await expect(el).not.to.be.undefined
20
+ expect(el).not.to.be.undefined
20
21
  })
21
22
 
22
23
  it("passes the a11y audit", async () => {
@@ -24,4 +25,39 @@ describe("LeuPopup", () => {
24
25
 
25
26
  await expect(el).shadowDom.to.be.accessible()
26
27
  })
28
+
29
+ it("moves content to the position of a virtual element", async () => {
30
+ const popup = await fixture<LeuPopup>(
31
+ html`<leu-popup ?active=${true} placement="bottom-start"
32
+ ><div slot="anchor"></div>
33
+ <div
34
+ style="background: white; border: 1px solid black; padding: 0.5rem"
35
+ >
36
+ Popup content
37
+ </div></leu-popup
38
+ >
39
+ <style>
40
+ * {
41
+ margin: 0;
42
+ padding: 0;
43
+ }
44
+ </style> `,
45
+ )
46
+
47
+ popup.anchor = {
48
+ getBoundingClientRect: () => new DOMRect(32, 125, 0, 0),
49
+ }
50
+
51
+ popup.reposition()
52
+
53
+ await new Promise<void>((resolve) => {
54
+ requestAnimationFrame(() => resolve())
55
+ })
56
+
57
+ const popupContent =
58
+ popup.shadowRoot!.querySelector<HTMLDivElement>(".popup")
59
+
60
+ expect(popupContent.style.left).to.equal("32px")
61
+ expect(popupContent.style.top).to.equal("125px")
62
+ })
27
63
  })
@@ -1,4 +1,4 @@
1
- import { html, nothing } from "lit"
1
+ import { html, nothing, PropertyValues } from "lit"
2
2
  import { classMap } from "lit/directives/class-map.js"
3
3
  import { createRef, ref } from "lit/directives/ref.js"
4
4
 
@@ -128,7 +128,7 @@ export class LeuSelect extends LeuElement {
128
128
  document.removeEventListener("click", this._handleDocumentClick)
129
129
  }
130
130
 
131
- updated(changedProperties) {
131
+ updated(changedProperties: PropertyValues<this>) {
132
132
  if (changedProperties.has("open") && this.open) {
133
133
  if (this.filterable) {
134
134
  this._optionFilterRef.value.focus()