@redvars/peacock 3.5.0 → 3.5.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 (148) hide show
  1. package/dist/BaseButton-DuASuVth.js +219 -0
  2. package/dist/BaseButton-DuASuVth.js.map +1 -0
  3. package/dist/BaseHyperlinkMixin-BNuwbiEf.js +65 -0
  4. package/dist/BaseHyperlinkMixin-BNuwbiEf.js.map +1 -0
  5. package/dist/assets/components.css +1 -1
  6. package/dist/assets/components.css.map +1 -1
  7. package/dist/assets/styles.css +1 -1
  8. package/dist/assets/styles.css.map +1 -1
  9. package/dist/banner.js +12 -27
  10. package/dist/banner.js.map +1 -1
  11. package/dist/{button-DMN1dPAg.js → button-DouvOfEU.js} +77 -251
  12. package/dist/button-DouvOfEU.js.map +1 -0
  13. package/dist/{button-group-CX9CUUXk.js → button-group-CEdMwvJJ.js} +71 -42
  14. package/dist/button-group-CEdMwvJJ.js.map +1 -0
  15. package/dist/button-group.js +5 -5
  16. package/dist/button.js +3 -3
  17. package/dist/card.js +18 -73
  18. package/dist/card.js.map +1 -1
  19. package/dist/chart-bar.js.map +1 -1
  20. package/dist/chart-doughnut.js +2 -2
  21. package/dist/chart-doughnut.js.map +1 -1
  22. package/dist/chart-pie.js +2 -2
  23. package/dist/chart-pie.js.map +1 -1
  24. package/dist/chart-stacked-bar.js.map +1 -1
  25. package/dist/code-highlighter.js +2 -1
  26. package/dist/code-highlighter.js.map +1 -1
  27. package/dist/custom-elements-jsdocs.json +3105 -1494
  28. package/dist/custom-elements.json +9244 -7829
  29. package/dist/fab.js +421 -9
  30. package/dist/fab.js.map +1 -1
  31. package/dist/index.js +6 -6
  32. package/dist/{select-4pl4XBj7.js → navigation-rail-Lxetd5-Z.js} +2214 -1090
  33. package/dist/navigation-rail-Lxetd5-Z.js.map +1 -0
  34. package/dist/notification.js +3 -2
  35. package/dist/notification.js.map +1 -1
  36. package/dist/peacock-loader.js +22 -10
  37. package/dist/peacock-loader.js.map +1 -1
  38. package/dist/search.js +4 -0
  39. package/dist/search.js.map +1 -1
  40. package/dist/src/__mixins/BaseButtonMixin.d.ts +20 -0
  41. package/dist/src/__mixins/BaseHyperlinkMixin.d.ts +18 -0
  42. package/dist/src/__mixins/MixinConstructor.d.ts +1 -0
  43. package/dist/src/banner/banner.d.ts +0 -4
  44. package/dist/src/button/BaseButton.d.ts +4 -47
  45. package/dist/src/button/button/button.d.ts +32 -3
  46. package/dist/src/button/button-group/button-group.d.ts +2 -2
  47. package/dist/src/button/icon-button/icon-button.d.ts +33 -8
  48. package/dist/src/card/card.d.ts +4 -15
  49. package/dist/src/fab/fab.d.ts +4 -35
  50. package/dist/src/focus-ring/focus-ring.d.ts +11 -5
  51. package/dist/src/index.d.ts +3 -1
  52. package/dist/src/link/link.d.ts +1 -1
  53. package/dist/src/navigation-rail/index.d.ts +2 -0
  54. package/dist/src/navigation-rail/navigation-rail-item.d.ts +55 -0
  55. package/dist/src/navigation-rail/navigation-rail.d.ts +71 -0
  56. package/dist/src/sidebar-menu/index.d.ts +3 -0
  57. package/dist/src/sidebar-menu/sidebar-menu-item.d.ts +58 -0
  58. package/dist/src/sidebar-menu/sidebar-menu.d.ts +38 -0
  59. package/dist/src/sidebar-menu/sidebar-sub-menu.d.ts +35 -0
  60. package/dist/src/toolbar/toolbar.d.ts +10 -10
  61. package/dist/src/tooltip/tooltip.d.ts +3 -0
  62. package/dist/src/url-field/index.d.ts +1 -0
  63. package/dist/src/url-field/url-field.d.ts +48 -0
  64. package/dist/test/sidebar-menu.test.d.ts +1 -0
  65. package/dist/toolbar.js +10 -10
  66. package/dist/toolbar.js.map +1 -1
  67. package/dist/tsconfig.tsbuildinfo +1 -1
  68. package/package.json +1 -1
  69. package/readme.md +73 -65
  70. package/scss/mixin.scss +16 -0
  71. package/src/__mixins/BaseButtonMixin.ts +83 -0
  72. package/src/__mixins/BaseHyperlinkMixin.ts +68 -0
  73. package/src/__mixins/MixinConstructor.ts +1 -0
  74. package/src/{__base_element → __mixins}/README.md +2 -2
  75. package/src/banner/banner.scss +18 -22
  76. package/src/banner/banner.ts +1 -7
  77. package/src/button/BaseButton.ts +11 -100
  78. package/src/button/button/button-sizes.scss +4 -2
  79. package/src/button/button/button.ts +76 -23
  80. package/src/button/button-group/button-group.ts +2 -2
  81. package/src/button/icon-button/icon-button.ts +75 -33
  82. package/src/card/card.ts +11 -71
  83. package/src/chart-bar/chart-bar.ts +9 -14
  84. package/src/chart-bar/chart-stacked-bar.ts +12 -18
  85. package/src/chart-doughnut/chart-doughnut.ts +23 -27
  86. package/src/chart-pie/chart-pie.ts +19 -23
  87. package/src/checkbox/checkbox.scss +17 -34
  88. package/src/checkbox/checkbox.ts +3 -1
  89. package/src/code-highlighter/code-highlighter.scss +1 -0
  90. package/src/code-highlighter/code-highlighter.ts +1 -1
  91. package/src/date-picker/date-picker.ts +1 -1
  92. package/src/elevation/elevation.scss +5 -5
  93. package/src/fab/fab.ts +29 -100
  94. package/src/focus-ring/focus-ring.ts +47 -40
  95. package/src/index.ts +3 -1
  96. package/src/input/input.ts +3 -1
  97. package/src/link/link.ts +2 -2
  98. package/src/menu/menu-item/menu-item.ts +3 -1
  99. package/src/navigation-rail/index.ts +2 -0
  100. package/src/navigation-rail/navigation-rail-item.scss +216 -0
  101. package/src/navigation-rail/navigation-rail-item.ts +223 -0
  102. package/src/navigation-rail/navigation-rail.scss +72 -0
  103. package/src/navigation-rail/navigation-rail.ts +149 -0
  104. package/src/notification/notification.ts +3 -2
  105. package/src/number-field/number-field.ts +6 -4
  106. package/src/pagination/pagination.ts +6 -4
  107. package/src/peacock-loader.ts +22 -5
  108. package/src/search/search.ts +4 -0
  109. package/src/sidebar-menu/demo/index.html +68 -0
  110. package/src/sidebar-menu/index.ts +3 -0
  111. package/src/sidebar-menu/sidebar-menu-item.scss +102 -0
  112. package/src/sidebar-menu/sidebar-menu-item.ts +151 -0
  113. package/src/{tree-view/tree-view.scss → sidebar-menu/sidebar-menu.scss} +1 -1
  114. package/src/sidebar-menu/sidebar-menu.ts +182 -0
  115. package/src/sidebar-menu/sidebar-sub-menu.scss +130 -0
  116. package/src/sidebar-menu/sidebar-sub-menu.ts +160 -0
  117. package/src/skeleton/skeleton.scss +18 -24
  118. package/src/snackbar/snackbar.ts +1 -1
  119. package/src/tabs/tab.ts +4 -3
  120. package/src/text/text.css-component.scss +7 -1
  121. package/src/time-picker/time-picker.ts +1 -1
  122. package/src/toolbar/toolbar.ts +10 -10
  123. package/src/tooltip/tooltip.ts +24 -0
  124. package/src/url-field/index.ts +1 -0
  125. package/src/url-field/url-field.scss +50 -0
  126. package/src/url-field/url-field.ts +239 -0
  127. package/dist/button-DMN1dPAg.js.map +0 -1
  128. package/dist/button-group-CX9CUUXk.js.map +0 -1
  129. package/dist/fab-C5Nzxk0E.js +0 -497
  130. package/dist/fab-C5Nzxk0E.js.map +0 -1
  131. package/dist/select-4pl4XBj7.js.map +0 -1
  132. package/dist/spread-B5cgadZl.js +0 -32
  133. package/dist/spread-B5cgadZl.js.map +0 -1
  134. package/dist/src/__base_element/BaseHyperlink.d.ts +0 -20
  135. package/dist/src/tree-view/index.d.ts +0 -2
  136. package/dist/src/tree-view/tree-node.d.ts +0 -69
  137. package/dist/src/tree-view/tree-view.d.ts +0 -40
  138. package/dist/src/tree-view/wc-tree-view.d.ts +0 -6
  139. package/dist/test/tree-view.test.d.ts +0 -1
  140. package/dist/throttle-C7ZAPqtu.js +0 -24
  141. package/dist/throttle-C7ZAPqtu.js.map +0 -1
  142. package/src/__base_element/BaseHyperlink.ts +0 -42
  143. package/src/tree-view/demo/index.html +0 -57
  144. package/src/tree-view/index.ts +0 -2
  145. package/src/tree-view/tree-node.scss +0 -101
  146. package/src/tree-view/tree-node.ts +0 -268
  147. package/src/tree-view/tree-view.ts +0 -182
  148. package/src/tree-view/wc-tree-view.ts +0 -9
