@kubex/zinc 1.0.113 → 1.0.115

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 (101) hide show
  1. package/.github/workflows/eleventy_build.yml +5 -5
  2. package/.github/workflows/js_build_and_deploy.yaml +1 -1
  3. package/dist/custom-elements.json +2422 -190
  4. package/dist/vscode.html-custom-data.json +86 -5
  5. package/dist/web-types.json +251 -9
  6. package/dist/zn.d.ts +518 -340
  7. package/dist/zn.min.css +1 -1
  8. package/dist/zn.min.js +960 -882
  9. package/docs/eleventy.config.cjs +4 -2
  10. package/docs/pages/components/header.md +13 -0
  11. package/docs/pages/components/inline-edit.md +20 -0
  12. package/docs/pages/components/item.md +12 -1
  13. package/docs/pages/components/page.md +149 -0
  14. package/docs/pages/components/progress-bar.md +16 -0
  15. package/docs/pages/components/select.md +25 -0
  16. package/docs/pages/components/split-pane.md +6 -7
  17. package/docs/pages/components/tabs.md +59 -9
  18. package/package.json +8 -5
  19. package/scripts/build.js +40 -19
  20. package/scss/_global-spacing.scss +12 -1
  21. package/scss/_root.scss +34 -2
  22. package/scss/mixins/_spacing-mixins.scss +1 -1
  23. package/scss/shared/layout.scss +15 -2
  24. package/scss/themes/_light.scss +1 -1
  25. package/src/components/absolute-container/absolute-container.component.ts +9 -25
  26. package/src/components/animated-button/animated-button.test.ts +8 -8
  27. package/src/components/button/button.component.ts +3 -1
  28. package/src/components/button/button.scss +5 -0
  29. package/src/components/button-menu/button-menu.component.ts +36 -21
  30. package/src/components/button-menu/button-menu.test.ts +55 -1
  31. package/src/components/chart/chart.component.ts +7 -7
  32. package/src/components/collapsible/collapsible.component.ts +7 -14
  33. package/src/components/cols/cols.scss +1 -1
  34. package/src/components/content-block/content-block.component.ts +11 -33
  35. package/src/components/data-select/data-select.component.ts +7 -11
  36. package/src/components/data-select/data-select.scss +1 -0
  37. package/src/components/data-table/data-table.component.ts +10 -14
  38. package/src/components/dialog/dialog.scss +4 -0
  39. package/src/components/editor/modules/toolbar/toolbar.component.ts +5 -9
  40. package/src/components/expanding-action/expanding-action.component.ts +37 -43
  41. package/src/components/form-group/form-group.component.ts +32 -27
  42. package/src/components/form-group/form-group.scss +1 -0
  43. package/src/components/header/header.component.ts +6 -5
  44. package/src/components/header/header.scss +28 -26
  45. package/src/components/inline-edit/inline-edit.component.ts +13 -1
  46. package/src/components/inline-edit/inline-edit.test.ts +50 -0
  47. package/src/components/input/input.test.ts +77 -0
  48. package/src/components/input-group/input-group.test.ts +2 -10
  49. package/src/components/item/item.component.ts +12 -6
  50. package/src/components/item/item.scss +1 -1
  51. package/src/components/item/item.test.ts +8 -0
  52. package/src/components/navbar/navbar.component.ts +222 -48
  53. package/src/components/navbar/navbar.scss +134 -21
  54. package/src/components/navbar/navbar.test.ts +114 -9
  55. package/src/components/option/option.scss +5 -7
  56. package/src/components/option/option.test.ts +30 -0
  57. package/src/components/page/index.ts +13 -0
  58. package/src/components/page/page.component.ts +406 -0
  59. package/src/components/page/page.scss +325 -0
  60. package/src/components/page/page.test.ts +397 -0
  61. package/src/components/page-nav/page-nav.scss +7 -5
  62. package/src/components/pane/pane.component.ts +2 -2
  63. package/src/components/pane/pane.scss +0 -1
  64. package/src/components/pane/pane.test.ts +31 -0
  65. package/src/components/panel/panel.scss +5 -16
  66. package/src/components/progress-bar/progress-bar.component.ts +5 -2
  67. package/src/components/progress-bar/progress-bar.scss +17 -1
  68. package/src/components/progress-bar/progress-bar.test.ts +12 -0
  69. package/src/components/scroll-container/scroll-container.component.ts +22 -21
  70. package/src/components/select/select.component.ts +243 -39
  71. package/src/components/select/select.scss +36 -5
  72. package/src/components/select/select.test.ts +533 -0
  73. package/src/components/settings-container/settings-container.component.ts +15 -19
  74. package/src/components/settings-container/settings-container.scss +8 -8
  75. package/src/components/sidebar/sidebar.component.ts +11 -11
  76. package/src/components/simple-chart/simple-chart.component.ts +6 -7
  77. package/src/components/sp/sp.scss +46 -15
  78. package/src/components/sp/sp.test.ts +15 -0
  79. package/src/components/split-pane/split-pane.component.ts +153 -26
  80. package/src/components/split-pane/split-pane.scss +89 -13
  81. package/src/components/split-pane/split-pane.test.ts +187 -0
  82. package/src/components/style/style.component.ts +8 -0
  83. package/src/components/tab/index.ts +13 -0
  84. package/src/components/tab/tab.component.ts +25 -0
  85. package/src/components/tab/tab.scss +7 -0
  86. package/src/components/table/table.component.ts +7 -2
  87. package/src/components/table/table.scss +1 -0
  88. package/src/components/tabs/tabs.component.ts +75 -70
  89. package/src/components/tabs/tabs.scss +1 -1
  90. package/src/components/textarea/textarea.component.ts +5 -8
  91. package/src/components/tile/tile.scss +2 -2
  92. package/src/components/translation-group/translation-group.component.ts +15 -14
  93. package/src/components/translations/translations.component.ts +18 -17
  94. package/src/components/translations/translations.scss +1 -0
  95. package/src/components/translations/translations.test.ts +3 -2
  96. package/src/internal/form.ts +234 -0
  97. package/src/internal/theme.ts +26 -46
  98. package/src/internal/zinc-element.ts +12 -7
  99. package/src/utilities/form.ts +1 -0
  100. package/src/wc.scss +1 -1
  101. package/src/zinc.ts +3 -0
