@atom-forge/brickee 0.0.1 → 0.0.3

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 (72) hide show
  1. package/CHANGELOG.md +59 -1
  2. package/README-AI.md +34 -28
  3. package/dist/Editor.svelte +713 -867
  4. package/dist/Editor.svelte.d.ts +0 -3
  5. package/dist/LinkPrompt.svelte +1 -14
  6. package/dist/Preview.svelte +27 -58
  7. package/dist/Preview.svelte.d.ts +2 -7
  8. package/dist/PropsBar.svelte +75 -249
  9. package/dist/PropsBar.svelte.d.ts +0 -3
  10. package/dist/code.d.ts +12 -0
  11. package/dist/code.js +15 -0
  12. package/dist/dom.d.ts +8 -0
  13. package/dist/dom.js +9 -0
  14. package/dist/editor/BlockMenu.svelte +102 -0
  15. package/dist/editor/BlockMenu.svelte.d.ts +32 -0
  16. package/dist/editor/FormatBar.svelte +41 -0
  17. package/dist/editor/FormatBar.svelte.d.ts +16 -0
  18. package/dist/editor/drag.svelte.d.ts +63 -0
  19. package/dist/editor/drag.svelte.js +95 -0
  20. package/dist/editor/history.d.ts +24 -0
  21. package/dist/editor/history.js +86 -0
  22. package/dist/editor/sweep.svelte.d.ts +21 -0
  23. package/dist/editor/sweep.svelte.js +56 -0
  24. package/dist/html.d.ts +12 -0
  25. package/dist/html.js +206 -0
  26. package/dist/i18n.js +1 -1
  27. package/dist/index.d.ts +3 -2
  28. package/dist/index.js +4 -3
  29. package/dist/kit/Empty.svelte +4 -15
  30. package/dist/kit/Frame.svelte +7 -37
  31. package/dist/locales/hu.js +6 -8
  32. package/dist/markdown.d.ts +20 -6
  33. package/dist/markdown.js +380 -85
  34. package/dist/model.js +2 -3
  35. package/dist/plugins/Gap.svelte +5 -8
  36. package/dist/plugins/GapPreview.svelte +4 -6
  37. package/dist/plugins/Map.svelte +2 -17
  38. package/dist/plugins/MapPreview.svelte +8 -10
  39. package/dist/plugins/Table.svelte +233 -282
  40. package/dist/plugins/TablePreview.svelte +28 -28
  41. package/dist/plugins/YouTube.svelte +3 -31
  42. package/dist/plugins/YouTubePreview.svelte +45 -43
  43. package/dist/plugins/index.js +15 -2
  44. package/dist/schema.js +1 -1
  45. package/dist/types.d.ts +27 -10
  46. package/dist/types.js +8 -1
  47. package/dist/view.js +1 -1
  48. package/docs/en/bundled-plugins.md +1 -1
  49. package/docs/en/documents-and-markdown.md +24 -19
  50. package/docs/en/editor-and-preview.md +7 -10
  51. package/docs/en/getting-started.md +14 -2
  52. package/docs/en/host-contexts.md +26 -132
  53. package/docs/en/index.md +5 -5
  54. package/docs/en/recipes-and-pitfalls.md +12 -11
  55. package/docs/en/server-side.md +0 -17
  56. package/docs/en/upgrading.md +34 -4
  57. package/docs/en/writing-plugins.md +147 -21
  58. package/docs/hu/bundled-plugins.md +1 -1
  59. package/docs/hu/documents-and-markdown.md +24 -19
  60. package/docs/hu/editor-and-preview.md +7 -10
  61. package/docs/hu/getting-started.md +14 -2
  62. package/docs/hu/host-contexts.md +26 -132
  63. package/docs/hu/index.md +5 -5
  64. package/docs/hu/recipes-and-pitfalls.md +12 -11
  65. package/docs/hu/server-side.md +0 -17
  66. package/docs/hu/upgrading.md +34 -4
  67. package/docs/hu/writing-plugins.md +147 -21
  68. package/package.json +14 -2
  69. package/dist/ImagePicker.svelte +0 -131
  70. package/dist/ImagePicker.svelte.d.ts +0 -13
  71. package/dist/media.d.ts +0 -17
  72. package/dist/media.js +0 -12
