@redvars/peacock 3.6.0 → 3.6.2

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 (105) hide show
  1. package/dist/assets/styles.css +1 -1
  2. package/dist/assets/styles.css.map +1 -1
  3. package/dist/assets/tokens.css +1 -1
  4. package/dist/assets/tokens.css.map +1 -1
  5. package/dist/{button-colors-Ccys3hvS.js → button-colors-AvGh22Zn.js} +18 -18
  6. package/dist/{button-colors-Ccys3hvS.js.map → button-colors-AvGh22Zn.js.map} +1 -1
  7. package/dist/button-group.js +2 -2
  8. package/dist/button.js +2 -3
  9. package/dist/button.js.map +1 -1
  10. package/dist/card.js +1 -1
  11. package/dist/card.js.map +1 -1
  12. package/dist/code-highlighter.js +34 -9
  13. package/dist/code-highlighter.js.map +1 -1
  14. package/dist/custom-elements-jsdocs.json +4973 -3553
  15. package/dist/custom-elements.json +7899 -6346
  16. package/dist/{flow-designer-node-XMe-jlKg.js → flow-designer-node-BWrPuxAR.js} +2 -2
  17. package/dist/flow-designer-node-BWrPuxAR.js.map +1 -0
  18. package/dist/flow-designer-node.js +1 -1
  19. package/dist/flow-designer.js +1402 -8
  20. package/dist/flow-designer.js.map +1 -1
  21. package/dist/html-editor.js +27245 -87
  22. package/dist/html-editor.js.map +1 -1
  23. package/dist/icon-CueRR7wx.js +260 -0
  24. package/dist/icon-CueRR7wx.js.map +1 -0
  25. package/dist/{icon-button-CK1ZuE-2.js → icon-button-ohxHhy4t.js} +2 -2
  26. package/dist/{icon-button-CK1ZuE-2.js.map → icon-button-ohxHhy4t.js.map} +1 -1
  27. package/dist/index.js +10 -9
  28. package/dist/index.js.map +1 -1
  29. package/dist/modal.js +12 -18
  30. package/dist/modal.js.map +1 -1
  31. package/dist/{navigation-rail-DyO0oAZU.js → navigation-rail-CD7IrqbN.js} +952 -279
  32. package/dist/navigation-rail-CD7IrqbN.js.map +1 -0
  33. package/dist/peacock-loader.js +39 -30
  34. package/dist/peacock-loader.js.map +1 -1
  35. package/dist/{popover-NC7b1lTq.js → popover-DUPmMVWS.js} +9 -4
  36. package/dist/{popover-NC7b1lTq.js.map → popover-DUPmMVWS.js.map} +1 -1
  37. package/dist/popover.js +1 -1
  38. package/dist/src/__controllers/floating-controller.d.ts +1 -0
  39. package/dist/src/avatar/avatar.d.ts +1 -1
  40. package/dist/src/breadcrumb/breadcrumb/breadcrumb.d.ts +0 -1
  41. package/dist/src/chip/chip/chip.d.ts +14 -11
  42. package/dist/src/chip/chip-set/chip-set.d.ts +20 -0
  43. package/dist/src/chip/chip-set/index.d.ts +1 -0
  44. package/dist/src/code-highlighter/code-highlighter.d.ts +4 -0
  45. package/dist/src/html-editor/html-editor.d.ts +44 -11
  46. package/dist/src/index.d.ts +3 -0
  47. package/dist/src/list/index.d.ts +2 -0
  48. package/dist/src/list/list-item.d.ts +35 -0
  49. package/dist/src/list/list.d.ts +28 -0
  50. package/dist/src/menu/menu/menu.d.ts +1 -0
  51. package/dist/src/modal/modal.d.ts +2 -8
  52. package/dist/src/navigation-rail/navigation-rail.d.ts +3 -7
  53. package/dist/src/number-field/number-field.d.ts +2 -2
  54. package/dist/src/svg/index.d.ts +1 -0
  55. package/dist/src/svg/svg.d.ts +38 -0
  56. package/dist/src/toolbar/toolbar.d.ts +3 -3
  57. package/dist/test/chip.test.d.ts +1 -0
  58. package/dist/toolbar.js +3 -3
  59. package/dist/toolbar.js.map +1 -1
  60. package/dist/tsconfig.tsbuildinfo +1 -1
  61. package/package.json +7 -1
  62. package/readme.md +3 -3
  63. package/scss/styles.scss +3 -3
  64. package/scss/tokens.css +1 -1
  65. package/src/__controllers/floating-controller.ts +9 -3
  66. package/src/avatar/avatar.scss +4 -4
  67. package/src/avatar/avatar.ts +1 -1
  68. package/src/breadcrumb/breadcrumb/breadcrumb.ts +0 -1
  69. package/src/button/button/button.scss +17 -17
  70. package/src/button/button/button.ts +1 -2
  71. package/src/card/card.ts +1 -1
  72. package/src/chip/chip/chip.scss +119 -45
  73. package/src/chip/chip/chip.ts +97 -38
  74. package/src/chip/chip-set/chip-set.scss +13 -0
  75. package/src/chip/chip-set/chip-set.ts +25 -0
  76. package/src/chip/chip-set/index.ts +1 -0
  77. package/src/code-highlighter/code-highlighter.ts +33 -6
  78. package/src/field/field.scss +1 -1
  79. package/src/flow-designer/flow-designer-node.ts +1 -1
  80. package/src/html-editor/html-editor.scss +44 -2
  81. package/src/html-editor/html-editor.ts +309 -94
  82. package/src/index.ts +3 -1
  83. package/src/list/index.ts +2 -0
  84. package/src/list/list-item.scss +111 -0
  85. package/src/list/list-item.ts +175 -0
  86. package/src/list/list.scss +24 -0
  87. package/src/list/list.ts +51 -0
  88. package/src/menu/menu/menu.ts +11 -0
  89. package/src/modal/modal.scss +10 -10
  90. package/src/modal/modal.ts +2 -8
  91. package/src/navigation-rail/navigation-rail-item.scss +7 -38
  92. package/src/navigation-rail/navigation-rail-item.ts +1 -2
  93. package/src/navigation-rail/navigation-rail.scss +17 -21
  94. package/src/navigation-rail/navigation-rail.ts +6 -9
  95. package/src/number-field/number-field.ts +2 -2
  96. package/src/peacock-loader.ts +36 -22
  97. package/src/svg/index.ts +1 -0
  98. package/src/svg/svg.scss +91 -0
  99. package/src/svg/svg.ts +160 -0
  100. package/src/toolbar/toolbar.ts +3 -3
  101. package/dist/flow-designer-dZnLJOQT.js +0 -1656
  102. package/dist/flow-designer-dZnLJOQT.js.map +0 -1
  103. package/dist/flow-designer-node-XMe-jlKg.js.map +0 -1
  104. package/dist/navigation-rail-DyO0oAZU.js.map +0 -1
  105. package/src/chip/chip/chip-colors.scss +0 -31
