@limetech/lime-elements 40.11.3 → 40.12.1

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 (73) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/cjs/{checkbox.template-CoNVC4Nf.js → checkbox.template-_WUgtMJy.js} +2 -2
  3. package/dist/cjs/lime-elements.cjs.js +1 -1
  4. package/dist/cjs/limel-breadcrumbs_6.cjs.entry.js +5 -10
  5. package/dist/cjs/limel-checkbox.cjs.entry.js +1 -1
  6. package/dist/cjs/limel-chip-set.cjs.entry.js +8 -3
  7. package/dist/cjs/limel-code-editor.cjs.entry.js +1 -2
  8. package/dist/cjs/limel-color-picker.cjs.entry.js +1 -1
  9. package/dist/cjs/limel-file-viewer.cjs.entry.js +81 -47
  10. package/dist/cjs/limel-form.cjs.entry.js +7 -7
  11. package/dist/cjs/limel-helper-line_3.cjs.entry.js +3 -3
  12. package/dist/cjs/limel-list-item.cjs.entry.js +1 -1
  13. package/dist/cjs/limel-select.cjs.entry.js +8 -3
  14. package/dist/cjs/limel-side-panel.cjs.entry.js +72 -5
  15. package/dist/cjs/limel-text-editor.cjs.entry.js +2 -2
  16. package/dist/cjs/loader.cjs.js +1 -1
  17. package/dist/collection/components/checkbox/checkbox.template.js +2 -2
  18. package/dist/collection/components/chip-set/chip-set.js +8 -3
  19. package/dist/collection/components/code-editor/code-editor.js +1 -2
  20. package/dist/collection/components/color-picker/color-picker.js +3 -3
  21. package/dist/collection/components/date-picker/date-picker.js +2 -2
  22. package/dist/collection/components/helper-line/helper-line.css +10 -14
  23. package/dist/collection/components/helper-line/helper-line.js +9 -6
  24. package/dist/collection/components/input-field/input-field.js +7 -12
  25. package/dist/collection/components/select/select.template.js +8 -3
  26. package/dist/collection/components/side-panel/side-panel.css +118 -6
  27. package/dist/collection/components/side-panel/side-panel.js +120 -77
  28. package/dist/collection/components/text-editor/text-editor.js +2 -2
  29. package/dist/esm/{checkbox.template-IDyv2bgn.js → checkbox.template-BFL4SMzl.js} +2 -2
  30. package/dist/esm/lime-elements.js +1 -1
  31. package/dist/esm/limel-breadcrumbs_6.entry.js +5 -10
  32. package/dist/esm/limel-checkbox.entry.js +1 -1
  33. package/dist/esm/limel-chip-set.entry.js +8 -3
  34. package/dist/esm/limel-code-editor.entry.js +1 -2
  35. package/dist/esm/limel-color-picker.entry.js +1 -1
  36. package/dist/esm/limel-file-viewer.entry.js +81 -47
  37. package/dist/esm/limel-form.entry.js +7 -7
  38. package/dist/esm/limel-helper-line_3.entry.js +3 -3
  39. package/dist/esm/limel-list-item.entry.js +1 -1
  40. package/dist/esm/limel-select.entry.js +8 -3
  41. package/dist/esm/limel-side-panel.entry.js +72 -5
  42. package/dist/esm/limel-text-editor.entry.js +2 -2
  43. package/dist/esm/loader.js +1 -1
  44. package/dist/lime-elements/lime-elements.esm.js +1 -1
  45. package/dist/lime-elements/p-06850418.entry.js +1 -0
  46. package/dist/lime-elements/p-0e662a3c.entry.js +1 -0
  47. package/dist/lime-elements/p-1qqvOwzC.js +1 -0
  48. package/dist/lime-elements/p-236743f1.entry.js +1 -0
  49. package/dist/lime-elements/p-2528f75e.entry.js +159 -0
  50. package/dist/lime-elements/{p-cf6fab84.entry.js → p-43c36a02.entry.js} +1 -1
  51. package/dist/lime-elements/{p-ae2e5e87.entry.js → p-49eb2f64.entry.js} +1 -1
  52. package/dist/lime-elements/p-4f58c6b3.entry.js +1 -0
  53. package/dist/lime-elements/p-5a15606a.entry.js +1 -0
  54. package/dist/lime-elements/{p-7a2e0e18.entry.js → p-675a7469.entry.js} +1 -1
  55. package/dist/lime-elements/{p-9b1934fb.entry.js → p-90286e5c.entry.js} +1 -1
  56. package/dist/lime-elements/p-92fab922.entry.js +23 -0
  57. package/dist/lime-elements/{p-14aff731.entry.js → p-c492af1b.entry.js} +1 -1
  58. package/dist/types/components/chip-set/chip-set.d.ts +2 -0
  59. package/dist/types/components/color-picker/color-picker.d.ts +10 -3
  60. package/dist/types/components/date-picker/date-picker.d.ts +11 -2
  61. package/dist/types/components/helper-line/helper-line.d.ts +7 -3
  62. package/dist/types/components/input-field/input-field.d.ts +10 -2
  63. package/dist/types/components/side-panel/side-panel.d.ts +35 -73
  64. package/dist/types/components.d.ts +108 -260
  65. package/package.json +6 -6
  66. package/dist/lime-elements/p-25d0d06b.entry.js +0 -159
  67. package/dist/lime-elements/p-33b58741.entry.js +0 -1
  68. package/dist/lime-elements/p-732e7807.entry.js +0 -23
  69. package/dist/lime-elements/p-82c65f70.entry.js +0 -1
  70. package/dist/lime-elements/p-9ed21407.entry.js +0 -1
  71. package/dist/lime-elements/p-DqQOawxc.js +0 -1
  72. package/dist/lime-elements/p-b69af611.entry.js +0 -1
  73. package/dist/lime-elements/p-ec74a07a.entry.js +0 -1
