@refrakt-md/editor 0.8.0 → 0.8.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 (42) hide show
  1. package/app/dist/assets/{index-Ca-wW6uw.js → index-80NtMar1.js} +1 -1
  2. package/app/dist/assets/index-B6H6LF1M.css +1 -0
  3. package/app/dist/assets/{index-Dg4A5Pez.js → index-BDj1XPol.js} +1 -1
  4. package/app/dist/assets/{index-BfYWp0QC.js → index-BXe1fKaT.js} +1 -1
  5. package/app/dist/assets/{index-Cq0Maciq.js → index-BfxTGrHB.js} +1 -1
  6. package/app/dist/assets/{index-BsSUa0GD.js → index-Bn8ajfVl.js} +1 -1
  7. package/app/dist/assets/{index-D6vnTt4b.js → index-CCkzIGTi.js} +2 -2
  8. package/app/dist/assets/{index-BehCztSl.js → index-CXeK-dZx.js} +1 -1
  9. package/app/dist/assets/{index-iGDqoXj_.js → index-CaRBCHaX.js} +1 -1
  10. package/app/dist/assets/index-Cd12jZId.js +479 -0
  11. package/app/dist/assets/{index-D5pMhPrg.js → index-Cgbvx23V.js} +1 -1
  12. package/app/dist/assets/{index-IU6QYZAa.js → index-D5ucdUTo.js} +1 -1
  13. package/app/dist/assets/{index-CdpS6tGk.js → index-DGYxLhpR.js} +1 -1
  14. package/app/dist/assets/{index-RKEq45V5.js → index-DNJBunzP.js} +1 -1
  15. package/app/dist/assets/{index-Cgaw2jCE.js → index-DNtuldOx.js} +1 -1
  16. package/app/dist/assets/{index-BEPqnnsd.js → index-DQUOY-pF.js} +1 -1
  17. package/app/dist/assets/{index-2hOoPFOR.js → index-DskvyNKT.js} +1 -1
  18. package/app/dist/assets/{index-CLZfwYyS.js → index-aPeHMqUX.js} +1 -1
  19. package/app/dist/assets/{index-BobjskUl.js → index-dGztG-54.js} +1 -1
  20. package/app/dist/assets/{index-DHALjxX5.js → index-xo7v6nRB.js} +1 -1
  21. package/app/dist/index.html +2 -2
  22. package/app/src/lib/api/client.ts +81 -0
  23. package/app/src/lib/components/ActionEditPopover.svelte +267 -0
  24. package/app/src/lib/components/BlockCard.svelte +285 -0
  25. package/app/src/lib/components/BlockEditPanel.svelte +640 -260
  26. package/app/src/lib/components/BlockEditor.svelte +513 -52
  27. package/app/src/lib/components/CodeEditPopover.svelte +444 -0
  28. package/app/src/lib/components/ContentModelTree.svelte +835 -0
  29. package/app/src/lib/components/EditorLayout.svelte +1 -6
  30. package/app/src/lib/components/FrontmatterEditPanel.svelte +0 -1
  31. package/app/src/lib/components/IconPickerPopover.svelte +389 -0
  32. package/app/src/lib/components/ImageEditPopover.svelte +519 -0
  33. package/app/src/lib/components/InlineEditPopover.svelte +616 -0
  34. package/app/src/lib/components/RuneAttributes.svelte +51 -0
  35. package/app/src/lib/editor/block-parser.ts +424 -6
  36. package/dist/server.d.ts +1 -0
  37. package/dist/server.d.ts.map +1 -1
  38. package/dist/server.js +189 -2
  39. package/dist/server.js.map +1 -1
  40. package/package.json +6 -6
  41. package/app/dist/assets/index-98ylvoBO.css +0 -1
  42. package/app/dist/assets/index-CVzOx0nV.js +0 -372