@@ -0,0 +1,160 @@
1
+ import { html, LitElement } from 'lit';
2
+ import { property, query } from 'lit/decorators.js';
3
+ import { classMap } from 'lit/directives/class-map.js';
4
+ import { styleMap } from 'lit/directives/style-map.js';
5
+ import styles from './sidebar-sub-menu.scss';
6
+
7
+ /**
8
+ * @label Sidebar Sub Menu
9
+ * @tag wc-sidebar-sub-menu
10
+ * @rawTag sidebar-sub-menu
11
+ * @parentRawTag sidebar-menu
12
+ * @summary A sidebar sub menu groups sidebar menu items and handles expand/collapse behavior.
13
+ *
14
+ * @example
15
+ * ```html
16
+ * <wc-sidebar-sub-menu label="Parent" expanded>
17
+ * <wc-sidebar-menu-item label="Child"></wc-sidebar-menu-item>
18
+ * </wc-sidebar-sub-menu>
19
+ * ```
20
+ * @tags navigation
21
+ */
22
+ export class SidebarSubMenu extends LitElement {
23
+ static styles = [styles];
24
+
25
+ @property({ type: String, reflect: true })
26
+ value: string = '';
27
+
28
+ @property({ type: String, reflect: true })
29
+ label: string = '';
30
+
31
+ @property({ type: String, reflect: true })
32
+ icon: string = '';
33
+
34
+ @property({ type: Boolean, reflect: true })
35
+ disabled: boolean = false;
36
+
37
+ @property({ type: Boolean, reflect: true })
38
+ selected: boolean = false;
39
+
40
+ @property({ type: Boolean, reflect: true })
41
+ expanded: boolean = false;
42
+
43
+ @property({ type: Number, reflect: true })
44
+ level: number = 0;
45
+
46
+ @query('.sidebar-sub-menu-inner')
47
+ private readonly _nativeElement!: HTMLElement | null;
48
+
49
+ override focus() {
50
+ this._nativeElement?.focus();
51
+ }
52
+
53
+ override blur() {
54
+ this._nativeElement?.blur();
55
+ }
56
+
57
+ connectedCallback() {
58
+ super.connectedCallback();
59
+ this._updateChildLevels();
60
+ }
61
+
62
+ updated(changedProps: Map<string, unknown>) {
63
+ super.updated(changedProps);
64
+
65
+ if (changedProps.has('expanded')) {
66
+ this.setAttribute('aria-expanded', String(this.expanded));
67
+ if (this.expanded) {
68
+ this.setAttribute('data-expanded', '');
69
+ } else {
70
+ this.removeAttribute('data-expanded');
71
+ }
72
+ }
73
+ }
74
+
75
+ private _getChildNodes(): Element[] {
76
+ return Array.from(this.children).filter(el => {
77
+ const tag = el.tagName.toLowerCase();
78
+ return tag === 'wc-sidebar-menu-item' || tag === 'wc-sidebar-sub-menu';
79
+ });
80
+ }
81
+
82
+ private _updateChildLevels = () => {
83
+ this._getChildNodes().forEach(child => {
84
+ if ('level' in child) {
85
+ // eslint-disable-next-line no-param-reassign
86
+ (child as { level: number }).level = this.level + 1;
87
+ }
88
+ });
89
+ };
90
+
91
+ private _onClick = () => {
92
+ if (this.disabled) return;
93
+
94
+ if (this._getChildNodes().length > 0) {
95
+ this.expanded = !this.expanded;
96
+ }
97
+
98
+ this.dispatchEvent(
99
+ new CustomEvent('sidebar-menu-item:click', {
100
+ bubbles: true,
101
+ composed: true,
102
+ detail: { value: this.value, label: this.label },
103
+ }),
104
+ );
105
+ };
106
+
107
+ override render() {
108
+ const hasChildren = this._getChildNodes().length > 0;
109
+ const wrapperClasses = classMap({
110
+ 'sidebar-sub-menu': true,
111
+ });
112
+ const innerClasses = classMap({
113
+ 'sidebar-sub-menu-inner': true,
114
+ disabled: this.disabled,
115
+ selected: this.selected,
116
+ });
117
+
118
+ const inlineStyles = styleMap({
119
+ paddingLeft: `calc(var(--sidebar-menu-item-height, 2.5rem) * ${this.level})`,
120
+ });
121
+
122
+ return html`
123
+ <div class="${wrapperClasses}" style="${inlineStyles}">
124
+ <div
125
+ id="item"
126
+ class="${innerClasses}"
127
+ role="treeitem"
128
+ aria-label="${this.label}"
129
+ aria-selected="${String(this.selected)}"
130
+ aria-disabled="${this.disabled}"
131
+ aria-expanded="${this.expanded}"
132
+ @click="${this._onClick}"
133
+ @keydown="${(e: KeyboardEvent) => {
134
+ if (e.key === 'Enter' || e.key === ' ') {
135
+ e.preventDefault();
136
+ this._onClick();
137
+ }
138
+ }}"
139
+ tabindex="${this.disabled ? -1 : 0}"
140
+ >
141
+ <wc-focus-ring class="focus-ring" for="item"></wc-focus-ring>
142
+ <div class="background"></div>
143
+ <wc-ripple class="ripple"></wc-ripple>
144
+ <div class="sidebar-sub-menu-content">
145
+ ${this.icon ? html`<wc-icon name="${this.icon}"></wc-icon>` : ''}
146
+ <span class="sidebar-sub-menu-label">${this.label}</span>
147
+ </div>
148
+ ${hasChildren
149
+ ? html`
150
+ <wc-icon class="expand-icon" name="arrow_drop_down" aria-hidden="true"></wc-icon>
151
+ `
152
+ : ''}
153
+ </div>
154
+ <div class="sidebar-sub-menu-children ${(!hasChildren || !this.expanded) ? 'hidden' : ''}">
155
+ <slot @slotchange="${this._updateChildLevels}"></slot>
156
+ </div>
157
+ </div>
158
+ `;
159
+ }
160
+ }
@@ -1,43 +1,37 @@
1
+ @use '../../scss/mixin';
2
+
3
+ @include mixin.base-styles;
4
+
5
+
6
+
7
+
1
8
  :host {
9
+ position: relative;
10
+ inset: 0;
2
11
  --skeleton-container-color: var(--color-surface-container);
3
12
  --skeleton-element: var(--color-on-surface);
4
- display: inline-block;
5
- height: 3rem;
6
- width: 10rem;
13
+ display: flex;
14
+ pointer-events: none;
15
+ }
16
+
17
+ .skeleton,
18
+ .skeleton::before {
19
+ inset: 0;
20
+ position: absolute;
21
+ @include mixin.apply-container-shape(skeleton);
7
22
  }