@@ -1,6 +1,12 @@
1
1
  import { html, nothing } from 'lit';
2
2
  import { property, query, state } from 'lit/decorators.js';
3
3
  import { classMap } from 'lit/directives/class-map.js';
4
+ import { Editor, mergeAttributes } from '@tiptap/core';
5
+ import StarterKit from '@tiptap/starter-kit';
6
+ import Underline from '@tiptap/extension-underline';
7
+ import Placeholder from '@tiptap/extension-placeholder';
8
+ import Mention from '@tiptap/extension-mention';
9
+ import { html as beautifyHtml } from 'js-beautify';
4
10
 
5
11
  import IndividualComponent from '@/IndividualComponent.js';
6
12
  import BaseInput from '../input/BaseInput.js';
@@ -13,24 +19,32 @@ import styles from './html-editor.scss';
13
19
  * @tag wc-html-editor
14
20
  * @rawTag html-editor
15
21
  *
16
- * @summary A WYSIWYG HTML editor component with a Material 3 styled toolbar.
22
+ * @summary A Tiptap-powered HTML editor with visual and source editing modes.
17
23
  * @overview
18
- * <p>The HTML Editor provides a rich-text editing experience using the browser's built-in
19
- * <code>contenteditable</code> API. It wraps the editable area in a Material 3 styled
20
- * <code>wc-field</code> and exposes a toolbar with common formatting actions.</p>
24
+ * <p>The HTML Editor provides a rich-text editing experience built on Tiptap.
25
+ * It wraps the editable area in a Material 3 styled <code>wc-field</code>,
26
+ * exposes common formatting actions, and includes a segmented switch between
27
+ * <strong>Visual</strong> and <strong>HTML</strong> source modes.</p>
21
28
  *