@@ -0,0 +1,616 @@
1
+ <script lang="ts">
2
+ import { onMount } from 'svelte';
3
+ import { parseInlineMarkdown, serializeInlineHtml, normalizeEditableDom } from '../editor/inline-markdown.js';
4
+
5
+ interface Props {
6
+ anchorRect: DOMRect;
7
+ dataName: string;
8
+ inlineSource: string;
9
+ onchange: (newInlineSource: string) => void;
10
+ onclose: () => void;
11
+ }
12
+
13
+ let { anchorRect, dataName, inlineSource, onchange, onclose }: Props = $props();
14
+
15
+ let popoverEl: HTMLDivElement;
16
+ let editableEl: HTMLDivElement;
17
+ let left = $state(0);
18
+ let top = $state(0);
19
+ let showAbove = $state(true);
20
+
21
+ // Link editing state
22
+ let linkEdit: {
23
+ anchorEl: HTMLAnchorElement;
24
+ href: string;
25
+ text: string;
26
+ rect: DOMRect;
27
+ } | null = $state(null);
28
+ let linkHrefInput: HTMLInputElement | undefined = $state();
29
+
30
+ let debounceTimer: ReturnType<typeof setTimeout>;
31
+
32
+ onMount(() => {
33
+ // Set initial content from markdown source
34
+ editableEl.innerHTML = parseInlineMarkdown(inlineSource);
35
+
36
+ // Position centered above the anchor by default
37
+ const popoverRect = popoverEl.getBoundingClientRect();
38
+ const popoverWidth = popoverRect.width;
39
+ const popoverHeight = popoverRect.height;
40
+ const gap = 8;
41
+
42
+ // Horizontal: center on anchor
43
+ let x = anchorRect.left + anchorRect.width / 2 - popoverWidth / 2;
44
+ if (x < gap) x = gap;
45
+ if (x + popoverWidth > window.innerWidth - gap) x = window.innerWidth - popoverWidth - gap;
46
+ left = x;
47
+
48
+ // Vertical: above anchor if room, else below
49
+ const aboveY = anchorRect.top - popoverHeight - gap;
50
+ if (aboveY >= gap) {
51
+ top = aboveY;
52
+ showAbove = true;
53
+ } else {
54
+ top = anchorRect.bottom + gap;
55
+ showAbove = false;
56
+ }
57
+
58
+ // Focus and place cursor at end
59
+ editableEl.focus();
60
+ const sel = window.getSelection();
61
+ if (sel && editableEl.childNodes.length > 0) {
62
+ const range = document.createRange();
63
+ range.selectNodeContents(editableEl);
64
+ range.collapse(false);
65
+ sel.removeAllRanges();
66
+ sel.addRange(range);
67
+ }
68
+
69
+ // Close handlers
70
+ function handleClickOutside(e: MouseEvent) {
71
+ if (popoverEl && !popoverEl.contains(e.target as Node)) {
72
+ onclose();
73
+ }
74
+ }
75
+
76
+ function handleKeydown(e: KeyboardEvent) {
77
+ if (e.key === 'Escape') {
78
+ if (linkEdit) {
79
+ linkEdit = null;
80
+ } else {
81
+ onclose();
82
+ }
83
+ }
84
+ }
85
+
86
+ requestAnimationFrame(() => {
87
+ document.addEventListener('mousedown', handleClickOutside);
88
+ });
89
+ document.addEventListener('keydown', handleKeydown);
90
+
91
+ return () => {
92
+ document.removeEventListener('mousedown', handleClickOutside);
93
+ document.removeEventListener('keydown', handleKeydown);
94
+ clearTimeout(debounceTimer);
95
+ };
96
+ });
97
+
98
+ function handleInput() {
99
+ clearTimeout(debounceTimer);
100
+ debounceTimer = setTimeout(() => {
101
+ normalizeEditableDom(editableEl);
102
+ const md = serializeInlineHtml(editableEl);
103
+ onchange(md);
104
+ }, 100);
105
+ }
106
+
107
+ function handleKeydown(e: KeyboardEvent) {
108
+ // Block Enter to prevent block-level elements
109
+ if (e.key === 'Enter') {
110
+ e.preventDefault();
111
+ return;
112
+ }
113
+
114
+ const mod = e.metaKey || e.ctrlKey;
115
+ if (!mod) return;
116
+
117
+ if (e.key === 'b') {
118
+ e.preventDefault();
119
+ toggleFormat('bold');
120
+ } else if (e.key === 'i') {
121
+ e.preventDefault();
122
+ toggleFormat('italic');
123
+ } else if (e.key === 'k') {
124
+ e.preventDefault();
125
+ openLinkEditor();
126
+ }
127
+ }
128
+
129
+ function handlePaste(e: ClipboardEvent) {
130
+ e.preventDefault();
131
+ const text = e.clipboardData?.getData('text/plain') ?? '';
132
+ document.execCommand('insertText', false, text);
133
+ }
134
+
135
+ function handleEditableClick(e: MouseEvent) {
136
+ // Check if an <a> was clicked
137
+ const target = e.target as HTMLElement;
138
+ const anchor = target.closest('a') as HTMLAnchorElement | null;
139
+ if (anchor && editableEl.contains(anchor)) {
140
+ e.preventDefault();
141
+ openLinkEditorForAnchor(anchor);
142
+ }
143
+ }
144
+
145
+ // ── Formatting ──────────────────────────────────────────────
146
+
147
+ function toggleFormat(format: 'bold' | 'italic' | 'code') {
148
+ editableEl.focus();
149
+ const sel = window.getSelection();
150
+ if (!sel || sel.rangeCount === 0) return;
151
+
152
+ if (format === 'code') {
153
+ const range = sel.getRangeAt(0);
154
+ if (range.collapsed) return;
155
+
156
+ // Check if selection is already inside a <code>
157
+ const codeParent = findAncestor(range.commonAncestorContainer, 'CODE', editableEl);
158
+ if (codeParent) {
159
+ // Unwrap: replace <code> with its text content
160
+ const text = document.createTextNode(codeParent.textContent ?? '');
161
+ codeParent.replaceWith(text);
162
+ // Re-select the text
163
+ const newRange = document.createRange();
164
+ newRange.selectNode(text);
165
+ sel.removeAllRanges();
166
+ sel.addRange(newRange);
167
+ } else {
168
+ const code = document.createElement('code');
169
+ code.textContent = range.toString();
170
+ range.deleteContents();
171
+ range.insertNode(code);
172
+ // Select the code element contents
173
+ const newRange = document.createRange();
174
+ newRange.selectNodeContents(code);
175
+ sel.removeAllRanges();
176
+ sel.addRange(newRange);
177
+ }
178
+ } else {
179
+ // Bold and italic use execCommand for simplicity
180
+ const command = format === 'bold' ? 'bold' : 'italic';
181
+ document.execCommand(command, false);
182
+ }
183
+
184
+ normalizeEditableDom(editableEl);
185
+ handleInput();
186
+ }
187
+
188
+ function openLinkEditor() {
189
+ const sel = window.getSelection();
190
+ if (!sel || sel.rangeCount === 0) return;
191
+
192
+ const range = sel.getRangeAt(0);
193
+
194
+ // Check if cursor is inside an existing link
195
+ const existingLink = findAncestor(range.commonAncestorContainer, 'A', editableEl) as HTMLAnchorElement | null;
196
+ if (existingLink) {
197
+ openLinkEditorForAnchor(existingLink);
198
+ return;
199
+ }
200
+
201
+ // Create a new link from selection
202
+ if (range.collapsed) return;
203
+
204
+ const linkText = range.toString();
205
+ const a = document.createElement('a');
206
+ a.href = '';
207
+ a.textContent = linkText;
208
+ range.deleteContents();
209
+ range.insertNode(a);
210
+
211
+ openLinkEditorForAnchor(a);
212
+ }
213
+
214
+ function openLinkEditorForAnchor(anchor: HTMLAnchorElement) {
215
+ linkEdit = {
216
+ anchorEl: anchor,
217
+ href: anchor.getAttribute('href') ?? '',
218
+ text: anchor.textContent ?? '',
219
+ rect: anchor.getBoundingClientRect(),
220
+ };
221
+ // Focus the URL input after render
222
+ requestAnimationFrame(() => {
223
+ linkHrefInput?.focus();
224
+ linkHrefInput?.select();
225
+ });
226
+ }
227
+
228
+ function applyLinkEdit() {
229
+ if (!linkEdit) return;
230
+ linkEdit.anchorEl.href = linkEdit.href;
231
+ linkEdit.anchorEl.textContent = linkEdit.text;
232
+ linkEdit = null;
233
+ editableEl.focus();
234
+ handleInput();
235
+ }
236
+
237
+ function removeLinkEdit() {
238
+ if (!linkEdit) return;
239
+ const text = document.createTextNode(linkEdit.anchorEl.textContent ?? '');
240
+ linkEdit.anchorEl.replaceWith(text);
241
+ linkEdit = null;
242
+ editableEl.focus();
243
+ handleInput();
244
+ }
245
+
246
+ function handleLinkKeydown(e: KeyboardEvent) {
247
+ if (e.key === 'Enter') {
248
+ e.preventDefault();
249
+ applyLinkEdit();
250
+ }
251
+ }
252
+
253
+ // ── Toolbar state ───────────────────────────────────────────
254
+
255
+ let isBold = $state(false);
256
+ let isItalic = $state(false);
257
+ let isCode = $state(false);
258
+
259
+ function updateToolbarState() {
260
+ isBold = document.queryCommandState('bold');
261
+ isItalic = document.queryCommandState('italic');
262
+
263
+ const sel = window.getSelection();
264
+ if (sel && sel.rangeCount > 0) {
265
+ isCode = !!findAncestor(sel.getRangeAt(0).commonAncestorContainer, 'CODE', editableEl);
266
+ } else {
267
+ isCode = false;
268
+ }
269
+ }
270
+
271
+ // ── Helpers ─────────────────────────────────────────────────
272
+
273
+ function findAncestor(node: globalThis.Node, tagName: string, boundary: HTMLElement): HTMLElement | null {
274
+ let current: globalThis.Node | null = node;
275
+ while (current && current !== boundary) {
276
+ if (current.nodeType === globalThis.Node.ELEMENT_NODE && (current as HTMLElement).tagName === tagName) {
277
+ return current as HTMLElement;
278
+ }
279
+ current = current.parentNode;
280
+ }
281
+ return null;
282
+ }
283
+ </script>
284
+
285
+ <div
286
+ class="inline-edit-popover"
287
+ class:above={showAbove}
288
+ class:below={!showAbove}
289
+ bind:this={popoverEl}
290
+ style="left: {left}px; top: {top}px"
291
+ >
292
+ <!-- Titlebar -->
293
+ <div class="inline-edit-popover__header">
294
+ <span class="inline-edit-popover__label">{dataName}</span>
295
+ <button class="inline-edit-popover__close" onclick={onclose} aria-label="Close">&times;</button>
296
+ </div>
297
+
298
+ <!-- Toolbar -->
299
+ <div class="inline-edit-popover__toolbar">
300
+ <button
301
+ class="inline-edit-popover__tool-btn"
302
+ class:active={isBold}
303
+ onclick={() => toggleFormat('bold')}
304
+ title="Bold (Ctrl+B)"
305
+ ><strong>B</strong></button>
306
+ <button
307
+ class="inline-edit-popover__tool-btn"
308
+ class:active={isItalic}
309
+ onclick={() => toggleFormat('italic')}
310
+ title="Italic (Ctrl+I)"
311
+ ><em>I</em></button>
312
+ <button
313
+ class="inline-edit-popover__tool-btn inline-edit-popover__tool-btn--code"
314
+ class:active={isCode}
315
+ onclick={() => toggleFormat('code')}
316
+ title="Inline code"
317
+ >&lt;/&gt;</button>
318
+ <button
319
+ class="inline-edit-popover__tool-btn"
320
+ onclick={openLinkEditor}
321
+ title="Link (Ctrl+K)"
322
+ >
323
+ <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
324
+ <path d="M6.5 9.5a3 3 0 0 0 4.2.3l2-2a3 3 0 0 0-4.2-4.3l-1.1 1.1" />
325
+ <path d="M9.5 6.5a3 3 0 0 0-4.2-.3l-2 2a3 3 0 0 0 4.2 4.3l1.1-1.1" />
326
+ </svg>
327
+ </button>
328
+ </div>
329
+
330
+ <!-- Editable content area -->
331
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
332
+ <div
333
+ class="inline-edit-popover__editable"
334
+ contenteditable="true"
335
+ bind:this={editableEl}
336
+ oninput={handleInput}
337
+ onkeydown={handleKeydown}
338
+ onkeyup={updateToolbarState}
339
+ onmouseup={updateToolbarState}
340
+ onpaste={handlePaste}
341
+ onclick={handleEditableClick}
342
+ ></div>
343
+
344
+ <!-- Link editor sub-popover -->
345
+ {#if linkEdit}
346
+ <div class="inline-edit-popover__link-editor">
347
+ <div class="inline-edit-popover__link-row">
348
+ <label class="inline-edit-popover__link-label">Text</label>
349
+ <input
350
+ class="inline-edit-popover__link-input"
351
+ type="text"
352
+ bind:value={linkEdit.text}
353
+ onkeydown={handleLinkKeydown}
354
+ />
355
+ </div>
356
+ <div class="inline-edit-popover__link-row">
357
+ <label class="inline-edit-popover__link-label">URL</label>
358
+ <input
359
+ class="inline-edit-popover__link-input"
360
+ type="text"
361
+ bind:value={linkEdit.href}
362
+ bind:this={linkHrefInput}
363
+ onkeydown={handleLinkKeydown}
364
+ placeholder="https://..."
365
+ />
366
+ </div>
367
+ <div class="inline-edit-popover__link-actions">
368
+ <button class="inline-edit-popover__link-btn inline-edit-popover__link-btn--apply" onclick={applyLinkEdit}>Apply</button>
369
+ <button class="inline-edit-popover__link-btn inline-edit-popover__link-btn--remove" onclick={removeLinkEdit}>Remove link</button>
370
+ </div>
371
+ </div>
372
+ {/if}
373
+ </div>
374
+
375
+ <style>
376
+ .inline-edit-popover {
377
+ position: fixed;
378
+ z-index: 1100;
379
+ display: flex;
380
+ flex-direction: column;
381
+ gap: var(--ed-space-1, 0.25rem);
382
+ background: var(--ed-surface-0, #fff);
383
+ border: 1px solid var(--ed-border-default, #e2e8f0);
384
+ border-radius: var(--ed-radius-lg, 8px);
385
+ box-shadow: var(--ed-shadow-xl, 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1));
386
+ padding: var(--ed-space-2, 0.5rem);
387
+ min-width: 280px;
388
+ max-width: 520px;
389
+ animation: inline-edit-enter 120ms ease-out;
390
+ }
391
+
392
+ .inline-edit-popover.above {
393
+ transform-origin: bottom center;
394
+ }
395
+
396
+ .inline-edit-popover.below {
397
+ transform-origin: top center;
398
+ }
399
+
400
+ @keyframes inline-edit-enter {
401
+ from { opacity: 0; transform: scale(0.97) translateY(4px); }
402
+ to { opacity: 1; transform: scale(1) translateY(0); }
403
+ }
404
+
405
+ /* ── Header ──────────────────────────────────────────── */
406
+
407
+ .inline-edit-popover__header {
408
+ display: flex;
409
+ align-items: center;
410
+ justify-content: space-between;
411
+ padding: 0 var(--ed-space-1, 0.25rem);
412
+ }
413
+
414
+ .inline-edit-popover__label {
415
+ font-size: 10px;
416
+ font-weight: 700;
417
+ color: var(--ed-text-muted, #94a3b8);
418
+ text-transform: uppercase;
419
+ letter-spacing: 0.04em;
420
+ white-space: nowrap;
421
+ flex-shrink: 0;
422
+ }
423
+
424
+ .inline-edit-popover__close {
425
+ background: none;
426
+ border: none;
427
+ font-size: 18px;
428
+ line-height: 1;
429
+ color: var(--ed-text-muted, #94a3b8);
430
+ cursor: pointer;
431
+ padding: 0 4px;
432
+ }
433
+
434
+ .inline-edit-popover__close:hover {
435
+ color: var(--ed-text-primary, #1a1a2e);
436
+ }
437
+
438
+ /* ── Toolbar ──────────────────────────────────────────── */
439
+
440
+ .inline-edit-popover__toolbar {
441
+ display: flex;
442
+ align-items: center;
443
+ gap: 2px;
444
+ padding: 0 var(--ed-space-1, 0.25rem);
445
+ }
446
+
447
+ .inline-edit-popover__tool-btn {
448
+ display: flex;
449
+ align-items: center;
450
+ justify-content: center;
451
+ width: 26px;
452
+ height: 26px;
453
+ border: none;
454
+ border-radius: var(--ed-radius-sm, 4px);
455
+ background: transparent;
456
+ color: var(--ed-text-tertiary, #a0aec0);
457
+ cursor: pointer;
458
+ font-size: 13px;
459
+ font-family: inherit;
460
+ transition: background 100ms, color 100ms;
461
+ }
462
+
463
+ .inline-edit-popover__tool-btn:hover {
464
+ background: var(--ed-surface-2, #f1f5f9);
465
+ color: var(--ed-text-secondary, #475569);
466
+ }
467
+
468
+ .inline-edit-popover__tool-btn.active {
469
+ background: var(--ed-accent-muted, rgba(59, 130, 246, 0.1));
470
+ color: var(--ed-accent, #3b82f6);
471
+ }
472
+
473
+ .inline-edit-popover__tool-btn--code {
474
+ font-size: 10px;
475
+ font-family: monospace;
476
+ letter-spacing: -0.5px;
477
+ }
478
+
479
+ /* ── Editable area ────────────────────────────────────── */
480
+
481
+ .inline-edit-popover__editable {
482
+ min-height: 1.6em;
483
+ padding: var(--ed-space-1, 0.25rem) var(--ed-space-2, 0.5rem);
484
+ border: 1px solid var(--ed-border-default, #e2e8f0);
485
+ border-radius: var(--ed-radius-sm, 4px);
486
+ font-size: var(--ed-text-sm, 13px);
487
+ color: var(--ed-text-primary, #1a1a2e);
488
+ background: var(--ed-surface-0, #fff);
489
+ outline: none;
490
+ font-family: inherit;
491
+ line-height: 1.6;
492
+ white-space: nowrap;
493
+ overflow-x: auto;
494
+ transition: border-color 0.15s ease, box-shadow 0.15s ease;
495
+ }
496
+
497
+ .inline-edit-popover__editable:focus {
498
+ border-color: var(--ed-accent, #3b82f6);
499
+ box-shadow: 0 0 0 2px var(--ed-accent-ring, rgba(59, 130, 246, 0.2));
500
+ }
501
+
502
+ /* Inline formatting styles within the editable area */
503
+ .inline-edit-popover__editable :global(a) {
504
+ color: var(--ed-accent, #3b82f6);
505
+ text-decoration: underline;
506
+ text-decoration-style: dashed;
507
+ text-underline-offset: 2px;
508
+ cursor: pointer;
509
+ }
510
+
511
+ .inline-edit-popover__editable :global(a:hover) {
512
+ text-decoration-style: solid;
513
+ }
514
+
515
+ .inline-edit-popover__editable :global(code) {
516
+ background: var(--ed-surface-2, #f1f5f9);
517
+ border-radius: 3px;
518
+ padding: 0.1em 0.3em;
519
+ font-family: monospace;
520
+ font-size: 0.9em;
521
+ }
522
+
523
+ .inline-edit-popover__editable :global(strong) {
524
+ font-weight: 700;
525
+ }
526
+
527
+ .inline-edit-popover__editable :global(em) {
528
+ font-style: italic;
529
+ }
530
+
531
+ /* ── Link editor ──────────────────────────────────────── */
532
+
533
+ .inline-edit-popover__link-editor {
534
+ display: flex;
535
+ flex-direction: column;
536
+ gap: var(--ed-space-1, 0.25rem);
537
+ padding: var(--ed-space-2, 0.5rem);
538
+ border-top: 1px solid var(--ed-border-default, #e2e8f0);
539
+ margin-top: var(--ed-space-1, 0.25rem);
540
+ }
541
+
542
+ .inline-edit-popover__link-row {
543
+ display: flex;
544
+ align-items: center;
545
+ gap: var(--ed-space-2, 0.5rem);
546
+ }
547
+
548
+ .inline-edit-popover__link-label {
549
+ font-size: 10px;
550
+ font-weight: 600;
551
+ color: var(--ed-text-muted, #94a3b8);
552
+ text-transform: uppercase;
553
+ letter-spacing: 0.03em;
554
+ width: 32px;
555
+ flex-shrink: 0;
556
+ }
557
+
558
+ .inline-edit-popover__link-input {
559
+ flex: 1;
560
+ min-width: 0;
561
+ padding: var(--ed-space-1, 0.25rem) var(--ed-space-2, 0.5rem);
562
+ border: 1px solid var(--ed-border-default, #e2e8f0);
563
+ border-radius: var(--ed-radius-sm, 4px);
564
+ font-size: var(--ed-text-sm, 13px);
565
+ color: var(--ed-text-primary, #1a1a2e);
566
+ background: var(--ed-surface-0, #fff);
567
+ outline: none;
568
+ font-family: inherit;
569
+ line-height: 1.4;
570
+ }
571
+
572
+ .inline-edit-popover__link-input:focus {
573
+ border-color: var(--ed-accent, #3b82f6);
574
+ box-shadow: 0 0 0 2px var(--ed-accent-ring, rgba(59, 130, 246, 0.2));
575
+ }
576
+
577
+ .inline-edit-popover__link-actions {
578
+ display: flex;
579
+ gap: var(--ed-space-2, 0.5rem);
580
+ margin-top: var(--ed-space-1, 0.25rem);
581
+ }
582
+
583
+ .inline-edit-popover__link-btn {
584
+ display: flex;
585
+ align-items: center;
586
+ justify-content: center;
587
+ gap: var(--ed-space-2);
588
+ height: 28px;
589
+ padding: 0 var(--ed-space-3);
590
+ border: 1px solid var(--ed-text-secondary);
591
+ border-radius: var(--ed-radius-sm);
592
+ background: transparent;
593
+ color: var(--ed-text-secondary);
594
+ font-size: var(--ed-text-sm);
595
+ font-weight: 500;
596
+ cursor: pointer;
597
+ transition: background var(--ed-transition-fast), color var(--ed-transition-fast), border-color var(--ed-transition-fast);
598
+ white-space: nowrap;
599
+ }
600
+
601
+ .inline-edit-popover__link-btn:hover:not(.inline-edit-popover__link-btn--apply):not(:disabled) {
602
+ border-color: var(--ed-text-primary);
603
+ color: var(--ed-text-primary);
604
+ }
605
+
606
+ .inline-edit-popover__link-btn--apply {
607
+ background: var(--ed-text-primary);
608
+ color: #ffffff;
609
+ border-color: var(--ed-text-primary);
610
+ }
611
+
612
+ .inline-edit-popover__link-btn--apply:hover:not(:disabled) {
613
+ background: var(--ed-text-secondary);
614
+ border-color: var(--ed-text-secondary);
615
+ }
616
+ </style>
@@ -103,6 +103,15 @@
103
103
  {#if info.required}
104
104
  <span class="rune-attrs__required">*</span>
105
105
  {/if}
106
+ {#if info.description}
107
+ <span class="rune-attrs__info" data-tooltip={info.description}>
108
+ <svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
109
+ <circle cx="8" cy="8" r="7" />
110
+ <line x1="8" y1="7" x2="8" y2="11" />
111
+ <circle cx="8" cy="4.5" r="0.5" fill="currentColor" stroke="none" />
112
+ </svg>
113
+ </span>
114
+ {/if}
106
115
  </span>
107
116
 
108
117
  {#if info.type === 'Boolean'}
@@ -220,6 +229,48 @@
220
229
  color: var(--ed-danger);
221
230
  }
222
231
 
232
+ .rune-attrs__info {
233
+ display: inline-flex;
234
+ align-items: center;
235
+ color: var(--ed-text-muted);
236
+ cursor: help;
237
+ position: relative;
238
+ margin-left: 0.2rem;
239
+ vertical-align: middle;
240
+ transition: color var(--ed-transition-fast);
241
+ }
242
+
243
+ .rune-attrs__info:hover {
244
+ color: var(--ed-accent);
245
+ }
246
+
247
+ .rune-attrs__info::after {
248
+ content: attr(data-tooltip);
249
+ position: absolute;
250
+ left: 0;
251
+ top: calc(100% + 6px);
252
+ background: var(--ed-surface-invert, #1a1a1a);
253
+ color: var(--ed-text-invert, #f0f0f0);
254
+ font-size: var(--ed-text-sm);
255
+ font-style: normal;
256
+ font-weight: 400;
257
+ padding: 0.35rem 0.5rem;
258
+ border-radius: var(--ed-radius-sm);
259
+ box-shadow: var(--ed-shadow-lg);
260
+ white-space: normal;
261
+ width: max-content;
262
+ max-width: 220px;
263
+ line-height: 1.4;
264
+ pointer-events: none;
265
+ opacity: 0;
266
+ transition: opacity var(--ed-transition-fast);
267
+ z-index: 20;
268
+ }
269
+
270
+ .rune-attrs__info:hover::after {
271
+ opacity: 1;
272
+ }
273
+
223
274
  /* Clickable value button */
224
275
  .rune-attrs__value {
225
276
  font-size: var(--ed-text-base);