8
23
 
9
24
  .skeleton {
10
- position: relative;
11
- padding: 0;
12
- border: none;
13
25
  background: var(--skeleton-container-color);
14
- box-shadow: none;
15
26
  overflow: hidden;
16
- pointer-events: none;
17
- width: 100%;
18
- height: 100%;
19
- border-start-start-radius: var(--skeleton-container-shape-start-start);
20
- border-start-end-radius: var(--skeleton-container-shape-start-end);
21
- border-end-start-radius: var(--skeleton-container-shape-end-start);
22
- border-end-end-radius: var(--skeleton-container-shape-end-end);
23
- corner-shape: var(--skeleton-container-shape-variant);
24
27
 
25
28
  &::before {
26
- position: absolute;
27
- left: 0;
28
- top: 0;
29
29
  animation: 3s ease-in-out skeleton infinite;
30
30
  background: var(--skeleton-element);
31
31
  block-size: 100%;
32
32
  content: "";
33
33
  inline-size: 100%;
34
34
  will-change: transform-origin, transform, opacity;
35
-
36
- border-start-start-radius: var(--skeleton-container-shape-start-start);
37
- border-start-end-radius: var(--skeleton-container-shape-start-end);
38
- border-end-start-radius: var(--skeleton-container-shape-end-start);
39
- border-end-end-radius: var(--skeleton-container-shape-end-end);
40
- corner-shape: var(--skeleton-container-shape-variant);
41
35
  }
42
36
  }
