@aquera/nile-elements 1.8.5 → 1.8.7

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 (115) hide show
  1. package/README.md +8 -0
  2. package/dist/index.cjs.js +1 -1
  3. package/dist/index.esm.js +1 -1
  4. package/dist/index.js +915 -321
  5. package/dist/nile-markdown/index.cjs.js +2 -0
  6. package/dist/nile-markdown/index.cjs.js.map +1 -0
  7. package/dist/nile-markdown/index.esm.js +1 -0
  8. package/dist/nile-markdown/nile-markdown.cjs.js +30 -0
  9. package/dist/nile-markdown/nile-markdown.cjs.js.map +1 -0
  10. package/dist/nile-markdown/nile-markdown.css.cjs.js +2 -0
  11. package/dist/nile-markdown/nile-markdown.css.cjs.js.map +1 -0
  12. package/dist/nile-markdown/nile-markdown.css.esm.js +152 -0
  13. package/dist/nile-markdown/nile-markdown.esm.js +3 -0
  14. package/dist/nile-markdown-editor/index.cjs.js +2 -0
  15. package/dist/nile-markdown-editor/index.cjs.js.map +1 -0
  16. package/dist/nile-markdown-editor/index.esm.js +1 -0
  17. package/dist/nile-markdown-editor/nile-markdown-editor.cjs.js +2 -0
  18. package/dist/nile-markdown-editor/nile-markdown-editor.cjs.js.map +1 -0
  19. package/dist/nile-markdown-editor/nile-markdown-editor.css.cjs.js +2 -0
  20. package/dist/nile-markdown-editor/nile-markdown-editor.css.cjs.js.map +1 -0
  21. package/dist/nile-markdown-editor/nile-markdown-editor.css.esm.js +255 -0
  22. package/dist/nile-markdown-editor/nile-markdown-editor.esm.js +143 -0
  23. package/dist/nile-option/nile-option.cjs.js +1 -1
  24. package/dist/nile-option/nile-option.cjs.js.map +1 -1
  25. package/dist/nile-option/nile-option.css.cjs.js +1 -1
  26. package/dist/nile-option/nile-option.css.cjs.js.map +1 -1
  27. package/dist/nile-option/nile-option.css.esm.js +22 -1
  28. package/dist/nile-option/nile-option.esm.js +12 -2
  29. package/dist/nile-select/nile-select.cjs.js +1 -1
  30. package/dist/nile-select/nile-select.cjs.js.map +1 -1
  31. package/dist/nile-select/nile-select.css.cjs.js +1 -1
  32. package/dist/nile-select/nile-select.css.cjs.js.map +1 -1
  33. package/dist/nile-select/nile-select.css.esm.js +16 -7
  34. package/dist/nile-select/nile-select.esm.js +2 -2
  35. package/dist/nile-select/virtual-scroll-helper.cjs.js +1 -1
  36. package/dist/nile-select/virtual-scroll-helper.cjs.js.map +1 -1
  37. package/dist/nile-select/virtual-scroll-helper.esm.js +2 -0
  38. package/dist/nile-virtual-select/nile-virtual-select.cjs.js +3 -3
  39. package/dist/nile-virtual-select/nile-virtual-select.cjs.js.map +1 -1
  40. package/dist/nile-virtual-select/nile-virtual-select.css.cjs.js +1 -1
  41. package/dist/nile-virtual-select/nile-virtual-select.css.cjs.js.map +1 -1
  42. package/dist/nile-virtual-select/nile-virtual-select.css.esm.js +4 -3
  43. package/dist/nile-virtual-select/nile-virtual-select.esm.js +4 -4
  44. package/dist/nile-virtual-select/renderer.cjs.js +1 -1
  45. package/dist/nile-virtual-select/renderer.cjs.js.map +1 -1
  46. package/dist/nile-virtual-select/renderer.esm.js +14 -12
  47. package/dist/src/index.d.ts +2 -0
  48. package/dist/src/index.js +2 -0
  49. package/dist/src/index.js.map +1 -1
  50. package/dist/src/nile-markdown/index.d.ts +1 -0
  51. package/dist/src/nile-markdown/index.js +2 -0
  52. package/dist/src/nile-markdown/index.js.map +1 -0
  53. package/dist/src/nile-markdown/nile-markdown.css.d.ts +10 -0
  54. package/dist/src/nile-markdown/nile-markdown.css.js +163 -0
  55. package/dist/src/nile-markdown/nile-markdown.css.js.map +1 -0
  56. package/dist/src/nile-markdown/nile-markdown.d.ts +91 -0
  57. package/dist/src/nile-markdown/nile-markdown.js +167 -0
  58. package/dist/src/nile-markdown/nile-markdown.js.map +1 -0
  59. package/dist/src/nile-markdown/nile-markdown.test.d.ts +1 -0
  60. package/dist/src/nile-markdown/nile-markdown.test.js +192 -0
  61. package/dist/src/nile-markdown/nile-markdown.test.js.map +1 -0
  62. package/dist/src/nile-markdown-editor/index.d.ts +1 -0
  63. package/dist/src/nile-markdown-editor/index.js +2 -0
  64. package/dist/src/nile-markdown-editor/index.js.map +1 -0
  65. package/dist/src/nile-markdown-editor/nile-markdown-editor.css.d.ts +10 -0
  66. package/dist/src/nile-markdown-editor/nile-markdown-editor.css.js +266 -0
  67. package/dist/src/nile-markdown-editor/nile-markdown-editor.css.js.map +1 -0
  68. package/dist/src/nile-markdown-editor/nile-markdown-editor.d.ts +121 -0
  69. package/dist/src/nile-markdown-editor/nile-markdown-editor.js +615 -0
  70. package/dist/src/nile-markdown-editor/nile-markdown-editor.js.map +1 -0
  71. package/dist/src/nile-markdown-editor/nile-markdown-editor.test.d.ts +1 -0
  72. package/dist/src/nile-markdown-editor/nile-markdown-editor.test.js +268 -0
  73. package/dist/src/nile-markdown-editor/nile-markdown-editor.test.js.map +1 -0
  74. package/dist/src/nile-option/nile-option.css.js +22 -1
  75. package/dist/src/nile-option/nile-option.css.js.map +1 -1
  76. package/dist/src/nile-option/nile-option.d.ts +3 -0
  77. package/dist/src/nile-option/nile-option.js +21 -0
  78. package/dist/src/nile-option/nile-option.js.map +1 -1
  79. package/dist/src/nile-select/nile-select.css.js +16 -7
  80. package/dist/src/nile-select/nile-select.css.js.map +1 -1
  81. package/dist/src/nile-select/nile-select.d.ts +7 -0
  82. package/dist/src/nile-select/nile-select.js +35 -0
  83. package/dist/src/nile-select/nile-select.js.map +1 -1
  84. package/dist/src/nile-select/virtual-scroll-helper.js +2 -0
  85. package/dist/src/nile-select/virtual-scroll-helper.js.map +1 -1
  86. package/dist/src/nile-virtual-select/nile-virtual-select.css.js +4 -3
  87. package/dist/src/nile-virtual-select/nile-virtual-select.css.js.map +1 -1
  88. package/dist/src/nile-virtual-select/nile-virtual-select.d.ts +4 -0
  89. package/dist/src/nile-virtual-select/nile-virtual-select.js +11 -1
  90. package/dist/src/nile-virtual-select/nile-virtual-select.js.map +1 -1
  91. package/dist/src/nile-virtual-select/renderer.d.ts +2 -2
  92. package/dist/src/nile-virtual-select/renderer.js +6 -4
  93. package/dist/src/nile-virtual-select/renderer.js.map +1 -1
  94. package/dist/src/version.js +1 -1
  95. package/dist/src/version.js.map +1 -1
  96. package/dist/tsconfig.tsbuildinfo +1 -1
  97. package/package.json +2 -1
  98. package/src/index.ts +3 -1
  99. package/src/nile-markdown/index.ts +1 -0
  100. package/src/nile-markdown/nile-markdown.css.ts +164 -0
  101. package/src/nile-markdown/nile-markdown.test.ts +252 -0
  102. package/src/nile-markdown/nile-markdown.ts +179 -0
  103. package/src/nile-markdown-editor/index.ts +1 -0
  104. package/src/nile-markdown-editor/nile-markdown-editor.css.ts +267 -0
  105. package/src/nile-markdown-editor/nile-markdown-editor.test.ts +402 -0
  106. package/src/nile-markdown-editor/nile-markdown-editor.ts +710 -0
  107. package/src/nile-option/nile-option.css.ts +22 -1
  108. package/src/nile-option/nile-option.ts +18 -0
  109. package/src/nile-select/nile-select.css.ts +16 -7
  110. package/src/nile-select/nile-select.ts +32 -0
  111. package/src/nile-select/virtual-scroll-helper.ts +2 -0
  112. package/src/nile-virtual-select/nile-virtual-select.css.ts +4 -3
  113. package/src/nile-virtual-select/nile-virtual-select.ts +9 -1
  114. package/src/nile-virtual-select/renderer.ts +9 -3
  115. package/vscode-html-custom-data.json +115 -3