@@ -7,8 +7,12 @@ import { Host, h } from "@stencil/core";
7
7
  * We created this to keep the visual styles the same everywhere
8
8
  * and to avoid importing styles separately.
9
9
  *
10
- * Also this enables us to open the helper line in limel-portal,
11
- * more easily without having to send the styles to the portal.
10
+ * :::note
11
+ * The component has `shadow: false`, so that the helper text is part of the
12
+ * DOM of the consumer. This way, the `id` of the helper text can be used in
13
+ * the `aria-describedby` of the input element in the consumer, which is
14
+ * not possible across a shadow root.
15
+ * :::
12
16
  *
13
17
  * @exampleComponent limel-example-helper-line-basic
14
18
  * @exampleComponent limel-example-helper-line-invalid
@@ -46,12 +50,11 @@ export class HelperLine {
46
50
  };
47
51
  }
48
52
  render() {
49
- return (h(Host, { key: '69872d6dcdbecf1d703a9fb441f118f9389596bc', tabIndex: -1, class: {
53
+ return (h(Host, { key: 'ac3dcfadd4d281bf64bce4124602279eef598d87', tabIndex: -1, class: {
50
54
  invalid: this.invalid,
51
- }, style: this.hasContent() ? {} : { display: 'none' }, "aria-hidden": !this.hasContent() }, h("div", { key: 'bf63441b2481b1d91fae50d7f85630e361fb2089' }, this.renderHelperText(), this.renderCharacterCounter())));
55
+ }, style: this.hasContent() ? {} : { display: 'none' }, "aria-hidden": !this.hasContent() }, h("div", { key: '80aaa762e38841ed13a880cc5444c8aa23025130' }, this.renderHelperText(), this.renderCharacterCounter())));
52
56
  }
53
57
  static get is() { return "limel-helper-line"; }