43
37
 
@@ -221,8 +221,8 @@ export class Snackbar extends LitElement {
221
221
  size='small'
222
222
  aria-label="Dismiss notification"
223
223
  @click=${this.handleCloseClick}
224
- name="close"
225
224
  >
225
+ <wc-icon name="close"></wc-icon>
226
226
  </wc-icon-button>`
227
227
  : nothing}
228
228
  </div>
package/src/tabs/tab.ts CHANGED
@@ -193,6 +193,7 @@ export class Tab extends LitElement {
193
193
 
194
194
  if (!isLink) {
195
195
  return html`<button
196
+ id="button"
196
197
  class=${classMap(cssClasses)}
197
198
  tabindex="0"
198
199
  @mousedown=${this.__handlePress}
@@ -241,7 +242,7 @@ export class Tab extends LitElement {
241
242
 
242
243
  renderPrimaryTabContent() {
243
244
  return html`
244
- <wc-focus-ring class="focus-ring" .control=${this} .forElement=${this.tabElement}></wc-focus-ring>
245
+ <wc-focus-ring class="focus-ring" for='button'></wc-focus-ring>
245
246
  <wc-elevation class="elevation"></wc-elevation>
246
247
  <div class="background"></div>
247
248
  <div class="outline"></div>
@@ -267,7 +268,7 @@ export class Tab extends LitElement {
267
268
 
268
269
  renderSecondaryTabContent() {
269
270
  return html`
270
- <wc-focus-ring class="focus-ring" .control=${this} .forElement=${this.tabElement}></wc-focus-ring>
271
+ <wc-focus-ring class="focus-ring" for='button'></wc-focus-ring>
271
272
  <wc-elevation class="elevation"></wc-elevation>
272
273
  <div class="background"></div>
273
274
  <div class="outline"></div>
@@ -300,7 +301,7 @@ export class Tab extends LitElement {
300
301
 
301
302
  renderSegmentedTabContent() {
302
303
  return html`
303
- <wc-focus-ring class="focus-ring" .control=${this} .forElement=${this.tabElement}></wc-focus-ring>
304
+ <wc-focus-ring class="focus-ring" for='button'></wc-focus-ring>
304
305
  <wc-elevation class="elevation"></wc-elevation>
305
306
  <div class="background"></div>
306
307
  <div class="outline"></div>
@@ -49,11 +49,17 @@ $sizes: "large", "medium", 'small';
49
49
  }
50
50
 
51
51
  .text-code-block {
52
- display: inline-flex;
53
52
  background-color: var(--color-surface-variant);
54
53
  text-shadow: 0 1px 1px var(--color-surface-variant);
55
54
  color: var(--color-on-surface);
56
55
  border-radius: var(--shape-corner-extra-small);
57
56
  padding: var(--spacing-050);
58
57
  font-family: var(--font-family-monospace);
58
+
59
+ display: inline-block;
60
+ max-width: 100%;
61
+ white-space: pre-wrap;
62
+ word-break: break-word;
63
+ overflow-wrap: anywhere;
64
+ vertical-align: middle;
59
65
  }
@@ -167,7 +167,6 @@ export class TimePicker extends BaseInput {
167
167
  slot="field-end"
168
168
  color="secondary"
169
169
  variant="text"
170
- name="calendar_today"
171
170
  ?disabled=${this.disabled}
172
171
  @click=${() => {
173
172
  setTimeout(() => {
@@ -176,6 +175,7 @@ export class TimePicker extends BaseInput {
176
175
  });
177
176
  }}
178
177
  >
178
+ <wc-icon name="calendar_today"></wc-icon>
179
179
  </wc-icon-button>
180
180
  </wc-field>
181
181
  `;
@@ -33,10 +33,10 @@ import colorStyles from './toolbar-colors.scss';
33
33
  * ```html
34
34
  * <!-- Docked toolbar -->
35
35
  * <wc-toolbar>
36
- * <wc-icon-button variant="text" name="home"></wc-icon-button>
37
- * <wc-icon-button variant="tonal" name="search"></wc-icon-button>
38
- * <wc-icon-button variant="text" name="favorite"></wc-icon-button>
39
- * <wc-icon-button variant="text" name="account_circle"></wc-icon-button>
36
+ * <wc-icon-button variant="text"><wc-icon name="home"></wc-icon></wc-icon-button>
37
+ * <wc-icon-button variant="tonal"><wc-icon name="search"></wc-icon></wc-icon-button>
38
+ * <wc-icon-button variant="text"><wc-icon name="favorite"></wc-icon></wc-icon-button>
39
+ * <wc-icon-button variant="text"><wc-icon name="account_circle"></wc-icon></wc-icon-button>
40
40
  * </wc-toolbar>
41
41
  * ```
42
42
  *
@@ -44,9 +44,9 @@ import colorStyles from './toolbar-colors.scss';
44
44
  * ```html
45
45
  * <!-- Floating horizontal toolbar -->
46
46
  * <wc-toolbar variant="floating" orientation="horizontal">
47
- * <wc-icon-button variant="tonal" name="home"></wc-icon-button>
48
- * <wc-icon-button variant="text" name="search"></wc-icon-button>
49
- * <wc-icon-button variant="text" name="favorite"></wc-icon-button>
47
+ * <wc-icon-button variant="tonal"><wc-icon name="home"></wc-icon></wc-icon-button>
48
+ * <wc-icon-button variant="text"><wc-icon name="search"></wc-icon></wc-icon-button>
49
+ * <wc-icon-button variant="text"><wc-icon name="favorite"></wc-icon></wc-icon-button>
50
50
  * </wc-toolbar>
51
51
  * ```
52
52
  *
@@ -54,9 +54,9 @@ import colorStyles from './toolbar-colors.scss';
54
54
  * ```html
55
55
  * <!-- Floating vertical toolbar -->
56
56
  * <wc-toolbar variant="floating" orientation="vertical">
57
- * <wc-icon-button variant="tonal" name="home"></wc-icon-button>
58
- * <wc-icon-button variant="text" name="search"></wc-icon-button>
59
- * <wc-icon-button variant="text" name="favorite"></wc-icon-button>
57
+ * <wc-icon-button variant="tonal"><wc-icon name="home"></wc-icon></wc-icon-button>
58
+ * <wc-icon-button variant="text"><wc-icon name="search"></wc-icon></wc-icon-button>
59
+ * <wc-icon-button variant="text"><wc-icon name="favorite"></wc-icon></wc-icon-button>
60
60
  * </wc-toolbar>
61
61
  * ```
62
62
  * @tags display navigation
@@ -133,6 +133,30 @@ export class Tooltip extends LitElement {
133
133
  this._target = null;
134
134
  }
135
135
 
136
+ _focusTarget?: HTMLElement;
137
+
138
+ set forElement(value: HTMLElement | null) {
139
+ if (value) {
140
+ this._focusTarget = value;
141
+ } else {
142
+ this._focusTarget = undefined;
143
+ }
144
+ }
145
+
146
+ __getFocusTarget(): HTMLElement | null {
147
+
148
+ if (this._focusTarget) {
149
+ return this._focusTarget;
150
+ }
151
+
152
+ const focusTarget = document.getElementById(this.for);
153
+ if (focusTarget) {
154
+ return focusTarget
155
+ }
156
+
157
+ return this.parentElement;
158
+ }
159
+
136
160
  private attachListeners() {
137
161
  this.detachListeners(); // Cleanup old target if it exists
138
162
 
@@ -0,0 +1 @@
1
+ export { UrlField } from './url-field.js';
@@ -0,0 +1,50 @@
1
+ @use '../../scss/mixin';
2
+
3
+ @include mixin.base-styles;
4
+
5
+ :host {
6
+ display: block;
7
+ width: 100%;
8
+ }
9
+
10
+ .url-input {
11
+ flex: 1;
12
+ width: 100%;
13
+ border: none;
14
+ outline: none;
15
+ margin: 0;
16
+ padding: 0;
17
+ background: none;
18
+ cursor: inherit;
19
+ font: inherit;
20
+ color: inherit;
21
+ }
22
+
23
+ .url-display {
24
+ flex: 1;
25
+ display: flex;
26
+ align-items: center;
27
+ overflow: hidden;
28
+ }
29
+
30
+ .url-link {
31
+ flex: 1;
32
+ overflow: hidden;
33
+ text-overflow: ellipsis;
34
+ white-space: nowrap;
35
+ color: var(--color-primary);
36
+ text-decoration: none;
37
+
38
+ &:hover {
39
+ text-decoration: underline;
40
+ }
41
+ }
42
+
43
+ .url-placeholder {
44
+ color: var(--color-on-surface-variant);
45
+ opacity: 0.6;
46
+ }
47
+
48
+ .edit-button {
49
+ --button-container-shape: var(--shape-corner-full);
50
+ }
@@ -0,0 +1,239 @@
1
+ import { html, nothing } from 'lit';
2
+ import { property, query, state } from 'lit/decorators.js';
3
+
4
+ import { redispatchEvent } from '@/__utils/dispatch-event-utils.js';
5
+
6
+ import BaseInput from '../input/BaseInput.js';
7
+ import styles from './url-field.scss';
8
+
9
+ /**
10
+ * @label URL Field
11
+ * @tag wc-url-field
12
+ * @rawTag url-field
13
+ *
14
+ * @summary A field for entering and displaying URLs with validation.
15
+ * @overview
16
+ * <p>URL Field wraps an input with URL validation, showing a clickable link preview when not in edit mode.</p>
17
+ *
18
+ * @example
19
+ * ```html
20
+ * <wc-url-field label="Website" value="https://example.com"></wc-url-field>
21
+ * ```
22
+ * @tags form
23
+ */
24
+ export class UrlField extends BaseInput {
25
+ static styles = [styles];
26
+
27
+ @property({ type: String })
28
+ value: string = '';
29
+
30
+ @property({ type: String })
31
+ name: string = '';
32
+
33
+ @property({ type: String })
34
+ placeholder: string = '';
35
+
36
+ @property({ type: String })
37
+ label: string = '';
38
+
39
+ @property({ type: Boolean, reflect: true })
40
+ editing: boolean = false;
41
+
42
+ @property({ type: Number })
43
+ debounce: number = 300;
44
+
45
+ @property({ type: String, reflect: true })
46
+ size: 'sm' | 'md' | 'lg' = 'md';
47
+
48
+ @property({ type: String })
49
+ variant: 'filled' | 'outlined' | 'default' = 'default';
50
+
51
+ @property({ type: String, attribute: 'helper-text' })
52
+ helperText: string = '';
53
+
54
+ @property({ type: Boolean })
55
+ error: boolean = false;
56
+
57
+ @property({ type: String, attribute: 'error-text' })
58
+ errorText: string = '';
59
+
60
+ @property({ type: Boolean })
61
+ warning: boolean = false;
62
+
63
+ @property({ type: String, attribute: 'warning-text' })
64
+ warningText: string = '';
65
+
66
+ @state()
67
+ private focused: boolean = false;
68
+
69
+ @state()
70
+ private isValid: boolean = true;
71
+
72
+ @query('.url-input')
73
+ private inputElement?: HTMLInputElement;
74
+
75
+ private debounceTimer?: ReturnType<typeof setTimeout>;
76
+
77
+ override disconnectedCallback(): void {
78
+ super.disconnectedCallback();
79
+ if (this.debounceTimer) {
80
+ clearTimeout(this.debounceTimer);
81
+ }
82
+ }
83
+
84
+ override focus() {
85
+ if (!this.editing && !this.disabled && !this.readonly) {
86
+ this.startEditing();
87
+ return;
88
+ }
89
+ this.inputElement?.focus();
90
+ }
91
+
92
+ override blur() {
93
+ this.inputElement?.blur();
94
+ }
95
+
96
+ private startEditing() {
97
+ if (this.disabled || this.readonly) return;
98
+ this.editing = true;
99
+ setTimeout(() => this.inputElement?.focus(), 80);
100
+ }
101
+
102
+ private closeEditing() {
103
+ this.isValid = this.validateUrl(this.value);
104
+ this.dispatchEvent(
105
+ new CustomEvent('input-invalid', {
106
+ detail: !this.isValid,
107
+ bubbles: true,
108
+ composed: true,
109
+ }),
110
+ );
111
+
112
+ if (this.isValid) {
113
+ this.editing = false;
114
+ }
115
+ }
116
+
117
+ private validateUrl(url: string): boolean {
118
+ if (!url) return true;
119
+
120
+ try {
121
+ new URL(url);
122
+ return true;
123
+ } catch {
124
+ return false;
125
+ }
126
+ }
127
+
128
+ private handleInput(event: InputEvent) {
129
+ this.value = (event.target as HTMLInputElement).value;
130
+ this.isValid = true;
131
+
132
+ if (this.debounceTimer) {
133
+ clearTimeout(this.debounceTimer);
134
+ }
135
+
136
+ this.debounceTimer = setTimeout(() => {
137
+ this.dispatchEvent(
138
+ new CustomEvent('value-change', {
139
+ detail: this.value,
140
+ bubbles: true,
141
+ composed: true,
142
+ }),
143
+ );
144
+ }, this.debounce);
145
+ }
146
+
147
+ private handleFocusChange() {
148
+ this.focused = this.inputElement?.matches(':focus') ?? false;
149
+ }
150
+
151
+ private handleBlur() {
152
+ this.focused = false;
153
+ this.closeEditing();
154
+ }
155
+
156
+ private handleChange(event: Event) {
157
+ redispatchEvent(this, event);
158
+ }
159
+
160
+ private renderDisplayValue() {
161
+ if (!this.value) {
162
+ return html`<span class="url-placeholder">${this.placeholder}</span>`;
163
+ }
164
+
165
+ return html`
166
+ <a
167
+ class="url-link"
168
+ href=${this.value}
169
+ target="_blank"
170
+ rel="noopener noreferrer"
171
+ >
172
+ ${this.value}
173
+ </a>
174
+ `;
175
+ }
176
+
177
+ render() {
178
+ const hasValue = !!this.value;
179
+ const showInvalidState = this.error || !this.isValid;
180
+ const resolvedErrorText = !this.isValid
181
+ ? 'Please enter a valid URL'
182
+ : this.errorText;
183
+
184
+ return html`
185
+ <wc-field
186
+ label=${this.label}
187
+ ?required=${this.required}
188
+ ?disabled=${this.disabled}
189
+ ?readonly=${this.readonly}
190
+ ?skeleton=${this.skeleton}
191
+ helper-text=${this.helperText}
192
+ ?error=${showInvalidState}
193
+ error-text=${resolvedErrorText}
194
+ ?warning=${this.warning}
195
+ warning-text=${this.warningText}
196
+ variant=${this.variant}
197
+ ?populated=${hasValue || this.editing}
198
+ ?focused=${this.focused}
199
+ .host=${this}
200
+ class="url-field-wrapper"
201
+ >
202
+ ${this.editing
203
+ ? html`
204
+ <input
205
+ class="url-input"
206
+ name=${this.name}
207
+ type="url"
208
+ placeholder=${this.placeholder}
209
+ .value=${this.value}
210
+ ?readonly=${this.readonly}
211
+ ?required=${this.required}
212
+ ?disabled=${this.disabled}
213
+ @input=${this.handleInput}
214
+ @change=${this.handleChange}
215
+ @focus=${this.handleFocusChange}
216
+ @blur=${this.handleBlur}
217
+ />
218
+ `
219
+ : html`<div class="url-display">${this.renderDisplayValue()}</div>`}
220
+
221
+ ${!this.editing && !this.disabled && !this.readonly
222
+ ? html`
223
+ <wc-icon-button
224
+ class="edit-button"
225
+ slot="field-end"
226
+ variant="text"
227
+ @click=${(event: MouseEvent) => {
228
+ event.stopPropagation();
229
+ this.startEditing();
230
+ }}
231
+ >
232
+ <wc-icon name="edit"></wc-icon>
233
+ </wc-icon-button>
234
+ `
235
+ : nothing}
236
+ </wc-field>
237
+ `;
238
+ }
239
+ }