@@ -14,13 +14,11 @@
14
14
  <iframe class="map" title={tr('Map: {place}', { place: String(props.q ?? '') })} {src} loading="lazy" referrerpolicy="no-referrer-when-downgrade"></iframe>
15
15
  {/if}
16
16
 
17
- <style>
18
- .map {
19
- display: block;
20
- width: 100%;
21
- height: 300px;
22
- margin: 0 0 1em;
23
- border: 0;
24
- border-radius: 10px;
25
- }
26
- </style>
17
+ <style>.map {
18
+ display: block;
19
+ width: 100%;
20
+ height: 300px;
21
+ margin: 0 0 1em;
22
+ border: 0;
23
+ border-radius: 10px;
24
+ }</style>
@@ -52,12 +52,9 @@
52
52
  BetweenVerticalEnd,
53
53
  BetweenVerticalStart,
54
54
  Columns3,
55
- Info,
56
55
  Rows3,
57
56
  GripHorizontal,
58
57
  GripVertical,
59
- PanelBottom,
60
- PanelTop,
61
58
  Plus,
62
59
  TextAlignCenter,
63
60
  TextAlignEnd,
@@ -65,6 +62,7 @@
65
62
  Trash2
66
63
  } from '@lucide/svelte';
67
64
  import { tick } from 'svelte';
65
+ import { inline } from '../markdown';
68
66
  import type { PluginViewProps } from '../types';
69
67
 
70
68
  let { props = $bindable(), readonly }: PluginViewProps<TableProps> = $props();
@@ -78,17 +76,6 @@
78
76
  let at = $state<{ r: number; c: number } | null>(null);
79
77
  const heads = $derived(tableHeads(props));
80
78
  const headOf = (r: number) => rowHead(heads, r, rows.length);
81
- function toggleHead(h: Head) {
82
- const next = new Set(heads);
83
- if (!next.delete(h)) next.add(h);
84
- const { header: _, ...rest } = props;
85
- props = { ...rest, heads: HEADS.filter((x) => next.has(x)) } as TableProps;
86
- }
87
- const HEAD_TOGGLES: { head: Head; icon: typeof PanelTop; label: string }[] = [
88
- { head: 'top', icon: PanelTop, label: 'Header row' },
89
- { head: 'bottom', icon: PanelBottom, label: 'Footer row (e.g. sums)' }
90
- ];
91
-
92
79
  /* Dragging a row or a column by its grip: `to` is the slot it would go to (0…count), shown as a blue line. */
93
80
  let drag = $state<{ kind: 'row' | 'col'; from: number; to: number } | null>(null);
94
81
 
@@ -124,7 +111,14 @@
124
111
  };
125
112
  addEventListener('pointermove', move);
126
113
  addEventListener('pointerup', up);
114
+ stopDrag = () => {
115
+ removeEventListener('pointermove', move);
116
+ removeEventListener('pointerup', up);
117
+ };
127
118
  }
119
+ // A table removed in the middle of a drag leaves no listener behind.
120
+ let stopDrag = () => {};
121
+ $effect(() => () => stopDrag());
128
122
  /** The drop line of a row or column: before it, after it (the last one), or none. */
129
123
  function dropMark(kind: 'row' | 'col', i: number, count: number) {
130
124
  if (drag?.kind !== kind || drag.to === drag.from || drag.to === drag.from + 1) return null;
@@ -165,9 +159,38 @@
165
159
  }
166
160
  const setAlign = (c: number, a: Align) => set({ align: align.map((x, j) => (j === c ? a : x)) });
167
161
 
