@signal9/era-ui 6.0.0 → 8.0.0

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.
@@ -61,5 +61,4 @@
61
61
  'field-sizing-content max-h-48 min-h-(--era-h-md) w-full resize-none bg-transparent px-(--era-inset-md) py-[calc((var(--era-h-md)-1lh)/2)] text-body leading-body text-fg outline-none placeholder:text-muted',
62
62
  className
63
63
  )}
64
- {...restProps}
65
- ></textarea>
64
+ {...restProps}></textarea>
@@ -6,6 +6,10 @@
6
6
  let {
7
7
  streaming = false,
8
8
  open = $bindable(false),
9
+ // A $props() default is read by the TEMPLATE, which eslint's scope
10
+ // analysis does not model — it sees only the effect below reassigning
11
+ // this and calls the default dead.
12
+ // eslint-disable-next-line no-useless-assignment
9
13
  duration = $bindable(0),
10
14
  children,
11
15
  class: className
@@ -18,6 +18,9 @@
18
18
  import { untrack } from 'svelte';
19
19
  import { Editor, type JSONContent } from '@tiptap/core';
20
20
  import { noteExtensions } from './editor/extensions.js';
21
+ // Side-effect import: mounting the editor should not require the consumer to
22
+ // remember a stylesheet. Also published as `@signal9/era-ui/styles/prose`.
23
+ import '../../styles/prose.css';
21
24
 
22
25
  interface Props {
23
26
  /** Initial document. Later changes are ignored — re-key to reseed. */
@@ -167,367 +170,7 @@
167
170
  <!-- eslint-disable better-tailwindcss/no-unknown-classes -- era-notes-prose is defined in this component's scoped stylesheet -->
168
171
  <div class="era-notes-prose min-w-0 {className ?? ''}" {@attach initEditor}></div>
169
172
 
170
- <style>
171
- /*
172
- * TipTap renders headless: the editable element it creates carries `.tiptap`,
173
- * so every rule here is scoped to that inside this component's wrapper.
174
- *
175
- * Vertical rhythm is ONE value — --era-gap, the library's inter-element gap —
176
- * on every block's bottom margin. A document then breathes at exactly the
177
- * pace the surrounding chrome does, and loosens with density along with it.
178
- *
179
- * Font family is deliberately absent: the [data-font] axis sets it globally
180
- * (with !important, by design), so anything declared here would either lose
181
- * or break the axis promise.
182
- */
183
- .era-notes-prose :global(.tiptap) {
184
- outline: none;
185
- min-height: 100%;
186
- font-size: var(--era-text);
187
- line-height: 1.6;
188
- color: var(--color-fg);
189
-
190
- /*
191
- * The first line's half-leading is not padding, but it reads as padding.
192
- *
193
- * line-height 1.6 puts ~8px of empty box above the first glyph's cap, on
194
- * top of the container's 8px inset — so the document opened 16px from the
195
- * top and 8px from the left, an asymmetry that grows with the first
196
- * block's font size (a note starting with a heading was the worst case).
197
- *
198
- * Trimming the block-start to the cap edge makes the ink itself sit on the
199
- * container's inset, so the first thing you read has the same space above
200
- * it as beside it, whatever block it happens to be. This is the block half
201
- * of what era-text-trim does for single-line controls; only the START is
202
- * trimmed, because the editor scrolls and trimming the end would put the
203
- * last line's descenders past the scrollable area.
204
- *
205
- * Firefox has no text-box yet and keeps the untrimmed leading — the same
206
- * graceful degradation era-text-trim already ships with.
207
- */
208
- text-box-trim: trim-start;
209
- text-box-edge: cap alphabetic;
210
- }
211
-
212
- /* The optical half of the trim. cap trimming is EXACT — the cap sits on the
213
- * container inset to the pixel — but the LEFT side isn't: glyphs start with
214
- * their side bearing (~0.07em of ink-free space inside the line box), so a
215
- * geometrically-equal corner read top-tight. The first block gets that same
216
- * allowance back as margin, in em so a heading's compensation scales with
217
- * the heading — the corner then MEASURES slightly uneven and finally LOOKS
218
- * even, which is the order optics always wins in.
219
- *
220
- * 0.22em, not the ~0.07em side-bearing alone: a first line is usually a
221
- * heavy cap or a heading, and dense ink right at a boundary needs more
222
- * room than a light vertical stem to feel equally far from it — the same
223
- * reason display type sets its top margin above geometric equality. Tuned
224
- * by eye against the left inset. */
225
- .era-notes-prose :global(.tiptap > :first-child) {
226
- margin-top: 0.22em;
227
- }
228
-
229
- /* FALLBACK for engines without text-box (Firefox): the trim above silently
230
- * disappears there, and the first line opened ~0.47em too low (measured:
231
- * cap 16.4px from the top against 8px from the left, dense/mono) — the
232
- * exact asymmetry the trim exists to remove. Approximate the same cut with
233
- * a negative first-block margin: -0.45em is the first line's half-leading
234
- * (0.3em at line-height 1.6) plus the ascent-above-cap of the ui stacks
235
- * (~0.15-0.17em). In em on purpose — it scales with the FIRST BLOCK's own
236
- * size, so a note opening on an h1 and one opening on a paragraph both
237
- * land their cap on the container inset, which one padding value could
238
- * never do for both (the reference terminal app's pt-1 is this same idea,
239
- * tuned only for paragraphs). The margin collapses up through .tiptap and
240
- * stops at the scroll container's padding, which is where it should act.
241
- * Within ~1px of the trimmed geometry; engines with text-box never match
242
- * this block and keep the exact version. */
243
- @supports not ((text-box-trim: trim-start) and (text-box-edge: cap alphabetic)) {
244
- .era-notes-prose :global(.tiptap > :first-child) {
245
- /* -0.45em of untrimmed leading, plus the same +0.22em optical
246
- * allowance the trimmed path adds above. */
247
- margin-top: -0.23em;
248
- }
249
- }
250
-
251
- /* Placeholder, via @tiptap/extension-placeholder */
252
- .era-notes-prose :global(.tiptap p.is-editor-empty:first-child::before) {
253
- content: attr(data-placeholder);
254
- float: left;
255
- height: 0;
256
- color: var(--color-muted);
257
- pointer-events: none;
258
- }
259
-
260
- /* Typography */
261
- .era-notes-prose :global(.tiptap p) {
262
- margin: 0 0 var(--era-gap);
263
- }
264
- .era-notes-prose :global(.tiptap p:last-child) {
265
- margin-bottom: 0;
266
- }
267
- .era-notes-prose :global(.tiptap h1) {
268
- font-size: 1.5em;
269
- }
270
- .era-notes-prose :global(.tiptap h2) {
271
- font-size: 1.25em;
272
- }
273
- .era-notes-prose :global(.tiptap h3) {
274
- font-size: 1.1em;
275
- }
276
- .era-notes-prose :global(.tiptap h1),
277
- .era-notes-prose :global(.tiptap h2),
278
- .era-notes-prose :global(.tiptap h3) {
279
- margin: 0 0 var(--era-gap);
280
- font-weight: 600;
281
- line-height: 1.6;
282
- color: var(--color-bright);
283
- }
284
- .era-notes-prose :global(.tiptap strong) {
285
- font-weight: 600;
286
- color: var(--color-bright);
287
- }
288
- .era-notes-prose :global(.tiptap em) {
289
- font-style: italic;
290
- }
291
- .era-notes-prose :global(.tiptap s) {
292
- text-decoration: line-through;
293
- text-decoration-thickness: 0.5px;
294
- }
295
-
296
- /* Code — inline chips take the xxs radius, blocks the lg one a Card uses. */
297
- .era-notes-prose :global(.tiptap code) {
298
- padding: 1px var(--era-inset-xxs);
299
- border-radius: var(--era-rd-xxs);
300
- background: var(--era-surface-bg-elevated);
301
- }
302
- .era-notes-prose :global(.tiptap pre) {
303
- overflow-x: auto;
304
- margin: 0 0 var(--era-gap);
305
- padding: var(--era-inset-md);
306
- border-radius: var(--era-rd-lg);
307
- background: var(--color-surface);
308
- box-shadow: var(--era-shadow);
309
- }
310
- .era-notes-prose :global(.tiptap pre code) {
311
- padding: 0;
312
- border-radius: 0;
313
- background: transparent;
314
- }
315
-
316
- /* Syntax highlighting — the neutral scale, so it themes with everything else. */
317
- .era-notes-prose :global(.tiptap pre .hljs-keyword),
318
- .era-notes-prose :global(.tiptap pre .hljs-built_in) {
319
- color: var(--color-9);
320
- }
321
- .era-notes-prose :global(.tiptap pre .hljs-string),
322
- .era-notes-prose :global(.tiptap pre .hljs-regexp) {
323
- color: var(--color-10);
324
- }
325
- .era-notes-prose :global(.tiptap pre .hljs-number),
326
- .era-notes-prose :global(.tiptap pre .hljs-literal),
327
- .era-notes-prose :global(.tiptap pre .hljs-type),
328
- .era-notes-prose :global(.tiptap pre .hljs-class) {
329
- color: var(--color-11);
330
- }
331
- .era-notes-prose :global(.tiptap pre .hljs-comment) {
332
- color: var(--color-8);
333
- }
334
- .era-notes-prose :global(.tiptap pre .hljs-function),
335
- .era-notes-prose :global(.tiptap pre .hljs-title) {
336
- font-weight: 600;
337
- color: var(--color-9);
338
- }
339
- .era-notes-prose :global(.tiptap pre .hljs-variable),
340
- .era-notes-prose :global(.tiptap pre .hljs-attr) {
341
- color: var(--color-12);
342
- }
343
- .era-notes-prose :global(.tiptap pre .hljs-params) {
344
- color: var(--color-11);
345
- }
346
- .era-notes-prose :global(.tiptap pre .hljs-meta),
347
- .era-notes-prose :global(.tiptap pre .hljs-tag) {
348
- color: var(--color-10);
349
- }
350
-
351
- /* Blockquote */
352
- .era-notes-prose :global(.tiptap blockquote) {
353
- margin: 0 0 var(--era-gap);
354
- padding-left: var(--era-inset-md);
355
- border-left: 2px solid var(--era-divider-color);
356
- color: var(--color-muted);
357
- }
358
-
359
- /* Lists */
360
- .era-notes-prose :global(.tiptap li),
361
- .era-notes-prose :global(.tiptap li p),
362
- .era-notes-prose :global(.tiptap ul),
363
- .era-notes-prose :global(.tiptap ol) {
364
- margin: 0;
365
- }
366
- .era-notes-prose :global(.tiptap ul) {
367
- padding-left: 1.4em;
368
- list-style-type: disc;
369
- }
370
- .era-notes-prose :global(.tiptap ol) {
371
- padding-left: 1.9em;
372
- list-style-type: decimal;
373
- }
374
-
375
- /* Task list — a checkbox styled like era's Checkbox, at the icon tier. */
376
- .era-notes-prose :global(.tiptap .task-list) {
377
- padding-left: 0;
378
- list-style: none;
379
- }
380
- .era-notes-prose :global(.tiptap .task-item) {
381
- display: flex;
382
- gap: var(--era-gap);
383
- }
384
- .era-notes-prose :global(.tiptap .task-item label) {
385
- /* Match the line box so the box centres against the first line of text. */
386
- display: flex;
387
- align-items: center;
388
- height: calc(var(--era-text) * 1.6);
389
- }
390
- .era-notes-prose :global(.tiptap .task-item input[type='checkbox']) {
391
- flex-shrink: 0;
392
- width: calc(var(--era-h-xs) - 4px);
393
- height: calc(var(--era-h-xs) - 4px);
394
- border-radius: var(--era-rd-xs);
395
- background: var(--era-surface-bg-elevated);
396
- box-shadow: var(--era-shadow-well);
397
- appearance: none;
398
- cursor: pointer;
399
- transition:
400
- background var(--era-duration) var(--era-ease),
401
- box-shadow var(--era-duration) var(--era-ease);
402
- }
403
- .era-notes-prose :global(.tiptap .task-item input[type='checkbox']:hover) {
404
- background: var(--era-highlight);
405
- }
406
- .era-notes-prose :global(.tiptap .task-item input[type='checkbox']:checked) {
407
- background: var(--color-primary);
408
- box-shadow: var(--era-shadow-pressed);
409
- }
410
- .era-notes-prose :global(.tiptap .task-item[data-checked='true'] > div > p) {
411
- color: var(--color-muted);
412
- text-decoration: line-through;
413
- }
414
-
415
- /* Horizontal rule */
416
- .era-notes-prose :global(.tiptap hr) {
417
- margin: var(--era-gap) 0;
418
- border: none;
419
- border-top: 1px solid var(--era-divider-color);
420
- }
421
-
422
- /* Links — the era-link recipe, inlined because ProseMirror owns this DOM. */
423
- .era-notes-prose :global(.tiptap a) {
424
- color: var(--color-link);
425
- text-decoration-line: underline;
426
- text-decoration-color: color-mix(in oklch, currentColor 30%, transparent);
427
- text-underline-offset: 0.2em;
428
- cursor: pointer;
429
- transition: text-decoration-color var(--era-duration) var(--era-ease);
430
- }
431
- .era-notes-prose :global(.tiptap a:hover) {
432
- text-decoration-color: currentColor;
433
- }
434
-
435
- /* Highlight — the warning token PAIR, not a wash of it.
436
- *
437
- * This was a 25% warning tint with `color: inherit`, which is two problems at
438
- * once: at 25% the mark barely separates from the page, and pushing the alpha
439
- * up to fix that puts light body text on yellow, which is worse. The pair
440
- * exists for exactly this — warning-fg is dark in EVERY mode by design
441
- * (see themes.css: yellow cannot carry light text), so a solid fill reads as
442
- * a real highlighter and the text on it stays legible in both modes. */
443
- .era-notes-prose :global(.tiptap mark) {
444
- padding: 1px 0;
445
- background: var(--color-warning);
446
- color: var(--color-warning-fg);
447
- }
448
-
449
- /* Table */
450
- .era-notes-prose :global(.tiptap .note-table) {
451
- width: 100%;
452
- margin: 0 0 var(--era-gap);
453
- border-collapse: collapse;
454
- table-layout: fixed;
455
- }
456
- .era-notes-prose :global(.tiptap .note-table td),
457
- .era-notes-prose :global(.tiptap .note-table th) {
458
- padding: var(--era-inset-sm) var(--era-inset-md);
459
- border: 1px solid var(--era-divider-color);
460
- text-align: left;
461
- vertical-align: top;
462
- }
463
- .era-notes-prose :global(.tiptap .note-table th) {
464
- background: var(--color-surface);
465
- font-weight: 600;
466
- color: var(--color-bright);
467
- }
468
- .era-notes-prose :global(.tiptap .note-table td > p),
469
- .era-notes-prose :global(.tiptap .note-table th > p) {
470
- margin: 0;
471
- }
472
-
473
- /* Details (collapsible) */
474
- .era-notes-prose :global(.tiptap details) {
475
- margin: 0 0 var(--era-gap);
476
- padding: var(--era-inset-sm) var(--era-inset-md);
477
- border-radius: var(--era-rd-md);
478
- background: var(--era-surface-bg-elevated);
479
- box-shadow: var(--era-shadow);
480
- }
481
- .era-notes-prose :global(.tiptap details summary) {
482
- font-weight: 600;
483
- color: var(--color-bright);
484
- cursor: pointer;
485
- }
486
- .era-notes-prose :global(.tiptap details > div) {
487
- margin-top: var(--era-gap);
488
- }
489
-
490
- /* Image */
491
- .era-notes-prose :global(.tiptap img) {
492
- max-width: 100%;
493
- height: auto;
494
- border-radius: var(--era-rd-md);
495
- }
496
-
497
- /*
498
- * ProseMirror ships hardcoded selection colours (#8cf outlines, a black gap
499
- * cursor, rgba(200,200,255,.4) cell selection, #adf resize handles). Each is
500
- * overridden here or the editor shows chrome from a different design system.
501
- */
502
- .era-notes-prose :global(.tiptap .ProseMirror-selectednode) {
503
- outline: 2px solid var(--color-primary);
504
- }
505
- .era-notes-prose :global(.tiptap li.ProseMirror-selectednode) {
506
- outline: none;
507
- }
508
- .era-notes-prose :global(.tiptap li.ProseMirror-selectednode::after) {
509
- border-color: var(--color-primary);
510
- }
511
- .era-notes-prose :global(.ProseMirror-gapcursor::after) {
512
- border-top-color: var(--color-primary);
513
- }
514
- .era-notes-prose :global(.tiptap .selectedCell::after) {
515
- background: var(--era-highlight);
516
- }
517
- .era-notes-prose :global(.tiptap .column-resize-handle) {
518
- background-color: var(--color-primary);
519
- }
520
- /* NO ::selection rule here, deliberately.
521
- *
522
- * era owns text selection as a surface token contract — --era-selection-bg /
523
- * -fg / -shadow (surfaces/base.css), which is how flat gets inverse video,
524
- * glass a glowing wash, and bevel an opaque accent bar. This file used to
525
- * override it with `background: var(--color-4)` and no `color`, which threw
526
- * all of that away AND left body text on a mid-scale grey: the band barely
527
- * separated from the page and the glyphs sat on top of it at whatever
528
- * contrast happened to fall out. Dropping the override restores both the
529
- * contrast (bright fill, --color-1 glyphs) and the per-surface treatment.
530
- *
531
- * The ProseMirror overrides above are still needed — those replace hardcoded
532
- * library chrome that no era token would otherwise reach. */
533
- </style>
173
+ <!-- The document styling lives in ../../styles/prose.css, imported above:
174
+ Svelte would scope a <style> block to this component, which is what made
175
+ era's prose typography unreachable for anyone building their own TipTap
176
+ instance. Same rules, addressable by anyone who sets .era-notes-prose. -->
@@ -1,4 +1,5 @@
1
1
  import { type JSONContent } from '@tiptap/core';
2
+ import '../../styles/prose.css';
2
3
  interface Props {
3
4
  /** Initial document. Later changes are ignored — re-key to reseed. */
4
5
  content: JSONContent;