22
29
  * <p>Get and set the HTML content via the <code>value</code> property. The component
23
- * dispatches a <code>change</code> event whenever the content is modified.</p>
30
+ * dispatches a <code>change</code> event whenever the content is modified.
31
+ * Mention suggestions are supported through the <code>mentions</code> property,
32
+ * with optional externally managed lookup via the <code>search</code> event.</p>
24
33
  *
25
34
  * @cssprop --html-editor-min-height - Minimum height of the editable area. Defaults to 8rem.
26
35
  * @cssprop --html-editor-toolbar-background - Background color of the toolbar.
27
36
  * @cssprop --html-editor-toolbar-border-color - Border color between toolbar and editing area.
28
37
  *
29
38
  * @fires {Event} change - Fired whenever the editable content changes.
39
+ * @fires {CustomEvent} search - Fired in managed mention mode with { query, callback } detail.
30
40
  *
31
41
  * @example
32
42
  * ```html
33
- * <wc-html-editor label="Description" value="<p>Hello <strong>world</strong></p>"></wc-html-editor>
43
+ * <wc-html-editor
44
+ * label="Description"
45
+ * value="<p>Hello <strong>world</strong></p>"
46
+ * .mentions="[{ label: 'Alex', value: 'alex' }]"
47
+ * ></wc-html-editor>
34
48
  * ```
35
49
  * @tags input editor
36
50
  */