@@ -3,6 +3,7 @@ import {type CSSResultGroup, html, nothing, type TemplateResult, unsafeCSS} from
3
3
  import {HasSlotController} from "../../internal/slot";
4
4
  import {ifDefined} from "lit/directives/if-defined.js";
5
5
  import {property, query} from 'lit/decorators.js';
6
+ import {ResizeController} from '@lit-labs/observers/resize-controller.js';
6
7
  import {ref} from "lit/directives/ref.js";
7
8
  import {Task} from "@lit/task";
8
9
  import {unsafeHTML} from "lit/directives/unsafe-html.js";
@@ -222,7 +223,14 @@ export default class ZnDataTable extends ZincElement {
222
223
  private _initialLoad = true;
223
224
  private _lastTableContent: TemplateResult = html``;
224
225
 
225
- private resizeObserver: ResizeObserver;
226
+ private readonly resizeObserver = new ResizeController(this, {
227
+ target: null,
228
+ callback: () => {
229
+ if (this.tableContainer) {
230
+ this.tableContainer.scrollIntoView({behavior: 'smooth', block: 'nearest'});
231
+ }
232
+ },
233
+ });
226
234
 
227
235
  private itemsPerPage: number = DEFAULT_PER_PAGE;
228
236
  private page: number = DEFAULT_PAGE;
@@ -384,22 +392,12 @@ export default class ZnDataTable extends ZincElement {
384
392
 
385
393
  connectedCallback() {
386
394
  super.connectedCallback();
387
-
388
- this.resizeObserver = new ResizeObserver(() => {
389
- if (this.tableContainer) {
390
- this.tableContainer.scrollIntoView({behavior: 'smooth', block: 'nearest'});
391
- }
392
- });
393
-
394
395
  this.addEventListener('zn-filter-change', this.filterChangeListener);
395
396
  this.addEventListener('zn-search-change', this.searchChangeListener);
396
397
  }
397
398
 
398
399
  disconnectedCallback() {
399
400
  super.disconnectedCallback();
400
- if (this.resizeObserver) {
401
- this.resizeObserver.disconnect();
402
- }
403
401
  this.removeEventListener('zn-filter-change', this.filterChangeListener);
404
402
  this.removeEventListener('zn-search-change', this.searchChangeListener);
405
403
  }
@@ -796,9 +794,7 @@ export default class ZnDataTable extends ZincElement {
796
794
  this.numberOfRowsSelected = 0;
797
795
  this._dataTask.run().then(r => r);
798
796
 
799
- if (this.resizeObserver) {
800
- this.resizeObserver.observe(this);
801
- }
797
+ this.resizeObserver.observe(this);
802
798
  }
803
799
 
804
800
  goToFirstPage() {
@@ -19,6 +19,10 @@
19
19
  overflow: auto;
20
20
  }
21
21
 
22
+ .dialog__header + .dialog__body {
23
+ padding-top: 0;
24
+ }
25
+
22
26
  .dialog {
23
27
  flex-direction: column;
24
28
  z-index: 2;
@@ -1,6 +1,7 @@
1
1
  import {colorDataProvider} from "../../../data-select/providers/color-data-provider";
2
2
  import {type CSSResultGroup, html, type PropertyValues, unsafeCSS} from "lit";
3
3
  import {property, query, queryAll} from "lit/decorators.js";
4
+ import {ResizeController} from '@lit-labs/observers/resize-controller.js';
4
5
  import ZincElement from "../../../../internal/zinc-element";
5
6
  import ZnButton from "../../../button";
6
7
  import ZnEditorTool from "./tool";
@@ -24,30 +25,25 @@ export default class ToolbarComponent extends ZincElement {
24
25
  @query('.toolbar__group--overflow') private _overflowGroup!: HTMLElement;
25
26
 
26
27
  private _featureConfig: EditorFeatureConfig = {};
27
- private _resizeObserver: ResizeObserver | null = null;
28
28
  private _resizeId: number | null = null;
29
29
 
30
+ private readonly _resizeObserver = new ResizeController(this, {
31
+ callback: () => this.handleResize(),
32
+ });
33
+
30
34
  // Tracks DOM nodes (e.g. emoji/date pickers) temporarily moved into the overflow submenu so we can restore them
31
35
  private _movedContent: Map<HTMLElement, { placeholder: Comment; originalParent: HTMLElement }> = new Map();
32
36
 
33
37
  connectedCallback() {
34
38
  super.connectedCallback();
35
39
  this.containerWidth = this.offsetWidth;
36
-
37
- this._resizeObserver = new ResizeObserver(this.handleResize.bind(this));
38
40
  if (this.parentElement) {
39
41
  this._resizeObserver.observe(this.parentElement);
40
42
  }
41
- this._resizeObserver.observe(this as HTMLElement);
42
43
  }
43
44
 
44
45
  disconnectedCallback() {
45
46
  super.disconnectedCallback();
46
-
47
- if (this._resizeObserver) {
48
- this._resizeObserver.disconnect();
49
- this._resizeObserver = null;
50
- }
51
47
  if (this._resizeId !== null) {
52
48
  cancelAnimationFrame(this._resizeId);
53
49
  this._resizeId = null;
@@ -2,6 +2,7 @@ import {classMap} from "lit/directives/class-map.js";
2
2
  import {type CSSResultGroup, html, nothing, type PropertyValues, unsafeCSS} from 'lit';
3
3
  import {deepQuerySelectorAll} from "../../utilities/query";
4
4
  import {md5} from "../../utilities/md5";
5
+ import {MutationController} from '@lit-labs/observers/mutation-controller.js';
5
6
  import {property} from 'lit/decorators.js';
6
7
  import {styleMap} from "lit/directives/style-map.js";
7
8
  import ZincElement from '../../internal/zinc-element';
@@ -54,8 +55,36 @@ export default class ZnExpandingAction extends ZincElement {
54
55
  private _knownUri: Map<string, string> = new Map<string, string>();
55
56
  private _actions: HTMLElement[] = [];
56
57
  private _preload = true;
57
- private _countObserver?: MutationObserver;
58
- private _colorObserver?: MutationObserver;
58
+ private _metaObserved = false;
59
+
60
+ private readonly _countObserver = new MutationController(this, {
61
+ target: null,
62
+ config: {subtree: true, attributes: true, attributeFilter: ['content'], childList: true},
63
+ callback: () => {
64
+ const appContent = deepQuerySelectorAll('app-content', this, '')[0];
65
+ const metaCount = appContent?.shadowRoot?.querySelector('meta[name="count"]');
66
+ if (!metaCount) return;
67
+ const count = metaCount.getAttribute('content');
68
+ const notification = this.shadowRoot?.querySelector('.expanding-action__dropdown zn-button');
69
+ if (count && notification) {
70
+ notification.setAttribute('notification', count);
71
+ }
72
+ },
73
+ });
74
+
75
+ private readonly _colorObserver = new MutationController(this, {
76
+ target: null,
77
+ config: {subtree: true, attributes: true, attributeFilter: ['content'], childList: true},
78
+ callback: () => {
79
+ const appContent = deepQuerySelectorAll('app-content', this, '')[0];
80
+ const metaColor = appContent?.shadowRoot?.querySelector('meta[name="color"]');
81
+ if (!metaColor) return;
82
+ const color = metaColor.getAttribute('content');
83
+ if (color) {
84
+ this.color = color;
85
+ }
86
+ },
87
+ });
59
88
 
60
89
  constructor() {
61
90
  super();
@@ -75,12 +104,6 @@ export default class ZnExpandingAction extends ZincElement {
75
104
  this._panel = this.shadowRoot?.querySelector('#content');
76
105
  }
77
106
 
78
- disconnectedCallback() {
79
- super.disconnectedCallback();
80
- this._countObserver?.disconnect();
81
- this._colorObserver?.disconnect();
82
- }
83
-
84
107
  firstUpdated(_changedProperties: PropertyValues) {
85
108
  super.firstUpdated(_changedProperties);
86
109
 
@@ -97,43 +120,14 @@ export default class ZnExpandingAction extends ZincElement {
97
120
  }
98
121
 
99
122
  _observeMetaData() {
123
+ if (this._metaObserved) return;
100
124
  const appContent = deepQuerySelectorAll('app-content', this, '')[0];
125
+ const root = appContent?.shadowRoot;
126
+ if (!root) return;
101
127
 
102
- this._countObserver = new MutationObserver(() => {
103
- const metaCount = appContent?.shadowRoot?.querySelector('meta[name="count"]');
104
- if (!metaCount) {
105
- return;
106
- }
107
- const count = metaCount.getAttribute('content');
108
- const notification = this.shadowRoot?.querySelector('.expanding-action__dropdown zn-button');
109
- if (count && notification) {
110
- notification.setAttribute('notification', count);
111
- }
112
- });
113
-
114
- this._colorObserver = new MutationObserver(() => {
115
- const metaColor = appContent?.shadowRoot?.querySelector('meta[name="color"]');
116
- if (!metaColor) {
117
- return;
118
- }
119
- const color = metaColor.getAttribute('content');
120
- if (color) {
121
- this.color = color;
122
- }
123
- });
124
-
125
- const root = appContent.shadowRoot;
126
- if (root) {
127
- const options: MutationObserverInit = {
128
- subtree: true,
129
- attributes: true,
130
- attributeFilter: ['content'],
131
- childList: true,
132
- };
133
-
134
- this._countObserver.observe(root, options);
135
- this._colorObserver.observe(root, options);
136
- }
128
+ this._countObserver.observe(root as unknown as Element);
129
+ this._colorObserver.observe(root as unknown as Element);
130
+ this._metaObserved = true;
137
131
  }
138
132
 
139
133
  _registerActions() {
@@ -34,6 +34,8 @@ export default class ZnFormGroup extends ZincElement {
34
34
 
35
35
  /** The form groups help text. If you need to display HTML, use the `help-text` slot instead. */
36
36
  @property({attribute: 'help-text'}) helpText = '';
37
+ @property({attribute: 'cols', type: Boolean}) forceCols = false;
38
+ @property({attribute: 'layout', type: String}) layout = "1,2";
37
39
 
38
40
  render() {
39
41
  const hasLabelSlot = this.hasSlotController.test('label');
@@ -46,42 +48,45 @@ export default class ZnFormGroup extends ZincElement {
46
48
  return html`
47
49
  <fieldset
48
50
  part="form-control"
49
- class=${classMap({
51
+ class="${classMap({
50
52
  'form-control': true,
51
53
  'form-control--has-label': hasLabel,
52
54
  'form-control--has-label-tooltip': hasLabelTooltip,
53
55
  'form-control--has-help-text': hasHelpText
54
- })}
56
+ })}"
55
57
  aria-labelledby="label"
56
58
  aria-describedby="help-text">
57
59
 
58
- <zn-cols layout="1,2" mc="2" part="form-control-container" class="form-control__container">
59
- ${hasLabel ? html`
60
+ <zn-cols layout="${this.layout}" part="form-control-container" class="form-control__container">
61
+ ${hasLabel || hasHelpText || this.forceCols ? html`
60
62
  <div>
61
- <label
62
- part="form-control-label"
63
- id="label"
64
- class="form-control__label"
65
- aria-hidden=${hasLabel ? 'false' : 'true'}>
66
- <slot name="label">${this.label}</slot>
67
- ${hasLabelTooltip
68
- ? html`
69
- <zn-tooltip class="form-control--label-tooltip">
70
- <div slot="content">
71
- <slot name="label-tooltip">${this.labelTooltip}</slot>
72
- </div>
73
- <zn-icon src="info"></zn-icon>
74
- </zn-tooltip>`
75
- : ''}
76
- </label>
77
- </div>` : html``}
78
63
 
79
- ${hasHelpText ? html`
80
- <div
81
- part="form-control-help-text"
82
- id="help-text"
83
- class="form-control__help-text">
84
- <slot name="help-text">${this.helpText}</slot>
64
+ ${hasLabel ? html`
65
+ <label
66
+ part="form-control-label"
67
+ id="label"
68
+ class="form-control__label"
69
+ aria-hidden="${hasLabel ? 'false' : 'true'}">
70
+ <slot name="label">${this.label}</slot>
71
+ ${hasLabelTooltip
72
+ ? html`
73
+ <zn-tooltip class="form-control--label-tooltip">
74
+ <div slot="content">
75
+ <slot name="label-tooltip">${this.labelTooltip}</slot>
76
+ </div>
77
+ <zn-icon src="info"></zn-icon>
78
+ </zn-tooltip>`
79
+ : ''}
80
+ </label>` : html``}
81
+
82
+ ${hasHelpText ? html`
83
+ <div
84
+ part="form-control-help-text"
85
+ id="help-text"
86
+ class="form-control__help-text">
87
+ <slot name="help-text">${this.helpText}</slot>
88
+ </div>` : html``}
89
+
85
90
  </div>` : html``}
86
91
 
87
92
  <div part="form-control-input" class="form-control-input">
@@ -5,6 +5,7 @@
5
5
  display: block;
6
6
  line-height: var(--zn-line-height-dense);
7
7
  max-width: var(--zn-container-lg);
8
+ --zn-col-gap: calc(var(--zn-spacing-medium, 20px) * 3)
8
9
  }
9
10
 
10
11
  .form-control {
@@ -4,7 +4,7 @@ import {HasSlotController} from "../../internal/slot";
4
4
  import {property} from 'lit/decorators.js';
5
5
  import {watch} from "../../internal/watch";
6
6
  import ZincElement from '../../internal/zinc-element';
7
- import type ZnNavbar from "../navbar";
7
+ import ZnNavbar from "../navbar";
8
8
 
9
9
  import styles from './header.scss';
10
10
 
@@ -51,6 +51,8 @@ export default class ZnHeader extends ZincElement {
51
51
 
52
52
  @property({attribute: 'previous-target'}) previousTarget: string;
53
53
 
54
+ @property({attribute: 'hide-breadcrumb', type: Boolean}) hideBreadcrumb: boolean = false;
55
+
54
56
  private navbar: ZnNavbar;
55
57
 
56
58
  // Attach any event listeners you may need
@@ -106,7 +108,7 @@ export default class ZnHeader extends ZincElement {
106
108
  const hasPreviousPath = this.previousPath;
107
109
  const hasEntityId = this.entityId;
108
110
  const hasFullLocation = this.fullLocation;
109
- const hasBreadcrumb = this.hasSlotController.test('breadcrumb');
111
+ const hasBreadcrumb = !this.hideBreadcrumb && this.hasSlotController.test('breadcrumb');
110
112
 
111
113
  // Do not add formatting within breadcrumb or navigation - css:empty in use
112
114
  const header = html`
@@ -137,9 +139,6 @@ export default class ZnHeader extends ZincElement {
137
139
 
138
140
 
139
141
  <div class="content" part="content">
140
- ${hasBreadcrumb ? html`
141
- <slot name="breadcrumb" class="breadcrumb"></slot>` : null}
142
-
143
142
  ${hasPreviousPath ? html`
144
143
  <a href="${this.previousPath}" class="caption__back"
145
144
  data-target="${this.previousTarget ? this.previousTarget : ''}">
@@ -156,6 +155,8 @@ export default class ZnHeader extends ZincElement {
156
155
  <span class="header__caption" part="header-caption">
157
156
  ${this.icon ? html`
158
157
  <zn-icon class="header__icon" src="${this.icon}"></zn-icon>` : null}
158
+ ${hasBreadcrumb ? html`
159
+ <slot name="breadcrumb" class="breadcrumb"></slot>` : null}
159
160
  <slot name="caption">${this.caption}</slot>
160
161
  </span>
161
162
  ${this.description ? html`
@@ -14,12 +14,14 @@
14
14
  justify-content: space-between;
15
15
  gap: var(--zn-spacing-2x-small);
16
16
  max-width: 100%;
17
+ min-height: 58px;
17
18
  }
18
19
 
19
20
  &__caption {
20
21
  font-weight: var(--zn-font-weight-medium);
21
- font-size: var(--zn-font-size-x-large);
22
+ font-size: var(--zn-font-size-large);
22
23
  color: rgba(var(--zn-text-panel-title), 100%);
24
+ line-height: 28px;
23
25
  margin: 0;
24
26
  overflow: hidden;
25
27
  white-space: nowrap;
@@ -30,17 +32,15 @@
30
32
  text-wrap: auto;
31
33
  }
32
34
 
33
- &--has-breadcrumb {
34
- .caption {
35
- padding-top: 0;
36
- }
37
- }
38
-
39
35
  // remove the padding on the nav
40
36
  &--has-nav {
41
37
  .caption {
42
38
  padding-bottom: 0;
43
39
  }
40
+
41
+ .content {
42
+ padding-bottom: var(--zn-spacing-x-small);
43
+ }
44
44
  }
45
45
  }
46
46
 
@@ -51,10 +51,6 @@
51
51
  color: inherit;
52
52
  }
53
53
 
54
- .content {
55
- padding: var(--zn-spacing-x-small) 0;
56
- }
57
-
58
54
  :host([transparent]) > div {
59
55
  background-color: transparent;
60
56
  }
@@ -71,6 +67,8 @@
71
67
 
72
68
  :host #nav-container {
73
69
  gap: 0;
70
+ border-bottom: 1px solid rgb(var(--zn-border-color));
71
+ margin: 0 3px;
74
72
  }
75
73
 
76
74
  .alt-overlay {
@@ -104,14 +102,21 @@
104
102
  }
105
103
  }
106
104
 
105
+ :host([transparent]) {
106
+ .header {
107
+ margin: 0;
108
+
109
+ &__caption {
110
+
111
+ }
112
+ }
113
+ }
107
114
 
108
115
  .header {
109
116
  display: flex;
110
117
  flex-direction: column;
111
- border-bottom-width: 1px;
112
- // border-top-width: 1px;
118
+ margin: 0;
113
119
  overflow: hidden;
114
- background-color: rgba(var(--zn-panel), var(--zn-panel-opacity));
115
120
 
116
121
  .content {
117
122
  display: flex;
@@ -130,7 +135,7 @@
130
135
  justify-content: center;
131
136
  flex-direction: column;
132
137
  gap: 1px;
133
- min-height: 43px;
138
+ min-height: 24px;
134
139
  flex-shrink: 1;
135
140
  max-width: 100%;
136
141
  }
@@ -158,19 +163,15 @@
158
163
  }
159
164
 
160
165
  .breadcrumb {
161
- flex-basis: 100%;
162
- font-size: var(--zn-font-size-x-small);
163
- font-weight: var(--zn-font-weight-semibold);
164
- color: rgb(var(--zn-text));
165
-
166
- display: flex;
166
+ display: inline-flex;
167
167
  align-items: center;
168
168
  gap: var(--zn-spacing-2x-small);
169
+ font: inherit;
170
+ color: inherit;
169
171
 
170
172
  &::slotted(a) {
171
- font-size: 0.875rem;
173
+ font: inherit;
172
174
  color: inherit;
173
- font-weight: var(--zn-font-weight-normal);
174
175
  text-decoration: inherit;
175
176
  }
176
177
 
@@ -178,10 +179,11 @@
178
179
  color: rgb(var(--zn-primary));
179
180
  }
180
181
 
181
- &::slotted(a:not(:last-of-type))::after {
182
+ &::slotted(a)::after {
182
183
  content: '>';
183
184
  margin-left: var(--zn-spacing-2x-small);
184
185
  color: rgb(var(--zn-text-muted));
186
+ font-weight: var(--zn-font-weight-normal);
185
187
  }
186
188
  }
187
189
 
@@ -211,17 +213,17 @@
211
213
 
212
214
  h1 {
213
215
  flex-grow: 1;
214
- font-size: 18px;
215
216
 
216
217
  overflow: hidden;
217
218
  text-overflow: ellipsis;
218
219
 
219
220
  color: rgb(var(--zn-text-page-title));
220
221
  margin: 0;
222
+ font-size: var(--zn-font-size-medium);
221
223
  font-weight: var(--zn-font-weight-medium);
222
224
 
223
225
  @media screen and (min-width: var(--zn-container-lg)) {
224
- font-size: var(--zn-font-size-x-large);
226
+ font-size: var(--zn-font-size-large);
225
227
  }
226
228
 
227
229
  &:empty {
@@ -108,6 +108,12 @@ export default class ZnInlineEdit extends ZincElement implements ZincFormControl
108
108
  /** Enables search/filtering on select inputs. */
109
109
  @property({ type: Boolean }) search: boolean;
110
110
 
111
+ /**
112
+ * Allows entering values that aren't in the options list on select inputs ("free text"). Setting this implies
113
+ * `input-type="select"` (unless a data `provider` is used) and forwards the behavior to the inner `<zn-select>`.
114
+ */
115
+ @property({ type: Boolean, attribute: 'free-text' }) freeText: boolean;
116
+
111
117
  /** The input's help text. If you need to display HTML, use the `help-text` slot instead. **/
112
118
  @property({ attribute: 'help-text' }) helpText: string = '';
113
119
 
@@ -315,6 +321,11 @@ export default class ZnInlineEdit extends ZincElement implements ZincFormControl
315
321
  this.inputType = 'data-select';
316
322
  }
317
323
 
324
+ // Free text is a select capability — imply a select input when it's set (unless a data provider is used)
325
+ if (this.freeText && !this.selectProvider) {
326
+ this.inputType = 'select';
327
+ }
328
+
318
329
  let input: HTMLTemplateResult;
319
330
  switch (this.inputType) {
320
331
  case 'select':
@@ -457,7 +468,8 @@ export default class ZnInlineEdit extends ZincElement implements ZincFormControl
457
468
  multiple=${ifDefined(this.multiple)}
458
469
  clearable=${ifDefined(this.isEditing ? this.clearable : undefined)}
459
470
  ?search=${this.search}
460
- non-removable
471
+ ?free-text=${this.freeText}
472
+ ?non-removable=${!this.isEditing}
461
473
  data-uri=${ifDefined(this.dataUri)}
462
474
  context-data=${ifDefined(this.contextData)}
463
475
  @zn-input="${this.handleInput}"
@@ -309,6 +309,56 @@ describe('<zn-inline-edit>', () => {
309
309
  expect(formData.get('tags')).to.equal('');
310
310
  });
311
311
 
312
+ // -- Free text --
313
+
314
+ it('should pass free-text to the inner select', async () => {
315
+ const el = await fixture<ZnInlineEdit>(html`
316
+ <zn-inline-edit input-type="select" free-text>
317
+ <zn-option value="a">A</zn-option>
318
+ </zn-inline-edit>
319
+ `);
320
+ await el.updateComplete;
321
+
322
+ const select = el.shadowRoot?.querySelector('zn-select') as ZnSelect;
323
+ expect(select).to.exist;
324
+ expect(select.freeText).to.be.true;
325
+ });
326
+
327
+ it('should render a free-text select when free-text is set without an input-type', async () => {
328
+ const el = await fixture<ZnInlineEdit>(html`
329
+ <zn-inline-edit free-text></zn-inline-edit>
330
+ `);
331
+ await el.updateComplete;
332
+
333
+ const select = el.shadowRoot?.querySelector('zn-select') as ZnSelect;
334
+ expect(select, 'free-text should imply a select input').to.exist;
335
+ expect(select.freeText).to.be.true;
336
+ });
337
+
338
+ it('should make inner select tags removable only while editing', async () => {
339
+ const el = await fixture<ZnInlineEdit>(html`
340
+ <zn-inline-edit free-text multiple value="a">
341
+ <zn-option value="a">A</zn-option>
342
+ </zn-inline-edit>
343
+ `);
344
+ await el.updateComplete;
345
+
346
+ const select = el.shadowRoot?.querySelector('zn-select') as ZnSelect;
347
+ expect(select).to.exist;
348
+
349
+ // Display mode: tags are not removable (no X)
350
+ expect(select.nonRemovable).to.be.true;
351
+
352
+ // Enter edit mode
353
+ const editBtn = el.shadowRoot!.querySelector<HTMLElement>('.button--edit')!;
354
+ editBtn.dispatchEvent(new MouseEvent('click', {bubbles: true, composed: true}));
355
+ await el.updateComplete;
356
+ await select.updateComplete;
357
+
358
+ // Editing: tags become removable (X shows)
359
+ expect(select.nonRemovable).to.be.false;
360
+ });
361
+
312
362
  it('should flatten array to string when multiple is not set', async () => {
313
363
  const el = await fixture<ZnInlineEdit>(
314
364
  html`<zn-inline-edit value="hello"></zn-inline-edit>`
@@ -1,5 +1,6 @@
1
1
  import '../../../dist/zn.min.js';
2
2
  import {expect, fixture, html} from '@open-wc/testing';
3
+ import {clearFormStoreValues} from '../../utilities/form';
3
4
  import type ZnInput from './input.component';
4
5
 
5
6
  describe('<zn-input>', () => {
@@ -40,4 +41,80 @@ describe('<zn-input>', () => {
40
41
  expect(wrapperEscapes, 'wrapper should not see Escape on press 1').to.equal(0);
41
42
  });
42
43
  });
44
+
45
+ describe('persisted state', () => {
46
+ const formStoreKey = 'input-test-form-state';
47
+ const elementStoreKey = 'input-test-element-state';
48
+
49
+ beforeEach(() => {
50
+ clearFormStoreValues(formStoreKey);
51
+ clearFormStoreValues(elementStoreKey);
52
+ });
53
+
54
+ afterEach(() => {
55
+ clearFormStoreValues(formStoreKey);
56
+ clearFormStoreValues(elementStoreKey);
57
+ });
58
+
59
+ it('restores values from a form store-key after refresh', async () => {
60
+ const firstForm = await fixture<HTMLFormElement>(html`
61
+ <form store-key=${formStoreKey}>
62
+ <zn-input name="first_name"></zn-input>
63
+ </form>
64
+ `);
65
+ const firstInput = firstForm.querySelector('zn-input') as ZnInput;
66
+ await firstInput.updateComplete;
67
+
68
+ firstInput.value = 'Ada';
69
+ firstInput.dispatchEvent(new CustomEvent('zn-input', {bubbles: true, composed: true}));
70
+
71
+ const secondForm = await fixture<HTMLFormElement>(html`
72
+ <form store-key=${formStoreKey}>
73
+ <zn-input name="first_name"></zn-input>
74
+ </form>
75
+ `);
76
+ const secondInput = secondForm.querySelector('zn-input') as ZnInput;
77
+ await secondInput.updateComplete;
78
+
79
+ expect(secondInput.value).to.equal('Ada');
80
+ });
81
+
82
+ it('restores values from an element store-key without a form store-key', async () => {
83
+ const firstInput = await fixture<ZnInput>(html`<zn-input store-key=${elementStoreKey}></zn-input>`);
84
+ await firstInput.updateComplete;
85
+
86
+ firstInput.value = 'Lovelace';
87
+ firstInput.dispatchEvent(new CustomEvent('zn-input', {bubbles: true, composed: true}));
88
+
89
+ const secondInput = await fixture<ZnInput>(html`<zn-input store-key=${elementStoreKey}></zn-input>`);
90
+ await secondInput.updateComplete;
91
+
92
+ expect(secondInput.value).to.equal('Lovelace');
93
+ });
94
+
95
+ it('clears values for a form store-key', async () => {
96
+ const firstForm = await fixture<HTMLFormElement>(html`
97
+ <form store-key=${formStoreKey}>
98
+ <zn-input name="first_name"></zn-input>
99
+ </form>
100
+ `);
101
+ const firstInput = firstForm.querySelector('zn-input') as ZnInput;
102
+ await firstInput.updateComplete;
103
+
104
+ firstInput.value = 'Grace';
105
+ firstInput.dispatchEvent(new CustomEvent('zn-input', {bubbles: true, composed: true}));
106
+
107
+ clearFormStoreValues(firstForm);
108
+
109
+ const secondForm = await fixture<HTMLFormElement>(html`
110
+ <form store-key=${formStoreKey}>
111
+ <zn-input name="first_name"></zn-input>
112
+ </form>
113
+ `);
114
+ const secondInput = secondForm.querySelector('zn-input') as ZnInput;
115
+ await secondInput.updateComplete;
116
+
117
+ expect(secondInput.value).to.equal('');
118
+ });
119
+ });
43
120
  });