162
+ /**
163
+ * Wraps the selected text of a cell in a mark (Ctrl+B and the like); then selects the text again, or, for a link,
164
+ * the address to type over.
165
+ */
166
+ async function mark(t: HTMLTextAreaElement, r: number, c: number, open: string, close: string, link = false) {
167
+ const { selectionStart: a, selectionEnd: b, value } = t;
168
+ const text = value.slice(a, b);
169
+ edit(r, c, value.slice(0, a) + open + text + close + value.slice(b));
170
+ await tick();
171
+ const cell = root.querySelector<HTMLTextAreaElement>(`textarea[data-cell="${r}:${c}"]`);
172
+ if (!cell) return;
173
+ cell.focus();
174
+ if (link) cell.setSelectionRange(a + open.length + text.length + 2, a + open.length + text.length + close.length - 1);
175
+ else cell.setSelectionRange(a + open.length, a + open.length + text.length);
176
+ }
177
+ const MARKS: Record<string, [string, string]> = { b: ['**', '**'], i: ['*', '*'], e: ['`', '`'] };
178
+
168
179
  function onKey(e: KeyboardEvent, r: number, c: number) {
169
180
  const t = e.currentTarget as HTMLTextAreaElement;
170
181
  const last = r === rows.length - 1;
182
+ const k = e.key.toLowerCase();
183
+ if ((e.ctrlKey || e.metaKey) && !e.altKey) {
184
+ const m = e.shiftKey ? (k === 'x' ? (['~~', '~~'] as [string, string]) : null) : MARKS[k];
185
+ if (m) {
186
+ e.preventDefault();
187
+ return void mark(t, r, c, m[0], m[1]);
188
+ }
189
+ if (k === 'k' && !e.shiftKey) {
190
+ e.preventDefault();
191
+ return void mark(t, r, c, '[', '](https://)', true);
192
+ }
193
+ }
171
194
  if (e.key === 'Enter' && !e.shiftKey) {
172
195
  // Enter goes down a row (a new one after the last); Shift+Enter breaks the line inside the cell.
173
196
  e.preventDefault();
@@ -213,6 +236,18 @@
213
236
  return { update: fit, destroy: () => node.removeEventListener('input', fit) };
214
237
  }
215
238
 
239
+ // The toolbar's look (Tailwind; whole class names, so Tailwind finds them): segments of small buttons.
240
+ const SEG = 'inline-flex items-center gap-px rounded-lg bg-muted p-0.5';
241
+ const LABEL = 'inline-grid place-items-center pr-1 pl-[5px] text-muted-contrast';
242
+ const TOOL =
243
+ 'inline-grid h-6 min-w-[26px] cursor-pointer place-items-center rounded-md border-0 bg-transparent px-[5px] text-surface-contrast enabled:hover:bg-surface enabled:hover:shadow-[0_1px_2px_rgb(0_0_0/0.08)] disabled:cursor-default disabled:opacity-30';
244
+ const TOOL_ON =
245
+ 'inline-grid h-6 min-w-[26px] cursor-pointer place-items-center rounded-md border-0 bg-surface px-[5px] text-accent shadow-[0_1px_2px_rgb(0_0_0/0.08)]';
246
+ const DANGER = `${TOOL} enabled:hover:text-[#dc2626]`;
247
+ const ADD = $derived(
248
+ `grid cursor-pointer place-items-center rounded-md border-0 bg-muted p-0 text-muted-contrast transition-[opacity,background] duration-[120ms] hover:bg-accent/12 hover:text-accent ${at ? 'opacity-100' : 'opacity-0 group-hover/table:opacity-100'}`
249
+ );
250
+
216
251
  const ALIGNS: { value: Align; icon: typeof TextAlignStart; label: string }[] = [
217
252
  { value: 'left', icon: TextAlignStart, label: 'Align column left' },
218
253
  { value: 'center', icon: TextAlignCenter, label: 'Center column' },
@@ -221,46 +256,42 @@
221
256
  </script>
222
257
 
223
258
  <div
224
- class="table"
225
- class:editing={!!at}
259
+ class="table-editor group/table text-[15px] leading-[1.45]"
226
260
  bind:this={root}
227
261
  onfocusout={(e) => {
228
262
  if (!root.contains(e.relatedTarget as Node)) at = null;
229
263
  }}
230
264
  >
231
265
  {#if !readonly}
232
- <div class="bar" role="toolbar" aria-label={tr('Table')}>
266
+ <!-- The row and column tools of the cell being edited; when it wraps, it pushes the table down. -->
267
+ <div class="mb-2 flex min-h-7 flex-wrap items-center gap-1.5" role="toolbar" aria-label={tr('Table')}>
233
268
  {#if at}
234
269
  {@const { r, c } = at}
235
- <div class="seg">
236
- <span class="label" title={tr('Row')}><Rows3 size={14} /></span>
237
- <button type="button" title={tr('Insert a row above')} onmousedown={(e) => e.preventDefault()} onclick={() => { insertRow(r); focusCell(r, c); }}><BetweenHorizontalStart size={15} /></button>
238
- <button type="button" title={tr('Insert a row below')} onmousedown={(e) => e.preventDefault()} onclick={() => { insertRow(r + 1); focusCell(r + 1, c); }}><BetweenHorizontalEnd size={15} /></button>
239
- <button type="button" class="danger" title={tr('Delete this row')} disabled={rows.length < 2} onclick={() => dropRow(r)}><Trash2 size={14} /></button>
270
+ <div class={SEG}>
271
+ <span class={LABEL} title={tr('Row')}><Rows3 size={14} /></span>
272
+ <button type="button" class={TOOL} title={tr('Insert a row above')} onmousedown={(e) => e.preventDefault()} onclick={() => { insertRow(r); focusCell(r, c); }}><BetweenHorizontalStart size={15} /></button>
273
+ <button type="button" class={TOOL} title={tr('Insert a row below')} onmousedown={(e) => e.preventDefault()} onclick={() => { insertRow(r + 1); focusCell(r + 1, c); }}><BetweenHorizontalEnd size={15} /></button>
274
+ <button type="button" class={DANGER} title={tr('Delete this row')} disabled={rows.length < 2} onclick={() => dropRow(r)}><Trash2 size={14} /></button>
240
275
  </div>
241
- <div class="seg">
242
- <span class="label" title={tr('Column')}><Columns3 size={14} /></span>
243
- <button type="button" title={tr('Insert a column left')} onmousedown={(e) => e.preventDefault()} onclick={() => { insertCol(c); focusCell(r, c); }}><BetweenVerticalStart size={15} /></button>
244
- <button type="button" title={tr('Insert a column right')} onmousedown={(e) => e.preventDefault()} onclick={() => { insertCol(c + 1); focusCell(r, c + 1); }}><BetweenVerticalEnd size={15} /></button>
245
- <button type="button" class="danger" title={tr('Delete this column')} disabled={width < 2} onclick={() => dropCol(c)}><Trash2 size={14} /></button>
276
+ <div class={SEG}>
277
+ <span class={LABEL} title={tr('Column')}><Columns3 size={14} /></span>
278
+ <button type="button" class={TOOL} title={tr('Insert a column left')} onmousedown={(e) => e.preventDefault()} onclick={() => { insertCol(c); focusCell(r, c); }}><BetweenVerticalStart size={15} /></button>
279
+ <button type="button" class={TOOL} title={tr('Insert a column right')} onmousedown={(e) => e.preventDefault()} onclick={() => { insertCol(c + 1); focusCell(r, c + 1); }}><BetweenVerticalEnd size={15} /></button>
280
+ <button type="button" class={DANGER} title={tr('Delete this column')} disabled={width < 2} onclick={() => dropCol(c)}><Trash2 size={14} /></button>
246
281
  </div>
247
- <div class="seg">
282
+ <div class={SEG}>
248
283
  {#each ALIGNS as a (a.value)}
249
- <button type="button" title={tr(a.label)} class:on={align[c] === a.value} onmousedown={(e) => e.preventDefault()} onclick={() => setAlign(c, a.value)}><a.icon size={15} /></button>
284
+ <button type="button" class={align[c] === a.value ? TOOL_ON : TOOL} title={tr(a.label)} onmousedown={(e) => e.preventDefault()} onclick={() => setAlign(c, a.value)}><a.icon size={15} /></button>
250
285
  {/each}
251
286
  </div>
252
287
  {:else}
253
- <span class="hint" title={tr('Enter: next row · Shift+Enter: line break · paste from a spreadsheet')}><Info size={14} /></span>
288
+ <span class="text-[12px] text-muted-contrast">{tr('Click a cell to edit its row and column')}</span>
254
289
  {/if}
255
- <div class="seg heads">
256
- {#each HEAD_TOGGLES as h (h.head)}
257
- <button type="button" title={tr(h.label)} aria-pressed={heads.has(h.head)} class:on={heads.has(h.head)} onmousedown={(e) => e.preventDefault()} onclick={() => toggleHead(h.head)}><h.icon size={15} /></button>
258
- {/each}
259
- </div>
260
290
  </div>
261
291
  {/if}
262
- <div class="grid">
263
- <div class="frame">
292
+ <!-- The table, with a + bar on the right (column) and below (row) that shows on hover. -->
293
+ <div class="grid grid-cols-[minmax(0,1fr)_18px] grid-rows-[auto_18px] gap-1">
294
+ <div class="overflow-x-auto rounded-[10px] border border-frame bg-surface">
264
295
  <table class:dragging={!!drag}>
265
296
  {#if !readonly}
266
297
  <!-- the column grips -->
@@ -284,12 +315,16 @@
284
315
  </td>
285
316
  {/if}
286
317
  {#each row as cell, c (c)}
318
+ {@const editing = at?.r === r && at?.c === c}
287
319
  <td
288
320
  class={dropMark('col', c, width) ? 'c-' + dropMark('col', c, width) : ''}
289
321
  class:col-on={at?.c === c}
290
322
  class:moving={drag?.kind === 'col' && drag.from === c}
291
323
  >
324
+ <!-- Out of editing a cell shows its text formatted (sizing the cell, over its textarea); editing shows the
325
+ markdown. -->
292
326
  <textarea
327
+ class:hidden-text={!editing && !!cell.trim()}
293
328
  rows="1"
294
329
  value={cell}
295
330
  {readonly}
@@ -303,6 +338,9 @@
303
338
  onkeydown={(e) => onKey(e, r, c)}
304
339
  onpaste={(e) => onPaste(e, r, c)}
305
340
  ></textarea>
341
+ {#if !editing && cell.trim()}
342
+ <div class="shown" style:text-align={align[c]} aria-hidden="true">{@html inline(cell)}</div>
343
+ {/if}
306
344
  </td>
307
345
  {/each}
308
346
  </tr>
@@ -311,253 +349,166 @@
311
349
  </table>
312
350
  </div>
313
351
  {#if !readonly}
314
- <button type="button" class="add-col" title={tr('Add a column')} aria-label={tr('Add a column')} onclick={() => { insertCol(width); focusCell(0, width); }}><Plus size={14} /></button>
315
- <button type="button" class="add-row" title={tr('Add a row')} aria-label={tr('Add a row')} onclick={() => { insertRow(rows.length); focusCell(rows.length, 0); }}><Plus size={14} /></button>
352
+ <button type="button" class={ADD} title={tr('Add a column')} aria-label={tr('Add a column')} onclick={() => { insertCol(width); focusCell(0, width); }}><Plus size={14} /></button>
353
+ <button type="button" class="col-start-1 {ADD}" title={tr('Add a row')} aria-label={tr('Add a row')} onclick={() => { insertRow(rows.length); focusCell(rows.length, 0); }}><Plus size={14} /></button>
316
354
  {/if}
317
355
  </div>
318
356
  </div>
319
357
 
320
- <style>
321
- .table {
322
- font: 15px/1.45 system-ui, -apple-system, 'Segoe UI', sans-serif;
323
- }
324
- /* Beside the tag and the […] button in the corner; when it wraps, it pushes the table down. */
325
- .bar {
326
- margin: 0 9em 8px 0;
327
- display: flex;
328
- flex-wrap: wrap;
329
- align-items: center;
330
- gap: 6px;
331
- min-height: 28px;
332
- }
333
- .seg {
334
- display: inline-flex;
335
- align-items: center;
336
- gap: 1px;
337
- padding: 2px;
338
- border-radius: 8px;
339
- background: #f4f4f6;
340
- }
341
- .label {
342
- display: inline-grid;
343
- place-items: center;
344
- padding: 0 4px 0 5px;
345
- color: #a0a0a8;
346
- }
347
- .bar button {
348
- display: inline-grid;
349
- place-items: center;
350
- height: 24px;
351
- min-width: 26px;
352
- padding: 0 5px;
353
- border: 0;
354
- border-radius: 6px;
355
- background: none;
356
- color: #4b4b55;
357
- cursor: pointer;
358
- }
359
- .bar button:hover:not(:disabled) {
360
- background: #fff;
361
- color: #1d1d1f;
362
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
363
- }
364
- .bar button.on {
365
- background: #fff;
366
- color: #2563eb;
367
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
368
- }
369
- .bar button.danger:hover:not(:disabled) {
370
- color: #dc2626;
371
- }
372
- .bar button:disabled {
373
- opacity: 0.3;
374
- cursor: default;
375
- }
376
- .bar .toggle {
377
- display: inline-flex;
378
- gap: 5px;
379
- height: 28px;
380
- padding: 0 10px;
381
- border: 1px solid #e5e5ea;
382
- border-radius: 8px;
383
- font-size: 12px;
384
- font-weight: 500;
385
- }
386
- .bar .toggle.on {
387
- border-color: #bfdbfe;
388
- background: #eff6ff;
389
- box-shadow: none;
390
- }
391
- .hint {
392
- display: inline-grid;
393
- place-items: center;
394
- color: #a0a0a8;
395
- cursor: help;
396
- opacity: 0;
397
- transition: opacity 0.12s;
398
- }
399
- .table:hover .hint {
400
- opacity: 1;
401
- }
358
+ <style>table {
359
+ width: 100%;
360
+ border-collapse: collapse;
361
+ }
362
+ table.dragging, table.dragging * {
363
+ cursor: grabbing !important;
364
+ user-select: none;
365
+ }
402
366
 
403
- /* The table, with a + bar on the right (column) and below (row) that shows on hover. */
404
- .grid {
405
- display: grid;
406
- grid-template-columns: minmax(0, 1fr) 18px;
407
- grid-template-rows: auto 18px;
408
- gap: 4px;
409
- }
410
- .frame {
411
- overflow-x: auto;
412
- border: 1px solid #e3e3e8;
413
- border-radius: 10px;
414
- background: #fff;
415
- }
416
- table {
417
- width: 100%;
418
- border-collapse: collapse;
419
- }
420
- /* The 1px height lets the editor inside fill the whole cell (height: 100% of the row's tallest cell). */
421
- td {
422
- height: 1px;
423
- padding: 0;
424
- border-right: 1px solid #ececf0;
425
- border-bottom: 1px solid #ececf0;
426
- vertical-align: top;
427
- transition: background 0.1s;
428
- }
429
- td:last-child {
430
- border-right: 0;
431
- }
432
- tr:last-child td {
433
- border-bottom: 0;
434
- }
435
- tr.row-on td,
436
- td.col-on {
437
- background: #f8faff;
438
- }
439
- /* The header and the footer as on the site: a band above, the sums bold on grey behind a thicker line. */
440
- tr.h-top td:not(.rgrip) {
441
- background: #f1f1f4;
442
- border-bottom: 2px solid #d8d8de;
443
- }
444
- tr.h-bottom td:not(.rgrip) {
445
- background: #f1f1f4;
446
- border-top: 2px solid #d8d8de;
447
- }
448
- textarea {
449
- display: block;
450
- width: 100%;
451
- height: 100%;
452
- min-width: 6em;
453
- box-sizing: border-box;
454
- padding: 8px 11px;
455
- border: 0;
456
- border-radius: 0;
457
- background: none;
458
- resize: none;
459
- font: inherit;
460
- color: inherit;
461
- outline: none;
462
- }
463
- textarea::placeholder {
464
- color: #b8b8c0;
465
- }
466
- textarea:focus {
467
- background: #fff;
468
- box-shadow: inset 0 0 0 2px #3b82f6;
469
- }
470
- tr.h-top textarea {
471
- font-weight: 600;
472
- font-size: 13px;
473
- letter-spacing: 0.04em;
474
- text-transform: uppercase;
475
- color: #3a3a44;
476
- }
477
- tr.h-bottom textarea {
478
- font-weight: 600;
479
- color: #1d1d1f;
480
- }
481
- /* grips: a thin row above the columns and a thin column before the rows, shown on hover */
482
- td.corner,
483
- td.rgrip {
484
- width: 14px;
485
- border-right: 1px solid #ececf0;
486
- }
487
- tr.grips td {
488
- height: 14px;
489
- border-bottom: 1px solid #ececf0;
490
- }
491
- .grip {
492
- display: grid;
493
- place-items: center;
494
- width: 100%;
495
- height: 100%;
496
- min-height: 14px;
497
- padding: 0;
498
- border: 0;
499
- background: none;
500
- color: #b0b0b8;
501
- cursor: grab;
502
- opacity: 0;
503
- touch-action: none;
504
- transition: opacity 0.12s;
505
- }
506
- .table:hover .grip,
507
- table.dragging .grip {
508
- opacity: 1;
509
- }
510
- .grip:hover {
511
- color: #2563eb;
512
- background: #eef3fd;
513
- }
514
- table.dragging,
515
- table.dragging * {
516
- cursor: grabbing !important;
517
- user-select: none;
518
- }
519
- .moving {
520
- opacity: 0.45;
521
- }
522
- tr.before td {
523
- box-shadow: inset 0 2px 0 #3b82f6;
524
- }
525
- tr.after td {
526
- box-shadow: inset 0 -2px 0 #3b82f6;
527
- }
528
- td.before,
529
- td.c-before {
530
- box-shadow: inset 2px 0 0 #3b82f6;
531
- }
532
- td.after,
533
- td.c-after {
534
- box-shadow: inset -2px 0 0 #3b82f6;
535
- }
536
- .add-col,
537
- .add-row {
538
- display: grid;
539
- place-items: center;
540
- padding: 0;
541
- border: 0;
542
- border-radius: 6px;
543
- background: #f4f4f6;
544
- color: #8a8a94;
545
- cursor: pointer;
546
- opacity: 0;
547
- transition: opacity 0.12s, background 0.12s;
548
- }
549
- .add-row {
550
- grid-column: 1;
551
- }
552
- .table:hover .add-col,
553
- .table:hover .add-row,
554
- .table.editing .add-col,
555
- .table.editing .add-row {
556
- opacity: 1;
557
- }
558
- .add-col:hover,
559
- .add-row:hover {
560
- background: #e8eefc;
561
- color: #2563eb;
562
- }
563
- </style>
367
+ td {
368
+ height: 1px;
369
+ padding: 0;
370
+ border-right: 1px solid var(--color-frame, #ececf0);
371
+ border-bottom: 1px solid var(--color-frame, #ececf0);
372
+ vertical-align: top;
373
+ transition: background 0.1s;
374
+ }
375
+ td:last-child {
376
+ border-right: 0;
377
+ }
378
+
379
+ tr:last-child td {
380
+ border-bottom: 0;
381
+ }
382
+ tr.row-on td,
383
+ tr td.col-on {
384
+ background: color-mix(in srgb, var(--color-accent, #3b82f6) 4%, transparent);
385
+ }
386
+ tr.h-top td:not(.rgrip) {
387
+ background: var(--color-muted, #f1f1f4);
388
+ border-bottom: 2px solid var(--color-frame, #d8d8de);
389
+ }
390
+ tr.h-bottom td:not(.rgrip) {
391
+ background: var(--color-muted, #f1f1f4);
392
+ border-top: 2px solid var(--color-frame, #d8d8de);
393
+ }
394
+ tr.h-top textarea, tr.h-top .shown {
395
+ font-weight: 600;
396
+ font-size: 13px;
397
+ letter-spacing: 0.04em;
398
+ text-transform: uppercase;
399
+ color: var(--color-surface-contrast, #3a3a44);
400
+ }
401
+ tr.h-bottom textarea, tr.h-bottom .shown {
402
+ font-weight: 600;
403
+ color: var(--color-surface-contrast, #1d1d1f);
404
+ }
405
+
406
+ td {
407
+ position: relative;
408
+ }
409
+
410
+ textarea {
411
+ display: block;
412
+ width: 100%;
413
+ height: 100%;
414
+ min-width: 6em;
415
+ box-sizing: border-box;
416
+ padding: 8px 11px;
417
+ border: 0;
418
+ border-radius: 0;
419
+ background: none;
420
+ resize: none;
421
+ font: inherit;
422
+ color: inherit;
423
+ outline: none;
424
+ }
425
+ textarea::placeholder {
426
+ color: var(--color-muted-contrast, #b8b8c0);
427
+ }
428
+ textarea:focus {
429
+ background: var(--color-surface, #fff);
430
+ box-shadow: inset 0 0 0 2px var(--color-accent, #3b82f6);
431
+ }
432
+ textarea.hidden-text {
433
+ position: absolute;
434
+ inset: 0;
435
+ min-height: 0 !important;
436
+ color: transparent;
437
+ }
438
+
439
+ .shown {
440
+ position: relative;
441
+ box-sizing: border-box;
442
+ min-width: 6em;
443
+ padding: 8px 11px;
444
+ overflow: hidden;
445
+ pointer-events: none;
446
+ white-space: pre-wrap;
447
+ word-break: break-word;
448
+ }
449
+ .shown :global(code) {
450
+ padding: 0.1em 0.35em;
451
+ border-radius: 4px;
452
+ background: rgba(120, 120, 140, 0.14);
453
+ font: 0.88em ui-monospace, SFMono-Regular, Menlo, monospace;
454
+ }
455
+ .shown :global(a) {
456
+ color: var(--color-accent, #3b82f6);
457
+ text-decoration: underline;
458
+ }
459
+
460
+ td.corner,
461
+ td.rgrip {
462
+ width: 14px;
463
+ border-right: 1px solid var(--color-frame, #ececf0);
464
+ }
465
+
466
+ tr.grips td {
467
+ height: 14px;
468
+ border-bottom: 1px solid var(--color-frame, #ececf0);
469
+ }
470
+
471
+ .grip {
472
+ display: grid;
473
+ place-items: center;
474
+ width: 100%;
475
+ height: 100%;
476
+ min-height: 14px;
477
+ padding: 0;
478
+ border: 0;
479
+ background: none;
480
+ color: var(--color-muted-contrast, #b0b0b8);
481
+ cursor: grab;
482
+ opacity: 0;
483
+ touch-action: none;
484
+ transition: opacity 0.12s;
485
+ }
486
+ .table-editor:hover .grip, table.dragging .grip {
487
+ opacity: 1;
488
+ }
489
+ .grip:hover {
490
+ color: var(--color-accent, #3b82f6);
491
+ background: color-mix(in srgb, var(--color-accent, #3b82f6) 9%, transparent);
492
+ }
493
+
494
+ .moving {
495
+ opacity: 0.45;
496
+ }
497
+
498
+ tr.before td {
499
+ box-shadow: inset 0 2px 0 var(--color-accent, #3b82f6);
500
+ }
501
+
502
+ tr.after td {
503
+ box-shadow: inset 0 -2px 0 var(--color-accent, #3b82f6);
504
+ }
505
+
506
+ td.before,
507
+ td.c-before {
508
+ box-shadow: inset 2px 0 0 var(--color-accent, #3b82f6);
509
+ }
510
+
511
+ td.after,
512
+ td.c-after {
513
+ box-shadow: inset -2px 0 0 var(--color-accent, #3b82f6);
514
+ }</style>