@@ -0,0 +1,615 @@
1
+ /**
2
+ * Copyright Aquera Inc 2023
3
+ *
4
+ * This source code is licensed under the BSD-3-Clause license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ import { __decorate } from "tslib";
8
+ import { html, nothing } from 'lit';
9
+ import { customElement, property, query, state } from 'lit/decorators.js';
10
+ import { classMap } from 'lit/directives/class-map.js';
11
+ import { styles } from './nile-markdown-editor.css';
12
+ import NileElement from '../internal/nile-element';
13
+ import '../nile-markdown/nile-markdown';
14
+ import '../nile-icon';
15
+ import '../nile-button-toggle-group/nile-button-toggle-group';
16
+ import '../nile-button-toggle/nile-button-toggle';
17
+ import '../nile-dropdown/nile-dropdown';
18
+ import '../nile-menu/nile-menu';
19
+ import '../nile-menu-item/nile-menu-item';
20
+ /** Toolbar actions, grouped — groups are separated by vertical dividers. */
21
+ const TOOLBAR_GROUPS = [
22
+ [
23
+ {
24
+ name: 'heading',
25
+ title: 'Heading',
26
+ kind: 'menu',
27
+ glyph: 'ng-heading',
28
+ items: [
29
+ { label: 'Heading 1', prefix: '# ', glyph: 'ng-heading-1' },
30
+ { label: 'Heading 2', prefix: '## ', glyph: 'ng-heading-2' },
31
+ { label: 'Heading 3', prefix: '### ', glyph: 'ng-heading-3' },
32
+ { label: 'Heading 4', prefix: '#### ', glyph: 'ng-heading-4' },
33
+ { label: 'Heading 5', prefix: '##### ', glyph: 'ng-heading-5' },
34
+ { label: 'Heading 6', prefix: '###### ', glyph: 'ng-heading-6' },
35
+ ],
36
+ },
37
+ {
38
+ name: 'bold',
39
+ title: 'Bold (Ctrl+B)',
40
+ kind: 'wrap',
41
+ prefix: '**',
42
+ suffix: '**',
43
+ placeholder: 'bold text',
44
+ icon: 'format_bold',
45
+ },
46
+ {
47
+ name: 'italic',
48
+ title: 'Italic (Ctrl+I)',
49
+ kind: 'wrap',
50
+ prefix: '_',
51
+ suffix: '_',
52
+ placeholder: 'italic text',
53
+ icon: 'format_italic',
54
+ },
55
+ {
56
+ name: 'strikethrough',
57
+ title: 'Strikethrough',
58
+ kind: 'wrap',
59
+ prefix: '~~',
60
+ suffix: '~~',
61
+ placeholder: 'text',
62
+ glyph: 'ng-strikethrough',
63
+ },
64
+ ],
65
+ [
66
+ { name: 'quote', title: 'Quote', kind: 'line', prefix: '> ', glyph: 'ng-quote' },
67
+ {
68
+ name: 'code',
69
+ title: 'Code',
70
+ kind: 'wrap',
71
+ prefix: '`',
72
+ suffix: '`',
73
+ placeholder: 'code',
74
+ glyph: 'ng-code',
75
+ },
76
+ ],
77
+ [
78
+ {
79
+ name: 'ul',
80
+ title: 'Bulleted list',
81
+ kind: 'line',
82
+ prefix: '- ',
83
+ icon: 'format_list_bulleted',
84
+ },
85
+ {
86
+ name: 'ol',
87
+ title: 'Numbered list',
88
+ kind: 'line',
89
+ prefix: '1. ',
90
+ numbered: true,
91
+ icon: 'format_list_numbered',
92
+ },
93
+ ],
94
+ [{ name: 'link', title: 'Link (Ctrl+K)', kind: 'link', icon: 'link_2' }],
95
+ ];
96
+ const TOOLBAR_ACTIONS = TOOLBAR_GROUPS.flat();
97
+ /** Glyph shown alongside each view-mode tab label. */
98
+ const TAB_GLYPHS = {
99
+ write: 'ng-pencil',
100
+ preview: 'ng-eye',
101
+ split: 'ng-square-split-horizontal',
102
+ };
103
+ /**
104
+ * Nile markdown editor component.
105
+ *
106
+ * @tag nile-markdown-editor
107
+ */
108
+ /**
109
+ * @summary A GitHub-style markdown editor with a formatting toolbar and a
110
+ * live preview rendered by `nile-markdown`.
111
+ * @status experimental
112
+ *
113
+ * @dependency nile-markdown
114
+ * @dependency nile-icon
115
+ * @dependency nile-glyph
116
+ * @dependency nile-button-toggle-group
117
+ * @dependency nile-button-toggle
118
+ *
119
+ * @attr {string} tools - JSON-array allowlist of toolbar tools to show, e.g.
120
+ * `tools='["bold","italic","link"]'`. Valid names: heading, bold, italic,
121
+ * strikethrough, quote, code, ul, ol, link. Empty shows all.
122
+ *
123
+ * @event nile-input - Emitted with `{ value }` on every keystroke or toolbar action.
124
+ * @event nile-change - Emitted with `{ value }` when the editor loses focus after an edit.
125
+ * @event nile-mode-change - Emitted with `{ mode }` when the write/preview/split mode changes.
126
+ *
127
+ * @csspart base - The component's base wrapper.
128
+ * @csspart header - The header containing the tabs and toolbar.
129
+ * @csspart toolbar - The formatting toolbar.
130
+ * @csspart textarea - The markdown source textarea.
131
+ * @csspart preview - The rendered preview pane.
132
+ */
133
+ let NileMarkdownEditor = class NileMarkdownEditor extends NileElement {
134
+ constructor() {
135
+ super(...arguments);
136
+ /** The markdown source. */
137
+ this.value = '';
138
+ /** Placeholder shown when the editor is empty. */
139
+ this.placeholder = 'Write markdown here…';
140
+ /** Disables the editor. */
141
+ this.disabled = false;
142
+ /** Makes the source read-only while still allowing mode switching. */
143
+ this.readonly = false;
144
+ /** Number of visible text rows in write mode. */
145
+ this.rows = 8;
146
+ /** Active view: `write`, `preview`, or `split`. */
147
+ this.mode = 'write';
148
+ /** Hides the formatting toolbar. */
149
+ this.hideToolbar = false;
150
+ /**
151
+ * Allowlist of toolbar tool names to show. Accepts a JSON array via the
152
+ * `tools` attribute (e.g. `tools='["bold","italic","link"]'`), a
153
+ * comma-separated string, or an array when set as a property. When empty
154
+ * (default) every tool is shown. Valid names: `heading`, `bold`, `italic`,
155
+ * `strikethrough`, `quote`, `code`, `ul`, `ol`, `link`.
156
+ */
157
+ this.tools = [];
158
+ /**
159
+ * Fraction of the body width given to the write pane in split mode
160
+ * (the rest goes to the preview). Clamped to a sensible range while
161
+ * dragging the splitter.
162
+ */
163
+ this.splitRatio = 0.5;
164
+ /** Whether the split divider is currently being dragged. */
165
+ this.splitDragging = false;
166
+ /** Begins a pointer-driven resize of the split divider. */
167
+ this.startSplitDrag = (e) => {
168
+ if (this.mode !== 'split')
169
+ return;
170
+ const body = this.bodyEl;
171
+ if (!body)
172
+ return;
173
+ e.preventDefault();
174
+ this.splitDragging = true;
175
+ const rect = body.getBoundingClientRect();
176
+ const MIN = 0.15;
177
+ const MAX = 0.85;
178
+ const onMove = (ev) => {
179
+ if (rect.width === 0)
180
+ return;
181
+ const ratio = (ev.clientX - rect.left) / rect.width;
182
+ this.splitRatio = Math.min(MAX, Math.max(MIN, ratio));
183
+ };
184
+ const onUp = () => {
185
+ this.splitDragging = false;
186
+ window.removeEventListener('pointermove', onMove);
187
+ window.removeEventListener('pointerup', onUp);
188
+ };
189
+ window.addEventListener('pointermove', onMove);
190
+ window.addEventListener('pointerup', onUp);
191
+ };
192
+ /** Resets the splitter to a 50/50 layout (double-click affordance). */
193
+ this.resetSplit = () => {
194
+ this.splitRatio = 0.5;
195
+ };
196
+ this.handleInput = (e) => {
197
+ this.value = e.target.value;
198
+ this.emit('nile-input', { value: this.value }, true, true);
199
+ };
200
+ this.handleChange = () => {
201
+ this.emit('nile-change', { value: this.value }, true, true);
202
+ };
203
+ this.handleKeydown = (e) => {
204
+ if (!(e.metaKey || e.ctrlKey))
205
+ return;
206
+ const key = e.key.toLowerCase();
207
+ const action = { b: 'bold', i: 'italic', k: 'link' }[key];
208
+ if (!action)
209
+ return;
210
+ // Respect the allowlist: a hidden tool's shortcut is disabled too.
211
+ const allowed = this.allowedTools;
212
+ if (allowed && !allowed.has(action))
213
+ return;
214
+ e.preventDefault();
215
+ this.runAction(TOOLBAR_ACTIONS.find(a => a.name === action));
216
+ };
217
+ }
218
+ /** Parsed allowlist, or `null` when no allowlist is set (show everything). */
219
+ get allowedTools() {
220
+ const names = (this.tools ?? []).map(s => s.trim()).filter(Boolean);
221
+ return names.length ? new Set(names) : null;
222
+ }
223
+ /** Toolbar groups after applying the allowlist; empty groups are dropped. */
224
+ get visibleGroups() {
225
+ const allowed = this.allowedTools;
226
+ if (!allowed)
227
+ return TOOLBAR_GROUPS;
228
+ return TOOLBAR_GROUPS.map(group => group.filter(action => allowed.has(action.name))).filter(group => group.length > 0);
229
+ }
230
+ /** Moves focus to the textarea. */
231
+ focus(options) {
232
+ this.textarea?.focus(options);
233
+ }
234
+ blur() {
235
+ this.textarea?.blur();
236
+ }
237
+ setMode(mode) {
238
+ if (this.mode === mode)
239
+ return;
240
+ this.mode = mode;
241
+ this.emit('nile-mode-change', { mode }, true, true);
242
+ }
243
+ /** Replaces the current selection and restores focus/selection. */
244
+ replaceSelection(start, end, replacement, selectStart, selectEnd) {
245
+ const ta = this.textarea;
246
+ if (!ta)
247
+ return;
248
+ ta.focus();
249
+ // Select the range to overwrite, then insert via the browser's editing
250
+ // pipeline so the change is recorded on the native undo/redo stack.
251
+ // Assigning `ta.value` directly would wipe that stack and break Ctrl/Cmd+Z.
252
+ ta.setSelectionRange(start, end);
253
+ const inserted = document.execCommand('insertText', false, replacement);
254
+ if (inserted) {
255
+ // execCommand dispatched a native `input` event, so `handleInput`
256
+ // already synced `value` and emitted `nile-input`; just fix selection.
257
+ ta.setSelectionRange(selectStart, selectEnd);
258
+ return;
259
+ }
260
+ // Fallback for environments without execCommand support.
261
+ ta.value = ta.value.slice(0, start) + replacement + ta.value.slice(end);
262
+ ta.setSelectionRange(selectStart, selectEnd);
263
+ this.value = ta.value;
264
+ this.emit('nile-input', { value: this.value }, true, true);
265
+ }
266
+ /** Wraps (or unwraps) the selection with prefix/suffix delimiters. */
267
+ applyWrap(action) {
268
+ const ta = this.textarea;
269
+ if (!ta)
270
+ return;
271
+ const { selectionStart: start, selectionEnd: end, value } = ta;
272
+ const { prefix, suffix } = action;
273
+ const selected = value.slice(start, end);
274
+ // Toggle off when the selection is already wrapped
275
+ const before = value.slice(Math.max(0, start - prefix.length), start);
276
+ const after = value.slice(end, end + suffix.length);
277
+ if (before === prefix && after === suffix) {
278
+ this.replaceSelection(start - prefix.length, end + suffix.length, selected, start - prefix.length, end - prefix.length);
279
+ return;
280
+ }
281
+ const content = selected || action.placeholder;
282
+ this.replaceSelection(start, end, prefix + content + suffix, start + prefix.length, start + prefix.length + content.length);
283
+ }
284
+ /** Toggles a markdown prefix on every line in the selection. */
285
+ applyLinePrefix(action) {
286
+ const ta = this.textarea;
287
+ if (!ta)
288
+ return;
289
+ const { selectionStart: start, selectionEnd: end, value } = ta;
290
+ // Expand the selection to whole lines
291
+ const lineStart = value.lastIndexOf('\n', start - 1) + 1;
292
+ const lineEndIndex = value.indexOf('\n', end);
293
+ const lineEnd = lineEndIndex === -1 ? value.length : lineEndIndex;
294
+ const block = value.slice(lineStart, lineEnd);
295
+ const lines = block.split('\n');
296
+ const matcher = action.numbered ? /^\d+\.\s/ : undefined;
297
+ const hasPrefix = lines.every(line => matcher ? matcher.test(line) : line.startsWith(action.prefix));
298
+ const replaced = lines
299
+ .map((line, i) => {
300
+ if (hasPrefix) {
301
+ return matcher
302
+ ? line.replace(matcher, '')
303
+ : line.slice(action.prefix.length);
304
+ }
305
+ return action.numbered ? `${i + 1}. ${line}` : action.prefix + line;
306
+ })
307
+ .join('\n');
308
+ this.replaceSelection(lineStart, lineEnd, replaced, lineStart, lineStart + replaced.length);
309
+ }
310
+ /** Inserts a `[text](url)` link, selecting the URL for quick editing. */
311
+ insertLink() {
312
+ const ta = this.textarea;
313
+ if (!ta)
314
+ return;
315
+ const { selectionStart: start, selectionEnd: end, value } = ta;
316
+ const text = value.slice(start, end) || 'text';
317
+ const url = 'url';
318
+ const replacement = `[${text}](${url})`;
319
+ const urlStart = start + text.length + 3; // "[text](".length
320
+ this.replaceSelection(start, end, replacement, urlStart, urlStart + url.length);
321
+ }
322
+ runAction(action) {
323
+ if (this.readonly || this.disabled)
324
+ return;
325
+ if (this.mode === 'preview')
326
+ this.setMode('write');
327
+ switch (action.kind) {
328
+ case 'wrap':
329
+ this.applyWrap(action);
330
+ break;
331
+ case 'line':
332
+ this.applyLinePrefix(action);
333
+ break;
334
+ case 'link':
335
+ this.insertLink();
336
+ break;
337
+ case 'menu':
338
+ // The dropdown items drive the edits via `runMenuItem`; nothing to do
339
+ // when the trigger itself is activated.
340
+ break;
341
+ }
342
+ }
343
+ /** Applies a heading-style line prefix chosen from a dropdown menu. */
344
+ runMenuItem(action, item) {
345
+ if (this.readonly || this.disabled)
346
+ return;
347
+ if (this.mode === 'preview')
348
+ this.setMode('write');
349
+ this.applyLinePrefix({
350
+ name: action.name,
351
+ title: action.title,
352
+ kind: 'line',
353
+ prefix: item.prefix,
354
+ });
355
+ }
356
+ /** Renders the glyph/icon shown inside a toolbar control. */
357
+ renderActionContent(action) {
358
+ if (action.icon) {
359
+ return html `<nile-icon
360
+ name=${action.icon}
361
+ size="20"
362
+ color=${this.disabled
363
+ ? 'var(--nile-colors-neutral-500, var(--ng-colors-fg-disabled-subtle))'
364
+ : 'var(--nile-colors-dark-900, var(--ng-colors-text-primary-900))'}
365
+ ></nile-icon>`;
366
+ }
367
+ // `ng-*` glyph names map to nile-glyph icons; anything else is plain text.
368
+ if (action.glyph?.startsWith('ng-')) {
369
+ return html `<nile-glyph
370
+ name=${action.glyph}
371
+ method="stroke"
372
+ color=${this.disabled
373
+ ? 'var(--nile-colors-neutral-500, var(--ng-colors-fg-disabled-subtle))'
374
+ : 'var(--nile-colors-dark-900, var(--ng-colors-text-primary-900))'}
375
+ size="20"
376
+ ></nile-glyph>`;
377
+ }
378
+ return html `<span class="toolbar__glyph--${action.name}">
379
+ ${action.glyph}
380
+ </span>`;
381
+ }
382
+ /** A standard click-to-apply toolbar button. */
383
+ renderActionButton(action) {
384
+ return html `
385
+ <button
386
+ type="button"
387
+ class="toolbar__button"
388
+ title=${action.title}
389
+ aria-label=${action.title}
390
+ tabindex="-1"
391
+ @mousedown=${(e) => e.preventDefault()}
392
+ @click=${() => this.runAction(action)}
393
+ >
394
+ ${this.renderActionContent(action)}
395
+ </button>
396
+ `;
397
+ }
398
+ /** A toolbar control that opens a dropdown of line-prefix choices. */
399
+ renderMenuAction(action) {
400
+ return html `
401
+ <nile-dropdown
402
+ class="toolbar__menu"
403
+ placement="bottom-start"
404
+ ?disabled=${this.disabled || this.readonly}
405
+ >
406
+ <button
407
+ slot="trigger"
408
+ type="button"
409
+ class="toolbar__button"
410
+ title=${action.title}
411
+ aria-label=${action.title}
412
+ aria-haspopup="menu"
413
+ tabindex="-1"
414
+ @mousedown=${(e) => e.preventDefault()}
415
+ >
416
+ ${this.renderActionContent(action)}
417
+ </button>
418
+ <nile-menu>
419
+ ${action.items.map(item => html `
420
+ <nile-menu-item
421
+ @click=${() => this.runMenuItem(action, item)}
422
+ >
423
+ ${item.glyph
424
+ ? html `<nile-glyph
425
+ slot="prefix"
426
+ name=${item.glyph}
427
+ method="stroke"
428
+ color="currentColor"
429
+ size="18"
430
+ ></nile-glyph>`
431
+ : nothing}
432
+ ${item.label}
433
+ </nile-menu-item>
434
+ `)}
435
+ </nile-menu>
436
+ </nile-dropdown>
437
+ `;
438
+ }
439
+ renderToolbar() {
440
+ if (this.hideToolbar || this.mode === 'preview')
441
+ return nothing;
442
+ const groups = this.visibleGroups;
443
+ if (groups.length === 0)
444
+ return nothing;
445
+ return html `
446
+ <div
447
+ class="toolbar"
448
+ part="toolbar"
449
+ role="toolbar"
450
+ aria-label="Formatting"
451
+ >
452
+ ${groups.map((group, groupIndex) => html `
453
+ ${groupIndex > 0
454
+ ? html `<span class="toolbar__divider"></span>`
455
+ : nothing}
456
+ ${group.map(action => action.kind === 'menu'
457
+ ? this.renderMenuAction(action)
458
+ : this.renderActionButton(action))}
459
+ `)}
460
+ </div>
461
+ `;
462
+ }
463
+ render() {
464
+ const showWrite = this.mode !== 'preview';
465
+ const showPreview = this.mode !== 'write';
466
+ return html `
467
+ <div
468
+ class=${classMap({
469
+ editor: true,
470
+ 'editor--disabled': this.disabled,
471
+ 'editor--split': this.mode === 'split',
472
+ })}
473
+ part="base"
474
+ >
475
+ <div class="header" part="header">
476
+ ${this.renderToolbar()}
477
+ <nile-button-toggle-group
478
+ class="tabs"
479
+ aria-label="Editor view"
480
+ .value=${this.mode}
481
+ @nile-change=${(e) => this.setMode(e.detail.value)}
482
+ >
483
+ ${['write', 'preview', 'split'].map(mode => html `
484
+ <nile-button-toggle
485
+ value=${mode}
486
+ ?active=${this.mode === mode}
487
+ >
488
+ <span class="tab__content">
489
+ <nile-glyph
490
+ name=${TAB_GLYPHS[mode]}
491
+ method="stroke"
492
+ color="currentColor"
493
+ size="16"
494
+ ></nile-glyph>
495
+ </span>
496
+ </nile-button-toggle>
497
+ `)}
498
+ </nile-button-toggle-group>
499
+ </div>
500
+ <div
501
+ class=${classMap({ body: true, 'body--dragging': this.splitDragging })}
502
+ >
503
+ ${showWrite
504
+ ? html `
505
+ <div
506
+ class="pane pane--write"
507
+ style=${this.mode === 'split'
508
+ ? `flex: 0 0 ${this.splitRatio * 100}%`
509
+ : nothing}
510
+ >
511
+ <textarea
512
+ part="textarea"
513
+ rows=${this.rows}
514
+ placeholder=${this.placeholder}
515
+ ?disabled=${this.disabled}
516
+ ?readonly=${this.readonly}
517
+ .value=${this.value}
518
+ @input=${this.handleInput}
519
+ @change=${this.handleChange}
520
+ @keydown=${this.handleKeydown}
521
+ ></textarea>
522
+ </div>
523
+ `
524
+ : nothing}
525
+ ${this.mode === 'split'
526
+ ? html `
527
+ <div
528
+ class="gutter"
529
+ part="gutter"
530
+ role="separator"
531
+ aria-orientation="vertical"
532
+ aria-label="Resize editor and preview"
533
+ title="Drag to resize · double-click to reset"
534
+ @pointerdown=${this.startSplitDrag}
535
+ @dblclick=${this.resetSplit}
536
+ ></div>
537
+ `
538
+ : nothing}
539
+ ${showPreview
540
+ ? html `
541
+ <div class="pane pane--preview" part="preview">
542
+ ${this.value.trim()
543
+ ? html `<nile-markdown .value=${this.value}></nile-markdown>`
544
+ : html `<span class="preview-empty"
545
+ >Nothing to preview</span
546
+ >`}
547
+ </div>
548
+ `
549
+ : nothing}
550
+ </div>
551
+ </div>
552
+ `;
553
+ }
554
+ };
555
+ NileMarkdownEditor.styles = styles;
556
+ __decorate([
557
+ property()
558
+ ], NileMarkdownEditor.prototype, "value", void 0);
559
+ __decorate([
560
+ property()
561
+ ], NileMarkdownEditor.prototype, "placeholder", void 0);
562
+ __decorate([
563
+ property({ type: Boolean, reflect: true })
564
+ ], NileMarkdownEditor.prototype, "disabled", void 0);
565
+ __decorate([
566
+ property({ type: Boolean, reflect: true })
567
+ ], NileMarkdownEditor.prototype, "readonly", void 0);
568
+ __decorate([
569
+ property({ type: Number })
570
+ ], NileMarkdownEditor.prototype, "rows", void 0);
571
+ __decorate([
572
+ property({ reflect: true })
573
+ ], NileMarkdownEditor.prototype, "mode", void 0);
574
+ __decorate([
575
+ property({ type: Boolean, attribute: 'hide-toolbar' })
576
+ ], NileMarkdownEditor.prototype, "hideToolbar", void 0);
577
+ __decorate([
578
+ property({
579
+ converter: {
580
+ fromAttribute: (value) => {
581
+ if (!value)
582
+ return [];
583
+ try {
584
+ const parsed = JSON.parse(value);
585
+ if (Array.isArray(parsed)) {
586
+ return parsed.map(s => String(s).trim()).filter(Boolean);
587
+ }
588
+ }
589
+ catch {
590
+ // Not JSON — fall back to a comma-separated string.
591
+ }
592
+ return value.split(',').map(s => s.trim()).filter(Boolean);
593
+ },
594
+ toAttribute: (value) => JSON.stringify(value ?? []),
595
+ },
596
+ })
597
+ ], NileMarkdownEditor.prototype, "tools", void 0);
598
+ __decorate([
599
+ query('textarea')
600
+ ], NileMarkdownEditor.prototype, "textarea", void 0);
601
+ __decorate([
602
+ query('.body')
603
+ ], NileMarkdownEditor.prototype, "bodyEl", void 0);
604
+ __decorate([
605
+ state()
606
+ ], NileMarkdownEditor.prototype, "splitRatio", void 0);
607
+ __decorate([
608
+ state()
609
+ ], NileMarkdownEditor.prototype, "splitDragging", void 0);
610
+ NileMarkdownEditor = __decorate([
611
+ customElement('nile-markdown-editor')
612
+ ], NileMarkdownEditor);
613
+ export { NileMarkdownEditor };
614
+ export default NileMarkdownEditor;
615
+ //# sourceMappingURL=nile-markdown-editor.js.map