54
- static get encapsulation() { return "shadow"; }
55
58
  static get originalStyleUrls() {
56
59
  return {
57
60
  "$": ["helper-line.scss"]
@@ -153,7 +156,7 @@ export class HelperLine {
153
156
  "optional": true,
154
157
  "docs": {
155
158
  "tags": [],
156
- "text": "Used by `aria-controls` and `aria-describedby` in the parent component."
159
+ "text": "Used by `aria-describedby` in the parent component."
157
160
  },
158
161
  "getter": false,
159
162
  "setter": false,
@@ -590,21 +590,16 @@ export class InputField {
590
590
  properties.required = this.required;
591
591
  properties.readonly = this.readonly;
592
592
  properties.disabled = this.disabled || this.readonly;
593
- let ariaControls = '';
594
593
  if (this.hasHelperText()) {
595
- ariaControls += this.helperTextId;
596
594
  properties['aria-describedby'] = this.helperTextId;
597
595
  }
598
- if (this.renderAutocompleteList()) {
599
- if (ariaControls) {
600
- ariaControls += ' ';
601
- }
602
- ariaControls += this.portalId;
596
+ if (this.isInvalid()) {
597
+ properties['aria-invalid'] = 'true';
603
598
  }
604
- if (ariaControls) {
605
- properties['aria-controls'] = ariaControls;
599
+ if (this.renderAutocompleteList()) {
600
+ properties['aria-controls'] = this.portalId;
606
601
  }
607
- return (h(Host, { key: 'dfb11ef606088978c2d3b4622b5d600187e532fa' }, h("limel-notched-outline", { key: 'd91fe3bb99eee9f7562db2c2b6bd6e2b01a4b5cb', labelId: this.inputId, labelElementId: this.labelElementId, label: this.label, required: this.required, invalid: this.invalid || this.isInvalid(), disabled: this.disabled, readonly: this.readonly, hasValue: !!this.value, hasLeadingIcon: !!this.leadingIcon }, h("label", { key: '4ab780c977ce088267686753b0c53f1dfcbb2f11', slot: "content", class: this.getContainerClassList() }, this.renderLeadingIcon(), this.renderPrefix(), this.renderFormattedNumber(), this.renderInput(properties), this.renderSuffix(), this.renderTextarea(properties), this.renderTrailingLinkOrButton())), this.renderHelperLine(), this.renderAutocompleteList()));
602
+ return (h(Host, { key: '88b9b2a3b7763341aff9796754e8e59ea979ed1f' }, h("limel-notched-outline", { key: '27a236db38f4f6f9690c7a44e294867b7a4f915f', labelId: this.inputId, labelElementId: this.labelElementId, label: this.label, required: this.required, invalid: this.invalid || this.isInvalid(), disabled: this.disabled, readonly: this.readonly, hasValue: !!this.value, hasLeadingIcon: !!this.leadingIcon }, h("label", { key: 'b20d232b3f4cf805b5a6b459b6d9193dc95e8b19', slot: "content", class: this.getContainerClassList() }, this.renderLeadingIcon(), this.renderPrefix(), this.renderFormattedNumber(), this.renderInput(properties), this.renderSuffix(), this.renderTextarea(properties), this.renderTrailingLinkOrButton())), this.renderHelperLine(), this.renderAutocompleteList()));
608
603
  }
609
604
  valueWatcher(newValue) {
610
605
  if (!this.mdcTextField || this.changeWaiting) {
@@ -738,7 +733,7 @@ export class InputField {
738
733
  "optional": false,
739
734
  "docs": {
740
735
  "tags": [],
741
- "text": "The placeholder text shown inside the input field, when the field is focused and empty."
736
+ "text": "A hint about what to type, such as an example or the format of the\nvalue. It is shown inside the field when the field is focused and empty.\n\nDo not use it instead of a `label`, and do not put anything in it that\nthe user needs to know. It disappears when the user starts to type, and\nsome screen readers do not read it. Use `helperText` for that."
742
737
  },
743
738
  "getter": false,
744
739
  "setter": false,
@@ -757,7 +752,7 @@ export class InputField {
757
752
  "optional": false,
758
753
  "docs": {
759
754
  "tags": [],
760
- "text": "Optional helper text to display below the input field when it has focus"
755
+ "text": "Optional helper text to display below the input field when it has focus.\nScreen readers read it as the description of the field, so use it for\nwhat the user needs to know, such as the format of the value or a\nrequirement."
761
756
  },
762
757
  "getter": false,
763
758
  "setter": false,
@@ -5,6 +5,7 @@ import { getIconColor, getIconName } from "../icon/get-icon-props";
5
5
  const TRIGGER_ID = 's-trigger';
6
6
  const LABEL_ID = 's-label';
7
7
  const SELECTED_TEXT_ID = 's-selected-text';
8
+ const HELPER_TEXT_ID = 's-helper-text';
8
9
  export const SelectTemplate = (props) => {
9
10
  const value = props.value;
10
11
  let hasValue = !!props.value;
@@ -37,6 +38,10 @@ export const SelectTemplate = (props) => {
37
38
  h(SelectDropdown, Object.assign({}, props)),
38
39
  ];
39
40
  };
41
+ const getDescribedBy = (props) => {
42
+ var _a;
43
+ return ((_a = props.helperText) === null || _a === void 0 ? void 0 : _a.trim()) ? HELPER_TEXT_ID : undefined;
44
+ };
40
45
  const floatLabelAbove = (props) => {
41
46
  if (props.isOpen) {
42
47
  return true;
@@ -58,7 +63,7 @@ const SelectValue = (props) => {
58
63
  };
59
64
  return (h("button", { id: TRIGGER_ID, slot: "content", class: anchorClassList, onClick: props.open, onKeyDown: props.onTriggerKeyDown, "aria-haspopup": "listbox", "aria-expanded": props.isOpen, "aria-controls": props.id, "aria-labelledby": props.label
60
65
  ? `${LABEL_ID} ${SELECTED_TEXT_ID}`
61
- : SELECTED_TEXT_ID, "aria-required": props.required, disabled: props.disabled || props.readonly }, h("span", { class: "mdc-select__selected-text-container limel-select__selected-option" }, getSelectedIcon(props.value), getSelectedPrimaryComponent(props.value), h("span", { id: SELECTED_TEXT_ID, class: "mdc-select__selected-text limel-select__selected-option__text" }, getSelectedText(props.value))), h(ShowIcon, Object.assign({}, props, { isValid: props.isValid })), h("span", { class: "mdc-select__dropdown-icon" }, h("svg", { class: "mdc-select__dropdown-icon-graphic", viewBox: "7 10 10 5", focusable: "false" }, h("polygon", { stroke: "none", "fill-rule": "evenodd", points: "7 10 12 15 17 10" })))));
66
+ : SELECTED_TEXT_ID, "aria-describedby": getDescribedBy(props), "aria-required": props.required, disabled: props.disabled || props.readonly }, h("span", { class: "mdc-select__selected-text-container limel-select__selected-option" }, getSelectedIcon(props.value), getSelectedPrimaryComponent(props.value), h("span", { id: SELECTED_TEXT_ID, class: "mdc-select__selected-text limel-select__selected-option__text" }, getSelectedText(props.value))), h(ShowIcon, Object.assign({}, props, { isValid: props.isValid })), h("span", { class: "mdc-select__dropdown-icon" }, h("svg", { class: "mdc-select__dropdown-icon-graphic", viewBox: "7 10 10 5", focusable: "false" }, h("polygon", { stroke: "none", "fill-rule": "evenodd", points: "7 10 12 15 17 10" })))));
62
67
  };
63
68
  const ShowIcon = (props) => {
64
69
  if (props.isValid) {
@@ -70,7 +75,7 @@ const HelperText = (props) => {
70
75
  if (typeof props.text !== 'string') {
71
76
  return;
72
77
  }
73
- return (h("limel-helper-line", { helperText: props.text.trim(), invalid: !props.isValid }));
78
+ return (h("limel-helper-line", { helperText: props.text.trim(), helperTextId: HELPER_TEXT_ID, invalid: !props.isValid }));
74
79
  };
75
80
  const SelectDropdown = (props) => {
76
81
  if (props.native) {
@@ -92,7 +97,7 @@ const NativeDropdown = (props) => {
92
97
  const options = props.options
93
98
  .filter((option) => !('separator' in option))
94
99
  .map(renderOption(props.value));
95
- return (h("select", { required: props.required, "aria-labelledby": props.label ? LABEL_ID : undefined, "aria-disabled": props.disabled, "aria-required": props.required, onChange: props.onNativeChange, onFocus: props.open, onBlur: props.close, class: "limel-select__native-control", disabled: props.disabled, multiple: props.multiple }, options));
100
+ return (h("select", { required: props.required, "aria-labelledby": props.label ? LABEL_ID : undefined, "aria-describedby": getDescribedBy(props), "aria-disabled": props.disabled, "aria-required": props.required, onChange: props.onNativeChange, onFocus: props.open, onBlur: props.close, class: "limel-select__native-control", disabled: props.disabled, multiple: props.multiple }, options));
96
101
  };
97
102
  const renderOption = (selectedOption) => {
98
103
  return (option) => {
@@ -132,19 +132,19 @@
132
132
  }
133
133
  section {
134
134
  position: absolute;
135
- z-index: var(--_side-panel-z-index);
136
135
  top: var(--_side-panel-top-inset);
137
136
  right: var(--_side-panel-right-inset);
138
137
  bottom: var(--_side-panel-bottom-inset);
139
138
  margin: 0.5rem;
140
- display: flex;
141
- flex-direction: column;
142
- isolation: isolate;
143
139
  width: var(--_side-panel-width);
144
140
  min-width: min(var(--_side-panel-min-width), 100% - 1rem);
145
141
  max-width: min(100% - 1rem - 3rem, 60%);
146
142
  max-height: 100%;
147
143
  border-radius: 1rem;
144
+ z-index: var(--_side-panel-z-index);
145
+ display: flex;
146
+ flex-direction: column;
147
+ isolation: isolate;
148
148
  box-shadow: var(--shadow-depth-64);
149
149
  transform-origin: left center;
150
150
  transition: background-color 0.5s ease, transform 0.3s ease, opacity 0.2s ease, right 0.2s ease;
@@ -160,7 +160,8 @@ section:focus {
160
160
  }
161
161
  }
162
162
 
163
- .content {
163
+ .content,
164
+ .stack {
164
165
  --dropdown-z-index: var(
165
166
  --_side-panel-dropdown-z-index,
166
167
  calc(var(--_side-panel-z-index) + 1)
@@ -173,6 +174,21 @@ section:focus {
173
174
  --_side-panel-tooltip-z-index,
174
175
  calc(var(--_side-panel-z-index) + 1)
175
176
  );
177
+ }
178
+
179
+ :host([full-screen]) section {
180
+ left: 0;
181
+ width: 100%;
182
+ min-width: 100%;
183
+ max-width: 100%;
184
+ margin: 0;
185
+ }
186
+ :host([full-screen]) section,
187
+ :host([full-screen]) .content {
188
+ border-radius: 0;
189
+ }
190
+
191
+ .content {
176
192
  display: flex;
177
193
  flex-direction: column;
178
194
  flex-grow: 1;
@@ -415,7 +431,7 @@ section:focus {
415
431
  --side-panel-header-text-color: var(--limel-theme-on-surface-color);
416
432
  --header-top-right-left-border-radius: 0;
417
433
  flex-shrink: 0;
418
- min-height: calc(1.25rem + 1rem + 2 * 0.125rem);
434
+ min-height: 2.5rem;
419
435
  padding: 0.125rem 0.75rem 0.125rem 0.25rem;
420
436
  background-color: var(--header-background-color, rgb(var(--contrast-200)));
421
437
  }
@@ -472,4 +488,100 @@ section:focus {
472
488
 
473
489
  [data-limit=max] {
474
490
  width: min(100% - 1rem - 3rem, 60%);
491
+ }
492
+
493
+ .stack {
494
+ position: absolute;
495
+ inset: 0;
496
+ z-index: calc(var(--_side-panel-z-index) - 1);
497
+ pointer-events: none;
498
+ }
499
+
500
+ .back {
501
+ appearance: none;
502
+ background: none;
503
+ border: none;
504
+ padding: 0;
505
+ margin: 0;
506
+ font: inherit;
507
+ color: inherit;
508
+ text-align: inherit;
509
+ position: absolute;
510
+ top: var(--_side-panel-top-inset);
511
+ right: var(--_side-panel-right-inset);
512
+ bottom: var(--_side-panel-bottom-inset);
513
+ margin: 0.5rem;
514
+ width: var(--_side-panel-width);
515
+ min-width: min(var(--_side-panel-min-width), 100% - 1rem);
516
+ max-width: min(100% - 1rem - 3rem, 60%);
517
+ max-height: 100%;
518
+ border-radius: 1rem;
519
+ }
520
+ .back:focus {
521
+ outline: none;
522
+ }
523
+ .back:focus-visible {
524
+ outline: none;
525
+ box-shadow: var(--shadow-depth-8-focused);
526
+ }
527
+ .back {
528
+ pointer-events: auto;
529
+ cursor: pointer;
530
+ box-sizing: border-box;
531
+ overflow: hidden;
532
+ display: flex;
533
+ flex-direction: column;
534
+ background-color: rgb(var(--contrast-100));
535
+ box-shadow: var(--shadow-depth-16);
536
+ transform-origin: left center;
537
+ transition: transform 0.3s ease, box-shadow 0.2s ease, background-color 0.2s ease, right 0.2s ease;
538
+ }
539
+ @media (prefers-reduced-motion) {
540
+ .back {
541
+ transition: none;
542
+ }
543
+ }
544
+ .back:nth-last-child(1) {
545
+ transform: translate3d(-1.4rem, 0, 0) scale(0.98);
546
+ }
547
+ .back:nth-last-child(2) {
548
+ transform: translate3d(-2.3rem, 0, 0) scale(0.96);
549
+ }
550
+ .back:nth-last-child(3) {
551
+ transform: translate3d(-3rem, 0, 0) scale(0.94);
552
+ }
553
+ .back:nth-last-child(n+4) {
554
+ transform: translate3d(-3.4rem, 0, 0) scale(0.92);
555
+ }
556
+ .back:hover {
557
+ box-shadow: var(--shadow-depth-64);
558
+ }
559
+ .back:hover, .back:focus-visible {
560
+ right: calc(var(--_side-panel-right-inset) + 0.25rem);
561
+ background-color: var(--lime-elevated-surface-background-color);
562
+ }
563
+
564
+ .stack:has(.back:hover) .back:nth-last-child(2) {
565
+ transform: translate3d(-2.6rem, 0, 0) scale(0.96);
566
+ }
567
+ .stack:has(.back:hover) .back:nth-last-child(3) {
568
+ transform: translate3d(-3.8rem, 0, 0) scale(0.94);
569
+ }
570
+ .stack:has(.back:hover) .back:nth-last-child(n+4) {
571
+ transform: translate3d(-4.9rem, 0, 0) scale(0.92);
572
+ }
573
+
574
+ .back-toolbar,
575
+ .back-band {
576
+ flex-shrink: 0;
577
+ }
578
+
579
+ .back-toolbar {
580
+ height: 2.375rem;
581
+ background-color: rgb(var(--contrast-300));
582
+ }
583
+
584
+ .back-band {
585
+ height: 2.5rem;
586
+ background-color: rgb(var(--contrast-200));
475
587
  }
@@ -22,87 +22,28 @@ function stopEvent(event) {
22
22
  }
23
23
  /**
24
24
  * A resizable panel that is displayed on top of the right side of the page.
25
- * The content is placed in the default slot, below a toolbar and a header.
25
+ * It does not block the rest of the page. Place it in an element with
26
+ * `position: relative`.
26
27
  *
27
- * The panel is positioned relative to its nearest positioned ancestor. Set
28
- * `position: relative` on that element. Use `--side-panel-top-inset`,
29
- * `--side-panel-right-inset` and `--side-panel-bottom-inset` to keep parts of
30
- * that element visible, for example a header.
28
+ * The panel has:
29
+ * - a toolbar with a close button, and buttons from `actions`
30
+ * - a header that shows the last entry of `stack`, with a slot for controls
31
+ * - backs behind its left edge, for the other entries of `stack`
32
+ * - a handle on its left edge, to resize the panel
33
+ * - a full screen mode, for small screens
31
34
  *
32
- * The CSS custom properties can be set on the panel or on any ancestor.
35
+ * The panel emits `close`, but does not remove itself. The consumer must
36
+ * remove it from the DOM.
33
37
  *
34
- * The panel is a dialog that does not block the rest of the page. Set `label`
35
- * to give it an accessible name, which screen readers announce. When the panel
36
- * opens, focus moves into it. When the panel is removed, focus returns to the
37
- * element that had it before, unless focus is somewhere else by then.
38
- *
39
- * The panel emits `close` when the user clicks the close button, or presses
40
- * Escape. It does not remove itself. The consumer must remove it from the DOM.
41
- * The user can resize the panel by dragging its left edge, or with the arrow
42
- * keys. The panel emits `changeWidth` when a resize is finished.
43
- *
44
- * See the examples for how closing and resizing work in detail.
45
- *
46
- * ## Header
47
- *
48
- * The header shows the heading, subheading and icon of the last entry in
49
- * `stack`. When the entry has a `color`, the header gets that background color,
50
- * and the headings are white with a text shadow. When `stack` is empty, the
51
- * panel has no header.
52
- *
53
- * Put controls for the header, for example a follow button or a menu, in the
54
- * `header-actions` slot. They are shown at the right end of the header. When
55
- * `stack` is empty, there is no header, and the slot content is not shown.
56
- *
57
- * The slot is for small controls. It is at most half as wide as the header,
58
- * and at most as tall as the heading and subheading together. Larger content
59
- * is cut off, so that the header keeps its size.
60
- *
61
- * The slot content is not tied to an entry in `stack`. When the last entry
62
- * changes, the consumer must also change the slot content.
63
- *
64
- * The slot content keeps its own colors. Use `--side-panel-header-text-color`
65
- * to match the text color of the header. It is white when the header has a
66
- * `color`. It can also be used as the `color` of an `Icon`.
67
- *
68
- * ## Actions
69
- *
70
- * The toolbar has a close button on the left. Other buttons are set with
71
- * `actions`, and are shown on the right. The `type` of an action decides its
72
- * icon. When the user clicks an action, the panel emits `selectAction` with the
73
- * `id` of the action and the click event. The consumer decides what the action
74
- * does.
75
- *
76
- * An action with an `href` is a link, so the user can open it in a new tab.
77
- * To handle the navigation in another way, for example with the router of the
78
- * application, call `preventDefault` on the click event in `selectAction`. Do
79
- * this only for a plain click with the primary button. Let the browser handle
80
- * all other clicks, so that Ctrl-click and Cmd-click still open the link in a
81
- * new tab:
82
- *
83
- * ```ts
84
- * function handleSelectAction(event: CustomEvent<SidePanelActionSelect>) {
85
- * const click = event.detail.event;
86
- * const isPlainClick =
87
- * click.button === 0 &&
88
- * !click.metaKey &&
89
- * !click.ctrlKey &&
90
- * !click.shiftKey &&
91
- * !click.altKey;
92
- * if (!isPlainClick) {
93
- * return;
94
- * }
95
- *
96
- * click.preventDefault();
97
- * router.navigate(event.detail.id);
98
- * }
99
- * ```
38
+ * Each example shows one feature in detail, and when to use it.
100
39
  *
101
40
  * @exampleComponent limel-example-side-panel-basic
102
41
  * @exampleComponent limel-example-side-panel-insets
103
42
  * @exampleComponent limel-example-side-panel-escape
104
43
  * @exampleComponent limel-example-side-panel-actions
105
44
  * @exampleComponent limel-example-side-panel-header-actions
45
+ * @exampleComponent limel-example-side-panel-stacking
46
+ * @exampleComponent limel-example-side-panel-full-screen
106
47
  * @slot - Content of the panel
107
48
  * @slot header-actions - Small controls at the right end of the header. Content that is larger than half the header width or the height of the headings is cut off.
108
49
  * @private
@@ -117,7 +58,7 @@ export class SidePanel {
117
58
  /**
118
59
  * The panels in the stack, outermost first. The last entry is the panel
119
60
  * that is displayed. Its heading, subheading and icon are shown in the
120
- * header.
61
+ * header. The other entries are shown as backs behind the panel.
121
62
  */
122
63
  this.stack = [];
123
64
  /**
@@ -134,13 +75,27 @@ export class SidePanel {
134
75
  * event either.
135
76
  */
136
77
  this.closeOnEscape = true;
78
+ /**
79
+ * Set to `true` when the screen is too narrow for the panel to be displayed
80
+ * on top of the page. The panel then covers the full width of the
81
+ * positioned ancestor. It cannot be resized, and the backs of the stack are
82
+ * not shown.
83
+ */
84
+ this.fullScreen = false;
137
85
  this.closeButtonId = createRandomString();
138
86
  this.actionIdPrefix = createRandomString();
87
+ this.backIdPrefix = createRandomString();
88
+ this.focusCloseButtonAfterRender = false;
139
89
  this.renderAction = (action, index) => {
140
90
  const id = `${this.actionIdPrefix}-${index}`;
141
91
  const Tag = action.href ? 'a' : 'button';
142
92
  return (h(Tag, { class: action.type, id: id, href: action.href, target: getLinkTarget(action), "aria-label": action.label, onClick: this.handleClickAction(action) }, h("limel-icon", { name: ACTION_ICONS[action.type], "aria-hidden": "true" }), h("limel-tooltip", { elementId: id, label: action.label, helperLabel: action.helperLabel })));
143
93
  };
94
+ this.renderBack = (entry, index) => {
95
+ var _a;
96
+ const id = `${this.backIdPrefix}-${index}`;
97
+ return (h("button", { class: "back", id: id, style: { width: (_a = this.panel) === null || _a === void 0 ? void 0 : _a.style.width }, "aria-label": entry.heading, onClick: this.handleClickBack(index) }, h("span", { class: "back-toolbar" }), h("span", { class: "back-band", style: { 'background-color': entry.color } }), h("limel-tooltip", { elementId: id, label: entry.heading, helperLabel: entry.subheading, openDirection: "left" })));
98
+ };
144
99
  /**
145
100
  * Components in the content can emit events with the same names as the
146
101
  * panel's events, for example `close` from `limel-dialog` or
@@ -235,6 +190,9 @@ export class SidePanel {
235
190
  event: event,
236
191
  });
237
192
  };
193
+ this.handleClickBack = (index) => () => {
194
+ this.selectStackEntry.emit(index);
195
+ };
238
196
  this.handleClickClose = () => {
239
197
  this.close.emit();
240
198
  };
@@ -256,8 +214,24 @@ export class SidePanel {
256
214
  this.removeFromOpenPanels();
257
215
  queueMicrotask(this.restoreFocus);
258
216
  }
217
+ // A removed button takes the focus with it, so a keyboard user that goes
218
+ // back would lose their place in the page. Both props can change in the
219
+ // same update, so the result comes from both, and not from the prop that
220
+ // changed last.
221
+ handleBacksChange() {
222
+ this.focusCloseButtonAfterRender = this.isFocusedBackRemoved();
223
+ }
224
+ componentDidRender() {
225
+ var _a;
226
+ if (!this.focusCloseButtonAfterRender) {
227
+ return;
228
+ }
229
+ this.focusCloseButtonAfterRender = false;
230
+ (_a = this.host.shadowRoot
231
+ .querySelector('button.close')) === null || _a === void 0 ? void 0 : _a.focus();
232
+ }
259
233
  render() {
260
- return (h(Host, { key: 'debf8ec5b8e179cbb69c2d87fdd0db7089707dfb' }, h("section", { key: 'd6165d2ff25cad3b9bebe807065b6a83bca24da2', ref: this.setPanel, role: "dialog", "aria-label": this.label, tabindex: "-1" }, this.renderHandle(), h("div", { key: 'b95d937af7fc5799abee8a6042ea2be6087c1b0b', class: "content" }, h("div", { key: 'e258c9475715b00fd1e56a9975db6d0f7e5d19fc', class: "toolbar" }, this.renderCloseButton(), this.renderActions()), this.renderHeader(), this.renderBody())), h("span", { key: '6b7793d7c02f632d630ac4936524104f81a10e23', "data-limit": "min", ref: this.setMinLimit }), h("span", { key: '6196bc2f8549ddd9b8c8bb43bcf221a43f62c98a', "data-limit": "max", ref: this.setMaxLimit })));
234
+ return (h(Host, { key: 'ca07c3557df3be7e026a95da296689e1e3c0cf41' }, h("section", { key: '610a8b47c7b34a6d4cb4d253d40cff889ea4c73d', ref: this.setPanel, role: "dialog", "aria-label": this.label, tabindex: "-1" }, this.renderHandle(), h("div", { key: 'c9ddb80456ac82cb39801943b55fcd4de3928852', class: "content" }, h("div", { key: 'f1430da0610a4fe71363ead5c7730d627a8a935c', class: "toolbar" }, this.renderCloseButton(), this.renderActions()), this.renderHeader(), this.renderBody())), this.renderStack(), h("span", { key: 'c7d204c8b323f88962508a4c54493717fe347c8e', "data-limit": "min", ref: this.setMinLimit }), h("span", { key: '2a7fa787fd0d4c777f90b2ef7dff1d9316108416', "data-limit": "max", ref: this.setMaxLimit })));
261
235
  }
262
236
  // A native button and not `limel-icon-button`, because the hover animation
263
237
  // styles the icon, which is inside the shadow root of `limel-icon-button`.
@@ -270,6 +244,21 @@ export class SidePanel {
270
244
  }
271
245
  return (h("div", { class: "actions" }, this.actions.map((action, index) => this.renderAction(action, index))));
272
246
  }
247
+ renderStack() {
248
+ const backs = this.stack.slice(0, -1);
249
+ if (this.fullScreen || backs.length === 0) {
250
+ return;
251
+ }
252
+ return (h("div", { class: "stack" }, backs.map((entry, index) => this.renderBack(entry, index))));
253
+ }
254
+ isFocusedBackRemoved() {
255
+ const backs = [...this.host.shadowRoot.querySelectorAll('.back')];
256
+ const index = backs.indexOf(this.host.shadowRoot.activeElement);
257
+ if (index === -1) {
258
+ return false;
259
+ }
260
+ return this.fullScreen || index >= this.stack.length - 1;
261
+ }
273
262
  renderHeader() {
274
263
  const entry = this.stack.at(-1);
275
264
  if (!entry) {
@@ -279,6 +268,9 @@ export class SidePanel {
279
268
  }
280
269
  renderHandle() {
281
270
  var _a, _b, _c;
271
+ if (this.fullScreen) {
272
+ return;
273
+ }
282
274
  return (h("limel-resize-handle", { class: "handle", label: this.resizeLabel, value: (_a = this.widthRange) === null || _a === void 0 ? void 0 : _a.now, min: (_b = this.widthRange) === null || _b === void 0 ? void 0 : _b.min, max: (_c = this.widthRange) === null || _c === void 0 ? void 0 : _c.max, onChangeSize: this.handleChangeSize, onEndResize: this.handleEndResize }));
283
275
  }
284
276
  renderBody() {
@@ -324,9 +316,16 @@ export class SidePanel {
324
316
  // An inline width wins over `--side-panel-width`, and is still limited by
325
317
  // `min-width` and `max-width`. The width is set on the element and not with
326
318
  // a state, because a render and a measurement on each pointer move make
327
- // the drag slow when the content is large.
319
+ // the drag slow when the content is large. The backs of the stack get the
320
+ // width too, so that they have the same width as the panel.
328
321
  setWidth(width) {
329
- this.panel.style.width = `${width}px`;
322
+ for (const element of this.getPanelBoxes()) {
323
+ element.style.width = `${width}px`;
324
+ }
325
+ }
326
+ getPanelBoxes() {
327
+ const backs = this.host.shadowRoot.querySelectorAll('.back');
328
+ return [this.panel, ...backs];
330
329
  }
331
330
  rememberWidthBeforeResize() {
332
331
  if (this.isResizing()) {
@@ -456,7 +455,7 @@ export class SidePanel {
456
455
  "optional": false,
457
456
  "docs": {
458
457
  "tags": [],
459
- "text": "The panels in the stack, outermost first. The last entry is the panel\nthat is displayed. Its heading, subheading and icon are shown in the\nheader."
458
+ "text": "The panels in the stack, outermost first. The last entry is the panel\nthat is displayed. Its heading, subheading and icon are shown in the\nheader. The other entries are shown as backs behind the panel."
460
459
  },
461
460
  "getter": false,
462
461
  "setter": false,
@@ -526,6 +525,26 @@ export class SidePanel {
526
525
  "reflect": false,
527
526
  "attribute": "close-on-escape",
528
527
  "defaultValue": "true"
528
+ },
529
+ "fullScreen": {
530
+ "type": "boolean",
531
+ "mutable": false,
532
+ "complexType": {
533
+ "original": "boolean",
534
+ "resolved": "boolean",
535
+ "references": {}
536
+ },
537
+ "required": false,
538
+ "optional": false,
539
+ "docs": {
540
+ "tags": [],
541
+ "text": "Set to `true` when the screen is too narrow for the panel to be displayed\non top of the page. The panel then covers the full width of the\npositioned ancestor. It cannot be resized, and the backs of the stack are\nnot shown."
542
+ },
543
+ "getter": false,
544
+ "setter": false,
545
+ "reflect": true,
546
+ "attribute": "full-screen",
547
+ "defaultValue": "false"
529
548
  }
530
549
  };
531
550
  }
@@ -587,9 +606,33 @@ export class SidePanel {
587
606
  "resolved": "number",
588
607
  "references": {}
589
608
  }
609
+ }, {
610
+ "method": "selectStackEntry",
611
+ "name": "selectStackEntry",
612
+ "bubbles": true,
613
+ "cancelable": true,
614
+ "composed": true,
615
+ "docs": {
616
+ "tags": [],
617
+ "text": "Emitted when a back of the stack is clicked. The detail is the index of\nits entry in `stack`."
618
+ },
619
+ "complexType": {
620
+ "original": "number",
621
+ "resolved": "number",
622
+ "references": {}
623
+ }
590
624
  }];
591
625
  }
592
626
  static get elementRef() { return "host"; }
627
+ static get watchers() {
628
+ return [{
629
+ "propName": "stack",
630
+ "methodName": "handleBacksChange"
631
+ }, {
632
+ "propName": "fullScreen",
633
+ "methodName": "handleBacksChange"
634
+ }];
635
+ }
593
636
  static get listeners() {
594
637
  return [{
595
638
  "name": "close",
@@ -180,9 +180,9 @@ export class TextEditor {
180
180
  }
181
181
  renderEditor() {
182
182
  if (this.readonly) {
183
- return (h("limel-markdown", { slot: "content", value: this.value, "aria-controls": this.helperText ? this.helperTextId : undefined, id: this.editorId }));
183
+ return (h("limel-markdown", { slot: "content", value: this.value, id: this.editorId }));
184
184
  }
185
- return (h("limel-prosemirror-adapter", { ref: (el) => (this.adapterElement = el), slot: "content", "aria-placeholder": this.placeholder, contentType: this.contentType, onChange: this.handleChange, onImagePasted: this.handleImagePasted, onImageRemoved: this.handleImageRemoved, onMetadataChange: this.handleMetadataChange, customElements: this.customElements, inlineImages: this.inlineImages, value: this.value, "aria-controls": this.helperText ? this.helperTextId : undefined, id: this.editorId, "aria-disabled": this.disabled, "aria-invalid": this.invalid, "aria-required": this.required, language: this.language, triggerCharacters: this.triggers, disabled: this.disabled, ui: this.ui }));
185
+ return (h("limel-prosemirror-adapter", { ref: (el) => (this.adapterElement = el), slot: "content", "aria-placeholder": this.placeholder, contentType: this.contentType, onChange: this.handleChange, onImagePasted: this.handleImagePasted, onImageRemoved: this.handleImageRemoved, onMetadataChange: this.handleMetadataChange, customElements: this.customElements, inlineImages: this.inlineImages, value: this.value, id: this.editorId, "aria-disabled": this.disabled, "aria-invalid": this.invalid, "aria-required": this.required, language: this.language, triggerCharacters: this.triggers, disabled: this.disabled, ui: this.ui }));
186
186
  }
187
187
  renderPlaceholder() {
188
188
  if (!this.placeholder || this.value) {
@@ -10,7 +10,7 @@ const CheckboxTemplate = (props) => {
10
10
  };
11
11
  }
12
12
  return [
13
- h("limel-dynamic-label", { value: props.checked, "aria-controls": props.helperText ? props.helperTextId : undefined, defaultLabel: { text: props.label, icon: icon }, labels: props.readonlyLabels }),
13
+ h("limel-dynamic-label", { value: props.checked, defaultLabel: { text: props.label, icon: icon }, labels: props.readonlyLabels }),
14
14
  h(HelperText, { text: props.helperText, helperTextId: props.helperTextId, invalid: props.invalid }),
15
15
  ];
16
16
  }
@@ -35,7 +35,7 @@ const CheckboxTemplate = (props) => {
35
35
  required: props.required,
36
36
  indeterminate: props.indeterminate,
37
37
  readonly: props.readonly,
38
- } }, h("input", Object.assign({ type: "checkbox", id: props.id, checked: props.checked, disabled: props.disabled || props.readonly, required: props.required, onChange: props.onChange, "aria-controls": props.helperText ? props.helperTextId : undefined, "aria-describedby": props.helperTextId }, inputProps)), h("div", { class: "box" }, h("svg", { class: "check-mark", viewBox: "0 0 24 24", "aria-hidden": "true", focusable: "false" }, h("path", { fill: "none", d: "M1.73,12.91 8.1,19.28 22.79,4.59" }))), h("label", { class: "boolean-input-label", htmlFor: props.id }, props.label)),
38
+ } }, h("input", Object.assign({ type: "checkbox", id: props.id, checked: props.checked, disabled: props.disabled || props.readonly, required: props.required, onChange: props.onChange, "aria-describedby": props.helperTextId }, inputProps)), h("div", { class: "box" }, h("svg", { class: "check-mark", viewBox: "0 0 24 24", "aria-hidden": "true", focusable: "false" }, h("path", { fill: "none", d: "M1.73,12.91 8.1,19.28 22.79,4.59" }))), h("label", { class: "boolean-input-label", htmlFor: props.id }, props.label)),
39
39
  h(HelperText, { text: props.helperText, helperTextId: props.helperTextId, invalid: props.invalid }),
40
40
  ];
41
41
  };