@@ -38,6 +52,10 @@ import styles from './html-editor.scss';
38
52
  export class HtmlEditor extends BaseInput {
39
53
  static styles = [styles];
40
54
 
55
+ private _editor?: Editor;
56
+
57
+ private _changeTimeout?: number;
58
+
41
59
  /** Current HTML value of the editor. */
42
60
  @property({ type: String })
43
61
  value = '';
@@ -66,64 +84,230 @@ export class HtmlEditor extends BaseInput {
66
84
  @property({ type: String, attribute: 'error-text' })
67
85
  errorText = '';
68
86
 
87
+ /** Whether toolbar controls should be displayed in visual mode. */
88
+ @property({ type: Boolean, attribute: 'show-toolbar' })
89
+ showToolbar = true;
90
+
91
+ /** Mention suggestions used by the mention extension. */
92
+ @property({ type: Array })
93
+ mentions: Array<{ label: string; value: string }> = [];
94
+
95
+ /** Mention filtering mode. */
96
+ @property({ type: String, attribute: 'mentions-search' })
97
+ mentionsSearch: 'contains' | 'managed' = 'contains';
98
+
99
+ /** Character that triggers mention suggestions. */
100
+ @property({ type: String, attribute: 'suggestion-character' })
101
+ suggestionCharacter = '@';
102
+
103
+ /** Whether to include the suggestion character in rendered mention text. */
104
+ @property({ type: Boolean, attribute: 'show-suggestion-character' })
105
+ showSuggestionCharacter = true;
106
+
107
+ /** Debounce in milliseconds for dispatching `change`. */
108
+ @property({ type: Number })
109
+ debounce = 250;
110
+
69
111
  @state() private _focused = false;
70
112
 
71
- @query('.html-editor-content')
113
+ @state() private _mode: 'visual' | 'html' = 'visual';
114
+
115
+ @query('.tiptap-root')
72
116
  private _editorEl!: HTMLDivElement;
73
117
 
74
118
  // ─── Lifecycle ─────────────────────────────────────────────────────────────
75
119
 
76
120
  protected firstUpdated() {
77
- if (this.value && this._editorEl) {
78
- this._editorEl.innerHTML = this.value;
121
+ this._initializeEditor();
122
+ }
123
+
124
+ disconnectedCallback() {
125
+ super.disconnectedCallback();
126
+ if (this._changeTimeout) {
127
+ window.clearTimeout(this._changeTimeout);
128
+ this._changeTimeout = undefined;
79
129
  }
130
+ this._destroyEditor();
80
131
  }
81
132
 
82
133
  protected updated(changed: Map<string, unknown>) {
83
- if (changed.has('value') && this._editorEl) {
84
- if (this._editorEl.innerHTML !== this.value) {
85
- this._editorEl.innerHTML = this.value ?? '';
134
+ if (changed.has('value') && this._editor) {
135
+ const editorHtml = HtmlEditor._normalizeHtml(this._editor.getHTML());
136
+ const nextHtml = HtmlEditor._normalizeHtml(this.value);
137
+ if (editorHtml !== nextHtml) {
138
+ this._editor.commands.setContent(this.value ?? '', false);
86
139
  }
87
140
  }
88
- if (changed.has('disabled') || changed.has('readonly')) {
89
- if (this._editorEl) {
90
- this._editorEl.contentEditable =
91
- this.disabled || this.readonly ? 'false' : 'true';
141
+
142
+ if ((changed.has('disabled') || changed.has('readonly')) && this._editor) {
143
+ this._editor.setEditable(!(this.disabled || this.readonly));
144
+ }
145
+
146
+ if (changed.has('placeholder') && this._editor) {
147
+ this._destroyEditor();
148
+ this._initializeEditor();
149
+ }
150
+
151
+ if (changed.has('mentions') && this._editor) {
152
+ const oldMentions = changed.get('mentions') as Array<{
153
+ label: string;
154
+ value: string;
155
+ }>;
156
+ if (oldMentions !== this.mentions) {
157
+ this._destroyEditor();
158
+ this._initializeEditor();
92
159
  }
93
160
  }
94
161
  }
95
162
 
96
163
  // ─── Private helpers ───────────────────────────────────────────────────────
97
164
 
98
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
99
- private _execCmd(command: string, value?: string) {
100
- if (this.disabled || this.readonly) return;
101
- this._editorEl.focus();
102
- // execCommand is deprecated but remains broadly supported for rich-text editing
103
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
104
- (document as any).execCommand(command, false, value ?? null);
165
+ private static _normalizeHtml(value: string) {
166
+ return beautifyHtml(value ?? '', {
167
+ wrap_line_length: 120,
168
+ });
105
169
  }
106
170
 
107
- private _handleInput() {
108
- this.value = this._editorEl.innerHTML;
109
- redispatchEvent(
110
- this,
111
- new Event('change', { bubbles: true, composed: true }),
112
- );
171
+ private _destroyEditor() {
172
+ if (!this._editor) return;
173
+ this._editorEl?.removeEventListener('click', this._focusEditorOnContainerClick);
174
+ this._editor.destroy();
175
+ this._editor = undefined;
113
176
  }
114
177
 
115
- private _handleFocus() {
116
- this._focused = true;
178
+ private _initializeEditor() {
179
+ if (!this._editorEl || this._editor) return;
180
+
181
+ this._editor = new Editor({
182
+ element: this._editorEl,
183
+ extensions: [
184
+ StarterKit,
185
+ Underline,
186
+ Placeholder.configure({
187
+ placeholder: this.placeholder,
188
+ }),
189
+ Mention.configure({
190
+ HTMLAttributes: {
191
+ class: 'mention',
192
+ },
193
+ renderHTML: ({ options, node }) => {
194
+ const item = this._getMentionItem(node.attrs.id);
195
+ return [
196
+ 'a',
197
+ mergeAttributes({ contenteditable: false }, options.HTMLAttributes),
198
+ `${this.showSuggestionCharacter ? options.suggestion.char : ''}${
199
+ item ? item.label : node.attrs.id
200
+ }`,
201
+ ];
202
+ },
203
+ suggestion: {
204
+ allowSpaces: true,
205
+ char: this.suggestionCharacter,
206
+ items: async ({ query: mentionQuery }) => {
207
+ if (this.mentionsSearch === 'managed') {
208
+ return this._requestManagedMentions(mentionQuery);
209
+ }
210
+
211
+ return this.mentions
212
+ .filter(item =>
213
+ item.label.toLowerCase().startsWith(mentionQuery.toLowerCase()),
214
+ )
215
+ .map(item => item.value)
216
+ .slice(0, 5);
217
+ },
218
+ },
219
+ }),
220
+ ],
221
+ content: this.value,
222
+ editable: !(this.disabled || this.readonly),
223
+ onFocus: () => {
224
+ this._focused = true;
225
+ },
226
+ onBlur: () => {
227
+ this._focused = false;
228
+ },
229
+ onUpdate: () => {
230
+ if (!this._editor) return;
231
+
232
+ const nextHtml = HtmlEditor._normalizeHtml(this._editor.getHTML());
233
+ if (nextHtml !== this.value) {
234
+ this.value = nextHtml;
235
+ }
236
+
237
+ this._dispatchDebouncedChange();
238
+ },
239
+ });
240
+
241
+ this._editorEl.addEventListener('click', this._focusEditorOnContainerClick);
117
242
  }
118
243
 
119
- private _handleBlur() {
120
- this._focused = false;
244
+ private _focusEditorOnContainerClick = (event: Event) => {
245
+ if (!this._editor) return;
246
+ if (event.target === this._editorEl) {
247
+ this._editor.commands.focus('end');
248
+ }
249
+ };
250
+
251
+ private _dispatchDebouncedChange() {
252
+ if (this._changeTimeout) {
253
+ window.clearTimeout(this._changeTimeout);
254
+ }
255
+
256
+ this._changeTimeout = window.setTimeout(() => {
257
+ redispatchEvent(this, new Event('change', { bubbles: true, composed: true }));
258
+ }, this.debounce);
259
+ }
260
+
261
+ private _requestManagedMentions(mentionQuery: string): Promise<string[]> {
262
+ return new Promise(resolve => {
263
+ this.dispatchEvent(
264
+ new CustomEvent('search', {
265
+ detail: {
266
+ query: mentionQuery,
267
+ callback: (mentions: Array<{ label: string; value: string }>) => {
268
+ this.mentions = mentions;
269
+ resolve(this.mentions.map(item => item.value));
270
+ },
271
+ },
272
+ bubbles: true,
273
+ composed: true,
274
+ }),
275
+ );
276
+ });
277
+ }
278
+
279
+ private _getMentionItem(value: string) {
280
+ return this.mentions.find(item => item.value === value);
281
+ }
282
+
283
+ private _execCommand(command: () => void) {
284
+ if (this.disabled || this.readonly || !this._editor) return;
285
+ command();
286
+ this._editor.commands.focus();
287
+ }
288
+
289
+ private _switchMode(event: CustomEvent<{ value: string }>) {
290
+ event.stopPropagation();
291
+ const nextMode = event.detail?.value === 'html' ? 'html' : 'visual';
292
+
293
+ if (nextMode === this._mode) return;
294
+
295
+ if (nextMode === 'html' && this._editor) {
296
+ this.value = HtmlEditor._normalizeHtml(this._editor.getHTML());
297
+ }
298
+
299
+ this._mode = nextMode;
121
300
  }
122
301
 
123
- private _insertLink() {
124
- // eslint-disable-next-line no-alert
125
- const url = window.prompt('Enter URL:', 'https://');
126
- if (url) this._execCmd('createLink', url);
302
+ private _handleSourceChange(event: Event) {
303
+ event.stopPropagation();
304
+ const target = event.currentTarget as { value?: string };
305
+ const nextValue = target.value ?? '';
306
+
307
+ if (nextValue === this.value) return;
308
+
309
+ this.value = nextValue;
310
+ this._dispatchDebouncedChange();
127
311
  }
128
312
 
129
313
  // ─── Toolbar button ────────────────────────────────────────────────────────
@@ -131,19 +315,22 @@ export class HtmlEditor extends BaseInput {
131
315
  private _toolbarButton(
132
316
  icon: string,
133
317
  title: string,
134
- command: string,
135
- value?: string,
318
+ action: () => void,
319
+ active = false,
136
320
  ) {
137
321
  return html`
138
322
  <button
139
- class="toolbar-btn"
323
+ class=${classMap({
324
+ 'toolbar-btn': true,
325
+ active,
326
+ })}
140
327
  title=${title}
141
328
  aria-label=${title}
142
329
  ?disabled=${this.disabled || this.readonly}
143
330
  @mousedown=${(e: Event) => e.preventDefault()}
144
331
  @click=${(e: Event) => {
145
332
  e.preventDefault();
146
- this._execCmd(command, value);
333
+ this._execCommand(action);
147
334
  }}
148
335
  >
149
336
  <wc-icon name=${icon} size="sm"></wc-icon>
@@ -154,33 +341,52 @@ export class HtmlEditor extends BaseInput {
154
341
  // ─── Toolbar ───────────────────────────────────────────────────────────────
155
342
 
156
343
  private _renderToolbar() {
344
+ if (!this._editor || !this.showToolbar || this._mode !== 'visual') {
345
+ return nothing;
346
+ }
347
+
157
348
  return html`
158
349
  <div
159
350
  class="html-editor-toolbar"
160
351
  role="toolbar"
161
352
  aria-label="Formatting toolbar"
162
353
  >
163
- ${this._toolbarButton('format_bold', 'Bold', 'bold')}
164
- ${this._toolbarButton('format_italic', 'Italic', 'italic')}
165
- ${this._toolbarButton('format_underlined', 'Underline', 'underline')}
166
354
  ${this._toolbarButton(
167
- 'format_strikethrough',
168
- 'Strikethrough',
169
- 'strikeThrough',
355
+ 'undo',
356
+ 'Undo',
357
+ () => this._editor?.commands.undo(),
358
+ )}
359
+ ${this._toolbarButton(
360
+ 'redo',
361
+ 'Redo',
362
+ () => this._editor?.commands.redo(),
170
363
  )}
171
364
 
172
365
  <span class="toolbar-divider"></span>
173
366
 
174
- ${this._toolbarButton('format_align_left', 'Align left', 'justifyLeft')}
175
367
  ${this._toolbarButton(
176
- 'format_align_center',
177
- 'Align center',
178
- 'justifyCenter',
368
+ 'format_bold',
369
+ 'Bold',
370
+ () => this._editor?.chain().focus().toggleBold().run(),
371
+ this._editor.isActive('bold'),
179
372
  )}
180
373
  ${this._toolbarButton(
181
- 'format_align_right',
182
- 'Align right',
183
- 'justifyRight',
374
+ 'format_italic',
375
+ 'Italic',
376
+ () => this._editor?.chain().focus().toggleItalic().run(),
377
+ this._editor.isActive('italic'),
378
+ )}
379
+ ${this._toolbarButton(
380
+ 'format_underlined',
381
+ 'Underline',
382
+ () => this._editor?.chain().focus().toggleUnderline().run(),
383
+ this._editor.isActive('underline'),
384
+ )}
385
+ ${this._toolbarButton(
386
+ 'format_strikethrough',
387
+ 'Strikethrough',
388
+ () => this._editor?.chain().focus().toggleStrike().run(),
389
+ this._editor.isActive('strike'),
184
390
  )}
185
391
 
186
392
  <span class="toolbar-divider"></span>
@@ -188,44 +394,29 @@ export class HtmlEditor extends BaseInput {
188
394
  ${this._toolbarButton(
189
395
  'format_list_bulleted',
190
396
  'Unordered list',
191
- 'insertUnorderedList',
397
+ () => this._editor?.chain().focus().toggleBulletList().run(),
398
+ this._editor.isActive('bulletList'),
192
399
  )}
193
400
  ${this._toolbarButton(
194
401
  'format_list_numbered',
195
402
  'Ordered list',
196
- 'insertOrderedList',
403
+ () => this._editor?.chain().focus().toggleOrderedList().run(),
404
+ this._editor.isActive('orderedList'),
197
405
  )}
198
-
199
- <span class="toolbar-divider"></span>
200
-
201
- ${this._toolbarButton(
202
- 'format_indent_increase',
203
- 'Indent',
204
- 'indent',
205
- )}
206
- ${this._toolbarButton('format_indent_decrease', 'Outdent', 'outdent')}
207
-
208
- <span class="toolbar-divider"></span>
209
-
210
- <button
211
- class="toolbar-btn"
212
- title="Insert link"
213
- aria-label="Insert link"
214
- ?disabled=${this.disabled || this.readonly}
215
- @mousedown=${(e: Event) => e.preventDefault()}
216
- @click=${() => this._insertLink()}
217
- >
218
- <wc-icon name="link" size="sm"></wc-icon>
219
- </button>
220
-
221
- <span class="toolbar-divider"></span>
222
-
223
- ${this._toolbarButton('undo', 'Undo', 'undo')}
224
- ${this._toolbarButton('redo', 'Redo', 'redo')}
225
406
  </div>
226
407
  `;
227
408
  }
228
409
 
410
+ private _renderReadonlyTag() {
411
+ if (this.disabled) {
412
+ return html`<wc-tag class="read-only-tag" color="red">Disabled</wc-tag>`;
413
+ }
414
+ if (this.readonly) {
415
+ return html`<wc-tag class="read-only-tag" color="red">Read Only</wc-tag>`;
416
+ }
417
+ return nothing;
418
+ }
419
+
229
420
  // ─── Render ────────────────────────────────────────────────────────────────
230
421
 
231
422
  render() {
@@ -251,25 +442,49 @@ export class HtmlEditor extends BaseInput {
251
442
  readonly: this.readonly,
252
443
  })}
253
444
  >
445
+ <div class="mode-switcher">
446
+ <wc-segmented-button-group @change=${this._switchMode}>
447
+ <wc-segmented-button
448
+ value="visual"
449
+ ?selected=${this._mode === 'visual'}
450
+ ?disabled=${this.disabled}
451
+ >
452
+ Visual
453
+ </wc-segmented-button>
454
+ <wc-segmented-button
455
+ value="html"
456
+ ?selected=${this._mode === 'html'}
457
+ ?disabled=${this.disabled}
458
+ >
459
+ HTML
460
+ </wc-segmented-button>
461
+ </wc-segmented-button-group>
462
+ </div>
463
+
254
464
  ${this._renderToolbar()}
255
465
 
256
466
  <div
257
467
  class=${classMap({
258
468
  'html-editor-content': true,
259
469
  'is-empty': isEmpty,
470
+ hidden: this._mode !== 'visual',
260
471
  })}
261
- contenteditable=${this.disabled || this.readonly ? 'false' : 'true'}
262
472
  data-placeholder=${this.placeholder}
263
- @input=${this._handleInput}
264
- @focus=${this._handleFocus}
265
- @blur=${this._handleBlur}
266
- ></div>
267
-
268
- ${this.disabled
269
- ? html`<wc-tag class="read-only-tag" color="red">Disabled</wc-tag>`
270
- : this.readonly
271
- ? html`<wc-tag class="read-only-tag" color="red">Read Only</wc-tag>`
272
- : nothing}
473
+ >
474
+ <div class="tiptap-root"></div>
475
+ </div>
476
+
477
+ <div class=${classMap({ 'html-source': true, hidden: this._mode !== 'html' })}>
478
+ <wc-code-editor
479
+ language="html"
480
+ .value=${this.value}
481
+ ?readonly=${this.readonly}
482
+ ?disabled=${this.disabled}
483
+ @change=${this._handleSourceChange}
484
+ ></wc-code-editor>
485
+ </div>
486
+
487
+ ${this._renderReadonlyTag()}
273
488
  </wc-field>
274
489
  `;
275
490
  }
package/src/index.ts CHANGED
@@ -16,6 +16,7 @@ export { Accordion } from './accordion/index.js';
16
16
  export { Link } from './link/index.js';
17
17
  export { Tag } from './chip/tag/index.js';
18
18
  export { Chip } from './chip/chip/index.js';
19
+ export { ChipSet } from './chip/chip-set/index.js';
19
20
  export { LinearProgress } from './progress/linear-progress/index.js';
20
21
  export { CircularProgress } from './progress/circular-progress/index.js';
21
22
  export { Skeleton } from './skeleton/index.js';
@@ -37,11 +38,13 @@ export { Tooltip } from './popover/index.js';
37
38
  export { Popover, PopoverContent } from './popover/index.js';
38
39
  export { Breadcrumb, BreadcrumbItem } from './breadcrumb/index.js';
39
40
  export { Menu, MenuItem, SubMenu } from './menu/index.js';
41
+ export { List, ListItem } from './list/index.js';
40
42
 
41
43
  export { CodeHighlighter } from './code-highlighter/index.js';
42
44
  export { CodeEditor } from './code-editor/index.js';
43
45
  export { HtmlEditor } from './html-editor/index.js';
44
46
  export { Image } from './image/index.js';
47
+ export { Svg } from './svg/index.js';
45
48
  export { Tab, TabGroup, TabPanel, Tabs } from './tabs/index.js';
46
49
  export { Slider } from './slider/index.js';
47
50
  export { ChartDoughnut } from './chart-doughnut/index.js';
@@ -84,4 +87,3 @@ export type {
84
87
  NodeTemplate,
85
88
  } from './flow-designer/index.js';
86
89
  export { ConditionBuilder, CbPredicate, CbCompoundExpression, CbExpression, CbDivider } from './condition-builder/index.js';
87
-
@@ -0,0 +1,2 @@
1
+ export { List } from './list.js';
2
+ export { ListItem } from './list-item.js';
@@ -0,0 +1,111 @@
1
+ @use "../../scss/mixin";
2
+
3
+ @include mixin.base-styles;
4
+
5
+ :host {
6
+ display: block;
7
+ padding-inline: var(--spacing-050);
8
+ }
9
+
10
+ .item-element {
11
+ position: relative;
12
+ min-height: 3.5rem;
13
+ width: 100%;
14
+ border: 0;
15
+ margin: 0;
16
+ padding: 0;
17
+ outline: 0;
18
+ background: transparent;
19
+ text-align: initial;
20
+ text-decoration: none;
21
+ cursor: pointer;
22
+
23
+ @include mixin.get-typography(body-large);
24
+
25
+ .list-item-content {
26
+ position: relative;
27
+ z-index: 1;
28
+ display: flex;
29
+ align-items: center;
30
+ gap: var(--spacing-200);
31
+ min-height: 3.5rem;
32
+ padding-inline: var(--spacing-200);
33
+ color: var(--_label-text-color);
34
+ opacity: var(--_label-text-opacity, 1);
35
+ }
36
+
37
+ .leading,
38
+ .trailing {
39
+ display: inline-flex;
40
+ align-items: center;
41
+ justify-content: center;
42
+ min-width: 1.5rem;
43
+ color: var(--_leading-trailing-color);
44
+ }
45
+
46
+ .trailing {
47
+ margin-inline-start: auto;
48
+ }
49
+
50
+ .content {
51
+ display: block;
52
+ flex: 1;
53
+ min-inline-size: 0;
54
+ }
55
+
56
+ .background {
57
+ position: absolute;
58
+ inset: 0;
59
+ background-color: var(--_container-color);
60
+ opacity: var(--_container-opacity, 1);
61
+ border-radius: var(--shape-corner-medium);
62
+ pointer-events: none;
63
+ }
64
+
65
+ .focus-ring {
66
+ --focus-ring-container-shape-start-start: var(--shape-corner-medium);
67
+ --focus-ring-container-shape-start-end: var(--shape-corner-medium);
68
+ --focus-ring-container-shape-end-start: var(--shape-corner-medium);
69
+ --focus-ring-container-shape-end-end: var(--shape-corner-medium);
70
+ z-index: 2;
71
+ }
72
+
73
+ .ripple {
74
+ --ripple-state-opacity: var(--_container-state-opacity, 0);
75
+ --ripple-pressed-color: var(--_container-state-color);
76
+ border-radius: var(--shape-corner-medium);
77
+ }
78
+ }
79
+ .item-element {
80
+ --_container-color: transparent;
81
+ --_label-text-color: var(--color-on-surface);
82
+ --_leading-trailing-color: var(--color-on-surface-variant);
83
+ --_container-state-color: var(--color-on-surface);
84
+
85
+ &:hover:not(:where(.disabled, .selected)) {
86
+ --_container-state-opacity: 0.08;
87
+ }
88
+
89
+ &.pressed:not(:where(.disabled)) {
90
+ --_container-state-opacity: 0.12;
91
+ }
92
+
93
+ &.selected {
94
+ --_container-color: var(--color-secondary-container);
95
+ --_label-text-color: var(--color-on-secondary-container);
96
+ --_leading-trailing-color: var(--color-on-secondary-container);
97
+ --_container-state-color: var(--color-on-secondary-container);
98
+ }
99
+
100
+ &.disabled {
101
+ cursor: not-allowed;
102
+ --_label-text-color: var(--color-on-surface);
103
+ --_label-text-opacity: 0.38;
104
+ --_leading-trailing-color: var(--color-on-surface);
105
+ --_container-opacity: 0.12;
106
+
107
+ .ripple {
108
+ display: none;
109
+ }
110
+ }
111
+ }