@panphora/bevel 0.3.2 → 0.4.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.
- package/README.md +4 -0
- package/bevel.css +1 -0
- package/dist/react/theme.css +4 -0
- package/examples/components/manifest.json +6 -1
- package/examples/components/sheet.html +114 -0
- package/integrations/sheet.css +99 -0
- package/package.json +3 -2
- package/vendor/richclay/provenance.json +3 -3
- package/vendor/richclay/richclay.css +24 -0
- package/vendor/richclay/richclay.js +291 -16
package/README.md
CHANGED
|
@@ -111,6 +111,10 @@ New mutating components emit cancelable `bevel:beforechange`, then `bevel:change
|
|
|
111
111
|
|
|
112
112
|
Richclay, Quickcrop and Datastar remain optional. Load their adapters from `integrations/` after `bevel.js`. The Richclay adapter exposes `Bevel.richtext.mount()` and `unmount()`. The Quickcrop adapter exposes `Bevel.crop()`. The Datastar adapter exposes `Bevel.datastar.mount()`, `unmount()`, `control()` and `state()`.
|
|
113
113
|
|
|
114
|
+
The Sheet add-on is CSS only. Load `integrations/sheet.css` after `bevel.css` for a compact toolbar (`.bevel-toolbar--sheet` on a `.bevel-toolbar`), a phone tool strip (`.bevel-sheet-tools`), a spreadsheet grid (`.bevel-sheet-grid`, or saved `<table grid>` markup with `<td n>` for numbers), sheet tabs (`.bevel-sheet-tabs`) and a flat look picker (`.bevel-sheet-looks`). It adds four selection tokens: `--bevel-sel`, `--bevel-sel-fill`, `--bevel-sel-head` and `--bevel-sel-line`. The application positions `.bevel-sheet-grid__selection` and `.bevel-sheet-grid__editor` over the grid. See `examples/components/sheet.html`.
|
|
115
|
+
|
|
116
|
+
The application supplies `table-layout: fixed` and a table width when column widths should clip cell content.
|
|
117
|
+
|
|
114
118
|
Nested Datastar registrations retain their own sessions when an outer registration unmounts. The adapter passes the remaining roots through `Bevel.destroy(root, { preserve: roots })`; those subtrees and their owned rich editors are excluded from teardown. Ordinary `Bevel.destroy(root)` still releases the entire subtree.
|
|
115
119
|
|
|
116
120
|
For Richclay, load its Squire and DOMPurify scripts followed by its `richclay.js` global build before mounting. Bevel reuses externally mounted instances and never destroys them. It owns newly created instances even when the author supplied `contenteditable`. Repeated mounts reuse the instance; unmount releases its owned editor and theme tracking. A missing peer throws and emits `bevel:error`.
|
package/bevel.css
CHANGED
|
@@ -1595,6 +1595,7 @@
|
|
|
1595
1595
|
.bevel-fieldset { border: 1px solid var(--bevel-line); border-radius: 0; padding: 16px 20px 18px; margin: 0; min-width: 0; }
|
|
1596
1596
|
.bevel-fieldset > legend { padding: 0 8px; margin-left: -8px; }
|
|
1597
1597
|
.bevel-form { display: grid; grid-template-columns: 1fr 1fr; gap: 20px 24px; }
|
|
1598
|
+
.bevel-form > .bevel-field + .bevel-field { margin-block-start: 0; }
|
|
1598
1599
|
.bevel-form__full { grid-column: 1 / -1; }
|
|
1599
1600
|
@media (max-width: 640px) { .bevel-form { grid-template-columns: 1fr; } }
|
|
1600
1601
|
.bevel-choices { display: flex; flex-wrap: wrap; gap: 12px 26px; }
|
package/dist/react/theme.css
CHANGED
|
@@ -4047,6 +4047,10 @@ a.bevel-r-file__name:where([data-bevel-appearance="bevel"]) { text-decoration: u
|
|
|
4047
4047
|
|
|
4048
4048
|
|
|
4049
4049
|
|
|
4050
|
+
.bevel-r-form > .bevel-r-field + .bevel-r-field:where([data-bevel-appearance="bevel"]) { margin-block-start: 0; }
|
|
4051
|
+
|
|
4052
|
+
|
|
4053
|
+
|
|
4050
4054
|
.bevel-r-form__full:where([data-bevel-appearance="bevel"]) { grid-column: 1 / -1; }
|
|
4051
4055
|
|
|
4052
4056
|
|
|
@@ -1575,11 +1575,16 @@
|
|
|
1575
1575
|
{
|
|
1576
1576
|
"source": "src/components/split-panes/editor-page.html",
|
|
1577
1577
|
"output": "examples/components/split-panes-editor.html"
|
|
1578
|
+
},
|
|
1579
|
+
{
|
|
1580
|
+
"source": "src/docs/sheet-addon.html",
|
|
1581
|
+
"output": "examples/components/sheet.html"
|
|
1578
1582
|
}
|
|
1579
1583
|
],
|
|
1580
1584
|
"exampleStyles": [
|
|
1581
1585
|
"examples/components/context-hypercms.css",
|
|
1582
|
-
"integrations/quickcrop.css"
|
|
1586
|
+
"integrations/quickcrop.css",
|
|
1587
|
+
"integrations/sheet.css"
|
|
1583
1588
|
],
|
|
1584
1589
|
"build": {
|
|
1585
1590
|
"javascript": [
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<title>Sheet add-on · Bevel</title>
|
|
7
|
+
<link rel="stylesheet" href="../../bevel-fonts.css">
|
|
8
|
+
<link rel="stylesheet" href="../../bevel.css">
|
|
9
|
+
<link rel="stylesheet" href="../../integrations/sheet.css">
|
|
10
|
+
<style>
|
|
11
|
+
.sheet-demo { display: grid; gap: 28px; max-inline-size: 960px; margin-inline: auto; }
|
|
12
|
+
.sheet-demo .bevel-sheet { block-size: 360px; }
|
|
13
|
+
.sheet-demo__phone { inline-size: 360px; max-inline-size: 100%; }
|
|
14
|
+
.sheet-demo__phone .bevel-sheet { block-size: 300px; }
|
|
15
|
+
.sheet-demo pre { overflow-x: auto; }
|
|
16
|
+
</style>
|
|
17
|
+
</head>
|
|
18
|
+
<body>
|
|
19
|
+
<main class="bevel-example">
|
|
20
|
+
<div class="sheet-demo">
|
|
21
|
+
<header class="bevel-example__header">
|
|
22
|
+
<div>
|
|
23
|
+
<h1>Sheet add-on</h1>
|
|
24
|
+
<p>An optional stylesheet for spreadsheet-like tools: a compact toolbar, a grid with headers that stay in place, a selection, sheet tabs and a flat look picker. Link it after <code>bevel.css</code>. It adds no script.</p>
|
|
25
|
+
</div>
|
|
26
|
+
</header>
|
|
27
|
+
|
|
28
|
+
<pre><code><link rel="stylesheet" href="bevel.css">
|
|
29
|
+
<link rel="stylesheet" href="integrations/sheet.css"></code></pre>
|
|
30
|
+
|
|
31
|
+
<section class="bevel-sheet" aria-label="Budget workbook">
|
|
32
|
+
<div class="bevel-toolbar bevel-toolbar--sheet" role="toolbar" aria-label="Formatting">
|
|
33
|
+
<div class="bevel-toolbar__group">
|
|
34
|
+
<button class="bevel-toolbar__icon" type="button" aria-label="Undo"><svg width="14" height="14" viewBox="0 0 256 256" fill="currentColor" aria-hidden="true" focusable="false"><path d="M224,128a96,96,0,0,1-94.71,96H128A95.38,95.38,0,0,1,62.1,197.8a8,8,0,0,1,11-11.63A80,80,0,1,0,71.43,71.39a3.07,3.07,0,0,1-.26.25L44.59,96H72a8,8,0,0,1,0,16H24a8,8,0,0,1-8-8V56a8,8,0,0,1,16,0V85.8L60.25,60A96,96,0,0,1,224,128Z"/></svg></button>
|
|
35
|
+
<button class="bevel-toolbar__icon" type="button" aria-label="Redo"><svg width="14" height="14" viewBox="0 0 256 256" fill="currentColor" aria-hidden="true" focusable="false"><path d="M240,56v48a8,8,0,0,1-8,8H184a8,8,0,0,1,0-16H211.4L184.81,71.64l-.25-.24a80,80,0,1,0-1.67,114.78,8,8,0,0,1,11,11.63A95.44,95.44,0,0,1,128,224h-1.32A96,96,0,1,1,195.75,60L224,85.8V56a8,8,0,1,1,16,0Z"/></svg></button>
|
|
36
|
+
</div>
|
|
37
|
+
<span class="bevel-toolbar__separator" aria-hidden="true"></span>
|
|
38
|
+
<div class="bevel-toolbar__group">
|
|
39
|
+
<button class="bevel-button" type="button" aria-haspopup="menu" aria-expanded="false">Currency <svg class="bevel-sheet-caret" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.18" stroke-linecap="square" stroke-linejoin="miter" aria-hidden="true" focusable="false"><path d="m5 9 7 7 7-7"/></svg></button>
|
|
40
|
+
<button class="bevel-button" type="button" aria-label="Fewer decimals">.0</button>
|
|
41
|
+
<button class="bevel-button" type="button" aria-label="More decimals">.00</button>
|
|
42
|
+
</div>
|
|
43
|
+
<span class="bevel-toolbar__separator" aria-hidden="true"></span>
|
|
44
|
+
<div class="bevel-toolbar__group">
|
|
45
|
+
<button class="bevel-toolbar__icon" type="button" aria-label="Bold" aria-pressed="true"><svg width="14" height="14" viewBox="0 0 256 256" fill="currentColor" aria-hidden="true" focusable="false"><path d="M178.48,115.7A44,44,0,0,0,148,40H80a8,8,0,0,0-8,8V200a8,8,0,0,0,8,8h80a48,48,0,0,0,18.48-92.3ZM88,56h60a28,28,0,0,1,0,56H88Zm72,136H88V128h72a32,32,0,0,1,0,64Z"/></svg></button>
|
|
46
|
+
<button class="bevel-toolbar__icon" type="button" aria-label="Italic" aria-pressed="false"><svg width="14" height="14" viewBox="0 0 256 256" fill="currentColor" aria-hidden="true" focusable="false"><path d="M200,56a8,8,0,0,1-8,8H157.77L115.1,192H144a8,8,0,0,1,0,16H64a8,8,0,0,1,0-16H98.23L140.9,64H112a8,8,0,0,1,0-16h80A8,8,0,0,1,200,56Z"/></svg></button>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<div class="bevel-sheet__viewport">
|
|
51
|
+
<table class="bevel-sheet-grid" id="sheet-demo-budget">
|
|
52
|
+
<colgroup><col style="width:42px"><col style="width:200px"><col style="width:120px"><col style="width:120px"><col style="width:120px"></colgroup>
|
|
53
|
+
<thead>
|
|
54
|
+
<tr><th class="bevel-sheet-grid__coord" scope="col"></th><th class="bevel-sheet-grid__coord" scope="col">A</th><th class="bevel-sheet-grid__coord" scope="col" aria-selected="true">B</th><th class="bevel-sheet-grid__coord" scope="col">C</th><th class="bevel-sheet-grid__coord" scope="col">D</th></tr>
|
|
55
|
+
<tr style="background:var(--bevel-sel-head);font-weight:400;text-align:center;white-space:normal"><th class="bevel-sheet-grid__coord" scope="row">1</th><td>Item</td><td>Planned</td><td>Spent</td><td>Left</td></tr>
|
|
56
|
+
</thead>
|
|
57
|
+
<tbody>
|
|
58
|
+
<tr><th class="bevel-sheet-grid__coord" scope="row">2</th><td>Rent</td><td n>1,200.00</td><td n>1,200.00</td><td n>0.00</td></tr>
|
|
59
|
+
<tr><th class="bevel-sheet-grid__coord" scope="row" aria-selected="true">3</th><td>Groceries</td><td n aria-selected="true">420.00</td><td n>388.15</td><td n>31.85</td></tr>
|
|
60
|
+
<tr><th class="bevel-sheet-grid__coord" scope="row" aria-selected="true">4</th><td>Transport</td><td n aria-selected="true">160.00</td><td n>142.60</td><td n>17.40</td></tr>
|
|
61
|
+
<tr><th class="bevel-sheet-grid__coord" scope="row" aria-selected="true">5</th><td>Utilities</td><td n aria-selected="true">210.00</td><td n>226.30</td><td n>-16.30</td></tr>
|
|
62
|
+
<tr><th class="bevel-sheet-grid__coord" scope="row">6</th><td>Savings</td><td n>500.00</td><td n>500.00</td><td n>0.00</td></tr>
|
|
63
|
+
<tr class="bevel-sheet-grid__total"><th class="bevel-sheet-grid__coord" scope="row">7</th><td>Total</td><td n>2,490.00</td><td n>2,457.05</td><td n>32.95</td></tr>
|
|
64
|
+
</tbody>
|
|
65
|
+
</table>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<div class="bevel-sheet-tabs">
|
|
69
|
+
<div class="bevel-sheet-tabs__list" role="tablist" aria-label="Sheets">
|
|
70
|
+
<button class="bevel-sheet-tabs__tab" type="button" role="tab" aria-selected="true" aria-controls="sheet-demo-budget">Budget</button>
|
|
71
|
+
<button class="bevel-sheet-tabs__tab" type="button" role="tab" aria-selected="false" aria-controls="sheet-demo-budget">Trips</button>
|
|
72
|
+
<button class="bevel-sheet-tabs__add" type="button" aria-label="Add a sheet">+</button>
|
|
73
|
+
</div>
|
|
74
|
+
<div class="bevel-sheet-tabs__status"><span>Sum <strong>790.00</strong></span><span>Count <strong>3</strong></span></div>
|
|
75
|
+
</div>
|
|
76
|
+
</section>
|
|
77
|
+
|
|
78
|
+
<div class="bevel-sheet-looks" role="group" aria-label="Page look">
|
|
79
|
+
<div class="bevel-sheet-looks__group">
|
|
80
|
+
<span class="bevel-sheet-looks__label">Look</span>
|
|
81
|
+
<button class="bevel-sheet-looks__button" type="button" aria-label="Previous look">‹</button>
|
|
82
|
+
<span class="bevel-sheet-looks__current">Ledger</span>
|
|
83
|
+
<button class="bevel-sheet-looks__button" type="button" aria-label="Next look">›</button>
|
|
84
|
+
</div>
|
|
85
|
+
<button class="bevel-sheet-looks__button bevel-sheet-looks__button--confirm" type="button">Use this look</button>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<div class="sheet-demo__phone">
|
|
89
|
+
<section class="bevel-sheet" aria-label="Budget workbook on a phone">
|
|
90
|
+
<div class="bevel-sheet-tools" role="toolbar" aria-label="Cell tools">
|
|
91
|
+
<button class="bevel-sheet-tools__button" type="button" aria-label="Bold" aria-pressed="true"><span class="bevel-sheet-tools__cap">B</span></button>
|
|
92
|
+
<button class="bevel-sheet-tools__button" type="button" aria-label="Italic" aria-pressed="false"><span class="bevel-sheet-tools__cap">I</span></button>
|
|
93
|
+
<button class="bevel-sheet-tools__button" type="button" aria-label="Currency"><span class="bevel-sheet-tools__cap">$</span></button>
|
|
94
|
+
<button class="bevel-sheet-tools__button" type="button" aria-label="Sum"><span class="bevel-sheet-tools__cap">Σ</span></button>
|
|
95
|
+
<button class="bevel-sheet-tools__button" type="button" aria-label="Undo"><span class="bevel-sheet-tools__cap"><svg width="17" height="17" viewBox="0 0 256 256" fill="currentColor" aria-hidden="true" focusable="false"><path d="M224,128a96,96,0,0,1-94.71,96H128A95.38,95.38,0,0,1,62.1,197.8a8,8,0,0,1,11-11.63A80,80,0,1,0,71.43,71.39a3.07,3.07,0,0,1-.26.25L44.59,96H72a8,8,0,0,1,0,16H24a8,8,0,0,1-8-8V56a8,8,0,0,1,16,0V85.8L60.25,60A96,96,0,0,1,224,128Z"/></svg></span></button>
|
|
96
|
+
</div>
|
|
97
|
+
<div class="bevel-sheet__viewport">
|
|
98
|
+
<table grid>
|
|
99
|
+
<thead><tr><td>Item</td><td>Planned</td></tr></thead>
|
|
100
|
+
<tbody>
|
|
101
|
+
<tr><td>Rent</td><td n>1,200.00</td></tr>
|
|
102
|
+
<tr><td>Groceries</td><td n>420.00</td></tr>
|
|
103
|
+
<tr><td>Transport</td><td n>160.00</td></tr>
|
|
104
|
+
</tbody>
|
|
105
|
+
</table>
|
|
106
|
+
</div>
|
|
107
|
+
</section>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
<p>The second grid is plain saved markup, <code><table grid></code> with <code><td n></code> for numbers. It needs no classes, so a file opened with no script still reads as a sheet.</p>
|
|
111
|
+
</div>
|
|
112
|
+
</main>
|
|
113
|
+
</body>
|
|
114
|
+
</html>
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/* Bevel add-on: the Sheet look. Optional. Link it after bevel.css.
|
|
2
|
+
It styles the Sheet template's saved <table grid> markup and plain
|
|
3
|
+
.bevel-sheet-* classes, so other data apps can use the same look.
|
|
4
|
+
The full interactive workbook is the Sheet starter template. */
|
|
5
|
+
@layer base, components;
|
|
6
|
+
|
|
7
|
+
@layer base {
|
|
8
|
+
:root {
|
|
9
|
+
--bevel-sel: light-dark(#1D5B79, #5CA8CC);
|
|
10
|
+
--bevel-sel-fill: light-dark(#E5F0F6, #182B3A);
|
|
11
|
+
--bevel-sel-head: light-dark(#D8E8F1, #1E3548);
|
|
12
|
+
--bevel-sel-line: light-dark(#ADCBE0, #2D4F6C);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@layer components {
|
|
17
|
+
.bevel-sheet { display: flex; flex-direction: column; min-inline-size: 0; border: 1px solid var(--bevel-line-2); background: var(--bevel-surface); color: var(--bevel-ink); font-family: var(--bevel-font-sans); }
|
|
18
|
+
.bevel-sheet__viewport { position: relative; flex: 1; min-block-size: 0; overflow: auto; overscroll-behavior: contain; background: var(--bevel-surface); }
|
|
19
|
+
|
|
20
|
+
.bevel-toolbar--sheet { flex-wrap: nowrap; gap: 4px; min-block-size: 36px; padding: 0 10px; border: 0; border-block-end: 1px solid var(--bevel-line-2); background: var(--bevel-ground); overflow-x: auto; }
|
|
21
|
+
.bevel-toolbar--sheet .bevel-toolbar__group { gap: 3px; }
|
|
22
|
+
.bevel-toolbar--sheet .bevel-toolbar__separator { flex: none; block-size: 18px; margin-inline: 5px; }
|
|
23
|
+
.bevel-toolbar--sheet .bevel-toolbar__icon { inline-size: 26px; block-size: 26px; }
|
|
24
|
+
.bevel-toolbar--sheet .bevel-toolbar__group .bevel-toolbar__icon + .bevel-toolbar__icon { margin-inline-start: 0; }
|
|
25
|
+
.bevel-toolbar--sheet .bevel-button { flex: none; gap: 5px; height: 26px; padding: 0 9px; font-size: 12px; line-height: 1; }
|
|
26
|
+
.bevel-toolbar--sheet .bevel-button[aria-pressed="true"], .bevel-toolbar--sheet .bevel-button[aria-expanded="true"] { border-color: var(--bevel-edge-in) var(--bevel-edge-in-hi) var(--bevel-edge-in-hi) var(--bevel-edge-in); background: var(--bevel-face-in); }
|
|
27
|
+
.bevel-toolbar--sheet svg { flex: none; inline-size: 14px; block-size: 14px; }
|
|
28
|
+
.bevel-toolbar--sheet .bevel-sheet-caret { inline-size: 11px; block-size: 11px; }
|
|
29
|
+
|
|
30
|
+
.bevel-sheet-tools { display: flex; align-items: center; justify-content: space-between; gap: 2px; block-size: 44px; padding: 0 6px; border-block-end: 1px solid var(--bevel-line-2); background: var(--bevel-ground); overflow-x: auto; }
|
|
31
|
+
.bevel-sheet-tools__button { display: inline-grid; flex: none; place-items: center; inline-size: 44px; block-size: 44px; padding: 0; border: 0; background: transparent; color: var(--bevel-ink); cursor: pointer; }
|
|
32
|
+
.bevel-sheet-tools__button:focus-visible { outline: 2px solid var(--bevel-brass); outline-offset: -2px; }
|
|
33
|
+
.bevel-sheet-tools__cap { display: inline-grid; place-items: center; inline-size: 30px; block-size: 30px; border: 2px solid; border-color: var(--bevel-edge-hi) var(--bevel-edge-lo) var(--bevel-edge-lo) var(--bevel-edge-hi); background: var(--bevel-face); font: 600 11.5px/1 var(--bevel-font-sans); }
|
|
34
|
+
.bevel-sheet-tools__cap svg { inline-size: 17px; block-size: 17px; }
|
|
35
|
+
.bevel-sheet-tools__button:hover .bevel-sheet-tools__cap { background: color-mix(in srgb, var(--bevel-face), var(--bevel-edge-hi) 45%); }
|
|
36
|
+
.bevel-sheet-tools__button:active .bevel-sheet-tools__cap, .bevel-sheet-tools__button[aria-pressed="true"] .bevel-sheet-tools__cap { border-color: var(--bevel-edge-in) var(--bevel-edge-in-hi) var(--bevel-edge-in-hi) var(--bevel-edge-in); background: var(--bevel-face-in); color: var(--bevel-sel); }
|
|
37
|
+
|
|
38
|
+
:where(.bevel-sheet-grid, table[grid]) { border-collapse: separate; border-spacing: 0; border-block-start: 1px solid var(--bevel-line); border-inline-start: 1px solid var(--bevel-line); background: var(--bevel-surface); color: var(--bevel-ink); font: 400 13px/1.35 var(--bevel-font-sans); text-align: start; white-space: nowrap; }
|
|
39
|
+
:where(.bevel-sheet-grid, table[grid]) :is(td, th) { block-size: 26px; padding: 0 8px; border: 0; border-inline-end: 1px solid var(--bevel-line); border-block-end: 1px solid var(--bevel-line); font-size: 13px; line-height: 25px; vertical-align: middle; overflow: hidden; text-overflow: ellipsis; }
|
|
40
|
+
:where(.bevel-sheet-grid, table[grid]) th { font-weight: inherit; text-align: inherit; }
|
|
41
|
+
:where(.bevel-sheet-grid, table[grid]) td[n], .bevel-sheet-grid__num { text-align: end; font-family: var(--bevel-font-mono); font-size: 12.5px; font-variant-numeric: tabular-nums; }
|
|
42
|
+
/* Stacking: frozen cells 2, frozen selection/editor 3, row and column labels 4, corner 5. */
|
|
43
|
+
:where(.bevel-sheet-grid, table[grid]) thead tr { background: var(--bevel-ground); color: var(--bevel-ink); font-weight: 600; }
|
|
44
|
+
:where(.bevel-sheet-grid, table[grid]) thead td { position: sticky; inset-block-start: var(--bevel-sheet-frozen-top, 0px); z-index: 2; block-size: 27px; border-block-end: 2px solid var(--bevel-line-3); background: inherit; }
|
|
45
|
+
:where(.bevel-sheet-grid, table[grid]) td[aria-selected="true"] { background: var(--bevel-sel-fill); }
|
|
46
|
+
.bevel-sheet-grid:has(.bevel-sheet-grid__coord) { --bevel-sheet-frozen-top: 24px; }
|
|
47
|
+
|
|
48
|
+
.bevel-sheet-grid .bevel-sheet-grid__coord { position: sticky; z-index: 4; border-inline-end-color: var(--bevel-line-2); border-block-end-color: var(--bevel-line-2); background: var(--bevel-sunk); color: var(--bevel-muted); font: 500 11px/23px var(--bevel-font-mono); text-align: center; }
|
|
49
|
+
.bevel-sheet-grid thead .bevel-sheet-grid__coord { inset-block-start: var(--bevel-sheet-frozen-top, 0px); }
|
|
50
|
+
.bevel-sheet-grid thead tr:first-child .bevel-sheet-grid__coord { inset-block-start: 0; block-size: 24px; }
|
|
51
|
+
.bevel-sheet-grid .bevel-sheet-grid__coord:first-child { inset-inline-start: 0; padding: 0; }
|
|
52
|
+
.bevel-sheet-grid thead tr:first-child .bevel-sheet-grid__coord:first-child { z-index: 5; }
|
|
53
|
+
.bevel-sheet-grid .bevel-sheet-grid__coord[aria-selected="true"] { background: var(--bevel-sel-head); color: var(--bevel-sel); font-weight: 600; }
|
|
54
|
+
.bevel-sheet-grid tr:has(> .bevel-sheet-grid__coord[aria-selected="true"]):has(+ tr > .bevel-sheet-grid__coord[aria-selected="true"]) > .bevel-sheet-grid__coord[aria-selected="true"] { border-block-end-color: var(--bevel-sel-line); }
|
|
55
|
+
|
|
56
|
+
.bevel-sheet-grid tr:has(+ .bevel-sheet-grid__total) > :is(td, th) { border-block-end-color: var(--bevel-line-3); }
|
|
57
|
+
:where(.bevel-sheet-grid .bevel-sheet-grid__total) > td { block-size: 28px; border-block-end-color: var(--bevel-line-2); background: var(--bevel-ground); font-weight: 600; }
|
|
58
|
+
|
|
59
|
+
.bevel-sheet-grid__selection { position: absolute; z-index: 1; box-sizing: border-box; border: 2px solid var(--bevel-sel); pointer-events: none; }
|
|
60
|
+
.bevel-sheet-grid__handle { position: absolute; inset-inline-end: -5px; inset-block-end: -5px; inline-size: 8px; block-size: 8px; border: 1.5px solid var(--bevel-surface); background: var(--bevel-sel); pointer-events: auto; cursor: crosshair; }
|
|
61
|
+
.bevel-sheet-grid__editor { position: absolute; z-index: 1; box-sizing: border-box; margin: 0; padding: 0 6px; border: 0; outline: 2px solid var(--bevel-brass); outline-offset: -2px; background: var(--bevel-surface); color: var(--bevel-ink); font: 500 12px/1 var(--bevel-font-mono); box-shadow: var(--bevel-shadow); }
|
|
62
|
+
|
|
63
|
+
.bevel-sheet-grid__selection--frozen, .bevel-sheet-grid__editor--frozen { z-index: 3; }
|
|
64
|
+
|
|
65
|
+
.bevel-sheet-tabs { display: flex; align-items: stretch; justify-content: space-between; gap: 12px; min-block-size: 30px; padding: 0 10px; border-block-start: 1px solid var(--bevel-line-2); background: var(--bevel-sunk); }
|
|
66
|
+
.bevel-sheet-tabs__list { display: flex; align-items: stretch; gap: 2px; min-inline-size: 0; overflow-x: auto; }
|
|
67
|
+
.bevel-sheet-tabs__tab { display: inline-flex; align-items: center; gap: 6px; padding: 0 14px; border: 0; border-inline: 1px solid var(--bevel-line-2); background: transparent; color: var(--bevel-muted); font: 500 12px/1 var(--bevel-font-sans); white-space: nowrap; cursor: pointer; }
|
|
68
|
+
.bevel-sheet-tabs__tab:hover { background: var(--bevel-ground); color: var(--bevel-ink); }
|
|
69
|
+
.bevel-sheet-tabs__tab[aria-selected="true"] { margin-block-start: -1px; border-block-start: 1px solid var(--bevel-surface); background: var(--bevel-surface); color: var(--bevel-sel); font-weight: 600; }
|
|
70
|
+
.bevel-sheet-tabs__add { inline-size: 28px; border: 0; background: transparent; color: var(--bevel-muted); font: 500 15px/1 var(--bevel-font-sans); cursor: pointer; }
|
|
71
|
+
.bevel-sheet-tabs__add:hover { background: var(--bevel-face); color: var(--bevel-ink); }
|
|
72
|
+
.bevel-sheet-tabs__tab:focus-visible, .bevel-sheet-tabs__add:focus-visible { outline: 2px solid var(--bevel-brass); outline-offset: -2px; }
|
|
73
|
+
.bevel-sheet-tabs__status { display: flex; align-items: center; gap: 14px; color: var(--bevel-ink-2); font: 11.5px/1 var(--bevel-font-mono); font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
74
|
+
.bevel-sheet-tabs__status strong { color: var(--bevel-ink); font-weight: 600; }
|
|
75
|
+
|
|
76
|
+
.bevel-sheet-looks { display: flex; align-items: center; justify-content: space-between; gap: 8px; inline-size: 100%; max-inline-size: 440px; padding: 5px 10px; border: 1px solid var(--bevel-line-2); background: var(--bevel-surface); color: var(--bevel-ink-2); font: 12px/1.2 var(--bevel-font-sans); }
|
|
77
|
+
.bevel-sheet-looks__group { display: inline-flex; align-items: center; gap: 4px; }
|
|
78
|
+
.bevel-sheet-looks__label { margin-inline-end: 2px; color: var(--bevel-muted); }
|
|
79
|
+
.bevel-sheet-looks__current { min-inline-size: 112px; color: var(--bevel-ink); font-weight: 600; text-align: center; }
|
|
80
|
+
.bevel-sheet-looks__button { display: inline-flex; align-items: center; justify-content: center; margin: 0; padding: 4px 7px; border: 0; border-radius: 0; background: transparent; box-shadow: none; color: var(--bevel-muted); font: 500 12px/1.2 var(--bevel-font-sans); cursor: pointer; appearance: none; }
|
|
81
|
+
.bevel-sheet-looks__button:hover { background: var(--bevel-sunk); color: var(--bevel-ink); }
|
|
82
|
+
.bevel-sheet-looks__button:focus-visible { outline: 2px solid var(--bevel-brass); outline-offset: -2px; }
|
|
83
|
+
.bevel-sheet-looks__button--confirm { color: var(--bevel-sel); font-weight: 600; }
|
|
84
|
+
|
|
85
|
+
@media (pointer: coarse) {
|
|
86
|
+
.bevel-toolbar--sheet .bevel-toolbar__icon { inline-size: 40px; block-size: 40px; }
|
|
87
|
+
.bevel-sheet-tabs { min-block-size: 44px; }
|
|
88
|
+
.bevel-sheet-looks__button { min-inline-size: 44px; min-block-size: 44px; }
|
|
89
|
+
}
|
|
90
|
+
@media (forced-colors: active) {
|
|
91
|
+
:where(.bevel-sheet-grid, table[grid]) td[aria-selected="true"] { outline: 1px solid Highlight; outline-offset: -1px; }
|
|
92
|
+
.bevel-sheet-grid .bevel-sheet-grid__coord[aria-selected="true"], .bevel-sheet-tabs__tab[aria-selected="true"] { text-decoration: underline; }
|
|
93
|
+
}
|
|
94
|
+
@media print {
|
|
95
|
+
.bevel-toolbar--sheet, .bevel-sheet-tools, .bevel-sheet-tabs__add, .bevel-sheet-looks, .bevel-sheet-grid__selection, .bevel-sheet-grid__handle { display: none; }
|
|
96
|
+
.bevel-sheet__viewport { overflow: visible; }
|
|
97
|
+
:where(.bevel-sheet-grid, table[grid]) thead td, .bevel-sheet-grid .bevel-sheet-grid__coord { position: static; }
|
|
98
|
+
}
|
|
99
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@panphora/bevel",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Beveled controls in an editorial page. One stylesheet, one script, no build.",
|
|
5
5
|
"license": "MIT-0",
|
|
6
6
|
"author": "David Miranda",
|
|
@@ -450,6 +450,7 @@
|
|
|
450
450
|
"fonts": "node scripts/vendor-fonts.mjs",
|
|
451
451
|
"vendor:floating": "node scripts/vendor-floating-ui.mjs",
|
|
452
452
|
"check": "node scripts/check.mjs && node scripts/icon-guard.mjs --strict",
|
|
453
|
+
"sheet-addon": "node --test tests/sheet-addon.test.mjs",
|
|
453
454
|
"check:icons": "node scripts/icon-guard.mjs",
|
|
454
455
|
"contrast": "node scripts/contrast.mjs",
|
|
455
456
|
"roundtrip": "node scripts/roundtrip.mjs",
|
|
@@ -458,7 +459,7 @@
|
|
|
458
459
|
"correction:c1": "node tests/correction-c1.mjs",
|
|
459
460
|
"correction:c2": "node tests/correction-c2.mjs",
|
|
460
461
|
"correction:c2:overlays": "node tests/correction-c2-modal-drawer.mjs",
|
|
461
|
-
"test": "node scripts/test-guard.mjs && npm run test:build && npm run test:showcase && npm run check && npm run contrast && npm run roundtrip && npm run functional && npm run datastar && npm run correction:c1 && npm run datastar:native && npm run clay:snapshot && npm run datastar:pickers && npm run correction:c2:overlays && npm run correction:c2:pickers && npm run correction:c2:forms && npm run correction:c2:wizard && npm run datastar:lifecycle && npm run datastar:reload && npm run richclay:integration && npm run inline-edit:integration && npm run quickcrop:integration && npm run packaged:peers && npm run upload:integration && npm run correction:c3:navigation && npm run correction:app-shell && npm run file-tree && npm run correction:c3:code-diff && npm run correction:c3:feedback && npm run correction:c3:split && npm run correction:c3:navigation-controls && npm run correction:c3:display && npm run correction:c3:rich-toolbar && npm run correction:c3:grid && npm run correction:c3:kanban && npm run context:hypercms && npm run context:quickcrop && npm run datastar:active && npm run datastar:board && npm run datastar:upload && npm run datastar:grid && npm run datastar:state-reload && npm run acceptance:layout && npm run final-review:regressions",
|
|
462
|
+
"test": "node scripts/test-guard.mjs && npm run test:build && npm run test:showcase && npm run check && npm run sheet-addon && npm run contrast && npm run roundtrip && npm run functional && npm run datastar && npm run correction:c1 && npm run datastar:native && npm run clay:snapshot && npm run datastar:pickers && npm run correction:c2:overlays && npm run correction:c2:pickers && npm run correction:c2:forms && npm run correction:c2:wizard && npm run datastar:lifecycle && npm run datastar:reload && npm run richclay:integration && npm run inline-edit:integration && npm run quickcrop:integration && npm run packaged:peers && npm run upload:integration && npm run correction:c3:navigation && npm run correction:app-shell && npm run file-tree && npm run correction:c3:code-diff && npm run correction:c3:feedback && npm run correction:c3:split && npm run correction:c3:navigation-controls && npm run correction:c3:display && npm run correction:c3:rich-toolbar && npm run correction:c3:grid && npm run correction:c3:kanban && npm run context:hypercms && npm run context:quickcrop && npm run datastar:active && npm run datastar:board && npm run datastar:upload && npm run datastar:grid && npm run datastar:state-reload && npm run acceptance:layout && npm run final-review:regressions",
|
|
462
463
|
"dev": "node scripts/dev.mjs",
|
|
463
464
|
"datastar:native": "node tests/datastar-native.mjs",
|
|
464
465
|
"correction:c2:pickers": "node tests/correction-c2-pickers.mjs",
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "richclay",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"source": "Explicitly supplied local distribution; hashes identify the exact tested bytes, not a published release claim.",
|
|
5
5
|
"files": {
|
|
6
|
-
"richclay.js": "
|
|
7
|
-
"richclay.css": "
|
|
6
|
+
"richclay.js": "cb11c591ef58cec92b7ce7b392606a8a74ba52a972750013a2a9a58eeb3c8c41",
|
|
7
|
+
"richclay.css": "b2aed9608266f6798ea25edfa40c344c84b8f37d65e19ce8484f93ad849cd3c8",
|
|
8
8
|
"LICENSE": "5312a8ef6ff9a844697b8b112612467efd12957e8afba098ce600dec0f8396c4",
|
|
9
9
|
"THIRD-PARTY-NOTICES.md": "3659529614b62297b8336ac7f6189ef9f67425911265bff1cde8d136df21562b",
|
|
10
10
|
"vendor/squire.js": "00f2b05431457a05f1b42e4f876b8a334c0e01841ed57a3aa47e6a0e463869c3",
|
|
@@ -314,6 +314,30 @@
|
|
|
314
314
|
font-weight: 650;
|
|
315
315
|
}
|
|
316
316
|
|
|
317
|
+
.richclay-dialog.richclay-link-popover {
|
|
318
|
+
align-items: center;
|
|
319
|
+
display: flex;
|
|
320
|
+
gap: 6px;
|
|
321
|
+
max-width: min(420px, calc(100vw - 16px));
|
|
322
|
+
padding: 6px;
|
|
323
|
+
width: auto;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.richclay-link-address {
|
|
327
|
+
color: inherit;
|
|
328
|
+
font-size: 0.875rem;
|
|
329
|
+
max-width: 260px;
|
|
330
|
+
overflow: hidden;
|
|
331
|
+
padding: 0 6px;
|
|
332
|
+
text-overflow: ellipsis;
|
|
333
|
+
white-space: nowrap;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.richclay-link-popover .richclay-secondary {
|
|
337
|
+
min-height: 30px;
|
|
338
|
+
padding: 3px 9px;
|
|
339
|
+
}
|
|
340
|
+
|
|
317
341
|
.richclay-field {
|
|
318
342
|
display: grid;
|
|
319
343
|
gap: 4px;
|
|
@@ -542,7 +542,7 @@ var RichClayBundle = (() => {
|
|
|
542
542
|
if (!el.tagName.includes("-")) return true;
|
|
543
543
|
return el.matches(RICHCLAY_OPT_IN);
|
|
544
544
|
}
|
|
545
|
-
var CHROME_SELECTOR = "[data-richclay-toolbar], [data-richclay-menu], [data-richclay-dialog], [data-richclay-live], [data-richclay-float]";
|
|
545
|
+
var CHROME_SELECTOR = "[data-richclay-toolbar], [data-richclay-menu], [data-richclay-dialog], [data-richclay-link-popover], [data-richclay-live], [data-richclay-float]";
|
|
546
546
|
var runtimeClasses = [
|
|
547
547
|
"richclay-editor",
|
|
548
548
|
"richclay-inline",
|
|
@@ -612,6 +612,11 @@ var RichClayBundle = (() => {
|
|
|
612
612
|
toolbarOnSelect: tokens.has("toolbar-on-select")
|
|
613
613
|
};
|
|
614
614
|
if (tokens.has("no-toolbar")) options.toolbar = false;
|
|
615
|
+
if (tokens.has("link-label")) {
|
|
616
|
+
options.singleLine = true;
|
|
617
|
+
options.linkLabel = true;
|
|
618
|
+
options.toolbar = ["link", "undo", "redo"];
|
|
619
|
+
}
|
|
615
620
|
return options;
|
|
616
621
|
}
|
|
617
622
|
function installHyperclayBridge(win = window) {
|
|
@@ -1448,12 +1453,15 @@ var RichClayBundle = (() => {
|
|
|
1448
1453
|
const scheme = (stripped.match(/^([a-z][a-z0-9+.\-]*):/i) || [])[1];
|
|
1449
1454
|
return !scheme || SAFE_URL_SCHEMES.has(scheme.toLowerCase());
|
|
1450
1455
|
}
|
|
1456
|
+
var SITE_FILE = /^[^\s/?#:]+\.(?:html?|pdf|txt|csv|json|xml|svg|png|jpe?g|gif|webp|docx?|xlsx?|pptx?|bib|tex|zip)(?:[?#]\S*)?$/i;
|
|
1457
|
+
var SITE_PATH = /^[^\s/?#:.]+\/\S*$/;
|
|
1451
1458
|
function normalizeUrl(value) {
|
|
1452
1459
|
const raw = String(value == null ? "" : value).trim();
|
|
1453
1460
|
if (!raw || !isSafeUrl(raw)) return "";
|
|
1454
1461
|
if (/^(?:https?:|mailto:|tel:)/i.test(raw)) return raw;
|
|
1455
1462
|
if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(raw)) return `mailto:${raw}`;
|
|
1456
1463
|
if (/^(?:[#?]|\/|\.{1,2}\/)/.test(raw)) return raw;
|
|
1464
|
+
if (SITE_FILE.test(raw) || SITE_PATH.test(raw)) return raw;
|
|
1457
1465
|
return `https://${raw}`;
|
|
1458
1466
|
}
|
|
1459
1467
|
function resolveDOMPurify(doc) {
|
|
@@ -1524,6 +1532,170 @@ var RichClayBundle = (() => {
|
|
|
1524
1532
|
if (last?.nodeType === 3) last.nodeValue = last.nodeValue.replace(/\s+$/, "");
|
|
1525
1533
|
}
|
|
1526
1534
|
|
|
1535
|
+
// src/link-popover.js
|
|
1536
|
+
var LINK_POPOVER_GRACE_MS = 300;
|
|
1537
|
+
var shownAddress = (href) => href.replace(/^mailto:/i, "").replace(/^https?:\/\//i, "");
|
|
1538
|
+
var LinkPopover = class {
|
|
1539
|
+
constructor(editor) {
|
|
1540
|
+
this.editor = editor;
|
|
1541
|
+
this.root = null;
|
|
1542
|
+
this.anchor = null;
|
|
1543
|
+
this.href = null;
|
|
1544
|
+
this.dismissed = null;
|
|
1545
|
+
this.hideTimer = null;
|
|
1546
|
+
this.pointerInside = false;
|
|
1547
|
+
this.cleanup = null;
|
|
1548
|
+
}
|
|
1549
|
+
currentAnchor() {
|
|
1550
|
+
const editor = this.editor;
|
|
1551
|
+
if (!editor.active || editor.options.readOnly || editor.dialog) return null;
|
|
1552
|
+
const doc = editor.element.ownerDocument;
|
|
1553
|
+
if (doc.hidden) return null;
|
|
1554
|
+
const live = doc.getSelection?.();
|
|
1555
|
+
if (!live || !live.rangeCount || !editor.element.contains(live.anchorNode)) return null;
|
|
1556
|
+
return editor.currentLinkElement();
|
|
1557
|
+
}
|
|
1558
|
+
update() {
|
|
1559
|
+
const anchor = this.currentAnchor();
|
|
1560
|
+
if (anchor && anchor === this.dismissed) return;
|
|
1561
|
+
this.dismissed = null;
|
|
1562
|
+
if (!anchor) {
|
|
1563
|
+
this.scheduleHide();
|
|
1564
|
+
return;
|
|
1565
|
+
}
|
|
1566
|
+
this.cancelHide();
|
|
1567
|
+
this.show(anchor);
|
|
1568
|
+
}
|
|
1569
|
+
show(anchor) {
|
|
1570
|
+
if (!this.root) this.mount();
|
|
1571
|
+
const href = anchor.getAttribute("href") || "";
|
|
1572
|
+
if (anchor !== this.anchor || href !== this.href) {
|
|
1573
|
+
this.anchor = anchor;
|
|
1574
|
+
this.href = href;
|
|
1575
|
+
const address = this.root.querySelector(".richclay-link-address");
|
|
1576
|
+
address.textContent = shownAddress(href) || href;
|
|
1577
|
+
address.title = href;
|
|
1578
|
+
if (href && isSafeUrl(href)) address.setAttribute("href", href);
|
|
1579
|
+
else address.removeAttribute("href");
|
|
1580
|
+
if (/^(?:mailto|tel):/i.test(href)) address.removeAttribute("target");
|
|
1581
|
+
else address.setAttribute("target", "_blank");
|
|
1582
|
+
}
|
|
1583
|
+
this.position();
|
|
1584
|
+
}
|
|
1585
|
+
mount() {
|
|
1586
|
+
const editor = this.editor;
|
|
1587
|
+
const doc = editor.element.ownerDocument;
|
|
1588
|
+
const win = doc.defaultView || globalThis;
|
|
1589
|
+
const root = doc.createElement("div");
|
|
1590
|
+
root.className = "richclay-dialog richclay-link-popover";
|
|
1591
|
+
root.setAttribute("role", "group");
|
|
1592
|
+
root.setAttribute("aria-label", "Link");
|
|
1593
|
+
root.setAttribute("data-richclay-link-popover", "");
|
|
1594
|
+
markChrome(root);
|
|
1595
|
+
root.setAttribute("save-remove", "");
|
|
1596
|
+
root.innerHTML = `
|
|
1597
|
+
<a class="richclay-link-address" rel="noopener noreferrer"></a>
|
|
1598
|
+
<button type="button" class="richclay-secondary" data-richclay-link-edit>Edit</button>
|
|
1599
|
+
<button type="button" class="richclay-secondary" data-richclay-link-remove>Remove</button>
|
|
1600
|
+
`;
|
|
1601
|
+
if (editor.options.linkLabel) root.querySelector("[data-richclay-link-remove]").remove();
|
|
1602
|
+
root.addEventListener("pointerdown", preservePointerSelection);
|
|
1603
|
+
root.addEventListener("pointerenter", () => {
|
|
1604
|
+
this.pointerInside = true;
|
|
1605
|
+
this.cancelHide();
|
|
1606
|
+
});
|
|
1607
|
+
root.addEventListener("pointerleave", () => {
|
|
1608
|
+
this.pointerInside = false;
|
|
1609
|
+
this.update();
|
|
1610
|
+
});
|
|
1611
|
+
root.querySelector("[data-richclay-link-edit]").addEventListener("click", () => {
|
|
1612
|
+
const anchor = this.anchor;
|
|
1613
|
+
if (!anchor || !editor.element.contains(anchor)) return this.hide();
|
|
1614
|
+
editor.selectElement(anchor);
|
|
1615
|
+
editor.openLinkDialog();
|
|
1616
|
+
});
|
|
1617
|
+
root.querySelector("[data-richclay-link-remove]")?.addEventListener("click", () => {
|
|
1618
|
+
const anchor = this.anchor;
|
|
1619
|
+
this.hide();
|
|
1620
|
+
editor.removeLinkElement(anchor);
|
|
1621
|
+
editor.focus();
|
|
1622
|
+
});
|
|
1623
|
+
const host = editor.element.closest("dialog") || doc.body;
|
|
1624
|
+
const layered = typeof root.showPopover === "function";
|
|
1625
|
+
if (layered) root.setAttribute("popover", "manual");
|
|
1626
|
+
pin(root, "position", "fixed");
|
|
1627
|
+
pin(root, "inset", "0px auto auto 0px");
|
|
1628
|
+
pin(root, "margin", "0");
|
|
1629
|
+
pin(root, "overflow", "visible");
|
|
1630
|
+
host.appendChild(root);
|
|
1631
|
+
if (layered) root.showPopover();
|
|
1632
|
+
this.root = root;
|
|
1633
|
+
const reposition = () => this.position();
|
|
1634
|
+
const onKeydown = (event) => {
|
|
1635
|
+
if (event.key !== "Escape" || !this.root) return;
|
|
1636
|
+
this.dismissed = this.anchor;
|
|
1637
|
+
this.hide();
|
|
1638
|
+
};
|
|
1639
|
+
doc.addEventListener("scroll", reposition, { capture: true, passive: true });
|
|
1640
|
+
win.addEventListener("resize", reposition, { passive: true });
|
|
1641
|
+
editor.element.addEventListener("keydown", onKeydown);
|
|
1642
|
+
this.cleanup = () => {
|
|
1643
|
+
doc.removeEventListener("scroll", reposition, { capture: true });
|
|
1644
|
+
win.removeEventListener("resize", reposition);
|
|
1645
|
+
editor.element.removeEventListener("keydown", onKeydown);
|
|
1646
|
+
};
|
|
1647
|
+
}
|
|
1648
|
+
position() {
|
|
1649
|
+
const root = this.root;
|
|
1650
|
+
if (!root || !this.anchor) return;
|
|
1651
|
+
const win = root.ownerDocument.defaultView || globalThis;
|
|
1652
|
+
const placement = placePopover({
|
|
1653
|
+
anchor: this.anchor.getBoundingClientRect(),
|
|
1654
|
+
box: { width: root.offsetWidth || 0, height: root.offsetHeight || 0 },
|
|
1655
|
+
viewport: {
|
|
1656
|
+
width: win.visualViewport?.width ?? win.innerWidth,
|
|
1657
|
+
height: win.visualViewport?.height ?? win.innerHeight
|
|
1658
|
+
}
|
|
1659
|
+
});
|
|
1660
|
+
root.setAttribute("data-richclay-side", placement.side);
|
|
1661
|
+
root.style.setProperty("--richclay-dialog-arrow", `${Math.round(placement.arrow)}px`);
|
|
1662
|
+
pin(root, "transform", `translate(${Math.round(placement.x)}px, ${Math.round(placement.y)}px)`);
|
|
1663
|
+
}
|
|
1664
|
+
scheduleHide() {
|
|
1665
|
+
if (!this.root || this.hideTimer !== null) return;
|
|
1666
|
+
const win = this.root.ownerDocument.defaultView || globalThis;
|
|
1667
|
+
this.hideTimer = win.setTimeout(() => {
|
|
1668
|
+
this.hideTimer = null;
|
|
1669
|
+
if (!this.pointerInside) this.hide();
|
|
1670
|
+
}, LINK_POPOVER_GRACE_MS);
|
|
1671
|
+
}
|
|
1672
|
+
cancelHide() {
|
|
1673
|
+
if (this.hideTimer === null) return;
|
|
1674
|
+
const win = this.editor.element.ownerDocument.defaultView || globalThis;
|
|
1675
|
+
win.clearTimeout(this.hideTimer);
|
|
1676
|
+
this.hideTimer = null;
|
|
1677
|
+
}
|
|
1678
|
+
onEditorBlur(event) {
|
|
1679
|
+
const next = event?.relatedTarget;
|
|
1680
|
+
if (next && this.editor.ownsFocusTarget(next)) return;
|
|
1681
|
+
this.hide();
|
|
1682
|
+
}
|
|
1683
|
+
hide() {
|
|
1684
|
+
this.cancelHide();
|
|
1685
|
+
this.cleanup?.();
|
|
1686
|
+
this.cleanup = null;
|
|
1687
|
+
this.root?.remove();
|
|
1688
|
+
this.root = null;
|
|
1689
|
+
this.anchor = null;
|
|
1690
|
+
this.href = null;
|
|
1691
|
+
this.pointerInside = false;
|
|
1692
|
+
}
|
|
1693
|
+
destroy() {
|
|
1694
|
+
this.hide();
|
|
1695
|
+
this.dismissed = null;
|
|
1696
|
+
}
|
|
1697
|
+
};
|
|
1698
|
+
|
|
1527
1699
|
// src/styles.js
|
|
1528
1700
|
var STYLE_ID = "richclay-styles";
|
|
1529
1701
|
var styledDocs = /* @__PURE__ */ new WeakSet();
|
|
@@ -1807,8 +1979,11 @@ var RichClayBundle = (() => {
|
|
|
1807
1979
|
Squire: null,
|
|
1808
1980
|
inline: false,
|
|
1809
1981
|
singleLine: false,
|
|
1810
|
-
toolbarOnSelect: false
|
|
1982
|
+
toolbarOnSelect: false,
|
|
1983
|
+
linkPopover: true,
|
|
1984
|
+
linkLabel: false
|
|
1811
1985
|
};
|
|
1986
|
+
var LINK_LABEL_CONTROLS = ["link", "undo", "redo"];
|
|
1812
1987
|
var RichClay = class _RichClay {
|
|
1813
1988
|
static presets = presets;
|
|
1814
1989
|
static selector = RICHCLAY_SELECTOR;
|
|
@@ -1834,6 +2009,7 @@ var RichClayBundle = (() => {
|
|
|
1834
2009
|
this.description = null;
|
|
1835
2010
|
this.dialog = null;
|
|
1836
2011
|
this.dialogCleanup = null;
|
|
2012
|
+
this.linkPopover = null;
|
|
1837
2013
|
this.savedSelection = null;
|
|
1838
2014
|
this.path = "";
|
|
1839
2015
|
this.active = false;
|
|
@@ -1853,6 +2029,7 @@ var RichClayBundle = (() => {
|
|
|
1853
2029
|
};
|
|
1854
2030
|
this._onBlur = (event) => {
|
|
1855
2031
|
this.element.classList.remove("richclay-focused");
|
|
2032
|
+
this.linkPopover?.onEditorBlur(event);
|
|
1856
2033
|
if (this.options.inline) this.scheduleFloatTeardown(event);
|
|
1857
2034
|
};
|
|
1858
2035
|
this._onLinkActivation = (event) => {
|
|
@@ -1864,6 +2041,13 @@ var RichClayBundle = (() => {
|
|
|
1864
2041
|
if (chrome && chrome !== this.element && this.element.contains(chrome) && chrome.contains(anchor)) return;
|
|
1865
2042
|
event.preventDefault();
|
|
1866
2043
|
};
|
|
2044
|
+
this._onExternalWrite = () => {
|
|
2045
|
+
if (!this._squire) return;
|
|
2046
|
+
this._squire._mutation?.takeRecords();
|
|
2047
|
+
if (this.options.linkLabel) this._labelHref = this.element.querySelector("a[href]")?.getAttribute("href") ?? this._labelHref;
|
|
2048
|
+
this.resetSquireUndoBaseline();
|
|
2049
|
+
this.updatePlaceholder();
|
|
2050
|
+
};
|
|
1867
2051
|
this.ensureMarker();
|
|
1868
2052
|
this.hyperclay = shouldUseHyperclay(this.options, this.window);
|
|
1869
2053
|
if (this.hyperclay) installHyperclayBridge(this.window);
|
|
@@ -2063,6 +2247,7 @@ var RichClayBundle = (() => {
|
|
|
2063
2247
|
const ignored = editor._ignoreChange;
|
|
2064
2248
|
editor._docWasChanged();
|
|
2065
2249
|
if (local && ignored) editor.fireEvent("input");
|
|
2250
|
+
if (this.options.linkLabel) this.keepOneLink();
|
|
2066
2251
|
} else if (local) {
|
|
2067
2252
|
editor._mayHaveZWS = true;
|
|
2068
2253
|
editor.fireEvent("input");
|
|
@@ -2091,6 +2276,7 @@ var RichClayBundle = (() => {
|
|
|
2091
2276
|
this.maskSquireCodeShortcut();
|
|
2092
2277
|
this.maskSquireBlockShortcuts();
|
|
2093
2278
|
this.installShortcuts();
|
|
2279
|
+
if (this.options.linkLabel) this.maskFormattingShortcuts();
|
|
2094
2280
|
if (this.options.inline) {
|
|
2095
2281
|
this._onToolbarKey = (event) => {
|
|
2096
2282
|
if (event.altKey && event.key === "F10") {
|
|
@@ -2114,6 +2300,17 @@ var RichClayBundle = (() => {
|
|
|
2114
2300
|
this._squire.addEventListener("willPaste", willPaste);
|
|
2115
2301
|
this._squireListeners.push(["willPaste", willPaste]);
|
|
2116
2302
|
}
|
|
2303
|
+
if (this.options.linkLabel) {
|
|
2304
|
+
const plainPaste = (event) => {
|
|
2305
|
+
const fragment = event.detail?.fragment;
|
|
2306
|
+
if (!fragment) return;
|
|
2307
|
+
const text = fragment.textContent.replace(/\s+/g, " ");
|
|
2308
|
+
fragment.replaceChildren(this.element.ownerDocument.createTextNode(text));
|
|
2309
|
+
};
|
|
2310
|
+
this._squire.addEventListener("willPaste", plainPaste);
|
|
2311
|
+
this._squireListeners.push(["willPaste", plainPaste]);
|
|
2312
|
+
this._labelHref = this.element.querySelector("a[href]")?.getAttribute("href") ?? null;
|
|
2313
|
+
}
|
|
2117
2314
|
this.renderToolbar();
|
|
2118
2315
|
this.updatePlaceholder();
|
|
2119
2316
|
this.warnOnBlockInInlineRegion();
|
|
@@ -2178,6 +2375,8 @@ var RichClayBundle = (() => {
|
|
|
2178
2375
|
}
|
|
2179
2376
|
destroy() {
|
|
2180
2377
|
this.closeLinkDialog();
|
|
2378
|
+
this.linkPopover?.destroy();
|
|
2379
|
+
this.linkPopover = null;
|
|
2181
2380
|
this.teardownFloatingToolbar();
|
|
2182
2381
|
this.toolbar?.destroy();
|
|
2183
2382
|
this.toolbar = null;
|
|
@@ -2420,6 +2619,7 @@ var RichClayBundle = (() => {
|
|
|
2420
2619
|
openLinkDialog() {
|
|
2421
2620
|
this.saveSelection();
|
|
2422
2621
|
this.closeLinkDialog();
|
|
2622
|
+
this.linkPopover?.hide();
|
|
2423
2623
|
const doc = this.element.ownerDocument;
|
|
2424
2624
|
const existingLink = this.currentLinkElement();
|
|
2425
2625
|
const existing = existingLink?.getAttribute("href") || "";
|
|
@@ -2439,7 +2639,7 @@ var RichClayBundle = (() => {
|
|
|
2439
2639
|
<input class="richclay-input" name="url" type="text" inputmode="url" autocomplete="url" placeholder="https://example.com">
|
|
2440
2640
|
</label>
|
|
2441
2641
|
<div class="richclay-dialog-actions">
|
|
2442
|
-
${existingLink ? '<button type="button" class="richclay-secondary" data-richclay-remove-link>Remove link</button>' : ""}
|
|
2642
|
+
${existingLink && !this.options.linkLabel ? '<button type="button" class="richclay-secondary" data-richclay-remove-link>Remove link</button>' : ""}
|
|
2443
2643
|
<button type="button" class="richclay-secondary" data-richclay-cancel>Cancel</button>
|
|
2444
2644
|
<button type="submit" class="richclay-primary">Apply</button>
|
|
2445
2645
|
</div>
|
|
@@ -2448,17 +2648,18 @@ var RichClayBundle = (() => {
|
|
|
2448
2648
|
this.closeLinkDialog();
|
|
2449
2649
|
this.toolbar?.update();
|
|
2450
2650
|
this.focus();
|
|
2651
|
+
this.updateLinkPopover();
|
|
2451
2652
|
};
|
|
2452
2653
|
const remove = () => {
|
|
2453
2654
|
this.restoreSelection();
|
|
2454
|
-
|
|
2455
|
-
if (anchor) {
|
|
2456
|
-
this.selectElement(anchor);
|
|
2457
|
-
this._squire.removeLink();
|
|
2458
|
-
announce(this.liveRegion, "Link removed");
|
|
2459
|
-
}
|
|
2655
|
+
this.removeLinkElement(this.currentLinkElement());
|
|
2460
2656
|
close();
|
|
2461
2657
|
};
|
|
2658
|
+
const cancel = () => {
|
|
2659
|
+
close();
|
|
2660
|
+
this.restoreSelection();
|
|
2661
|
+
this.updateLinkPopover();
|
|
2662
|
+
};
|
|
2462
2663
|
dialog.addEventListener("submit", (event) => {
|
|
2463
2664
|
event.preventDefault();
|
|
2464
2665
|
const data = new FormData(dialog);
|
|
@@ -2473,7 +2674,12 @@ var RichClayBundle = (() => {
|
|
|
2473
2674
|
return;
|
|
2474
2675
|
}
|
|
2475
2676
|
this.restoreSelection();
|
|
2476
|
-
const anchor = this.currentLinkElement();
|
|
2677
|
+
const anchor = this.options.linkLabel ? null : this.currentLinkElement();
|
|
2678
|
+
if (this.options.linkLabel) {
|
|
2679
|
+
const range = this.element.ownerDocument.createRange();
|
|
2680
|
+
range.selectNodeContents(this.element);
|
|
2681
|
+
this._squire.setSelection(range);
|
|
2682
|
+
}
|
|
2477
2683
|
if (anchor) {
|
|
2478
2684
|
this.selectElement(anchor);
|
|
2479
2685
|
this._squire.saveUndoState();
|
|
@@ -2485,11 +2691,11 @@ var RichClayBundle = (() => {
|
|
|
2485
2691
|
close();
|
|
2486
2692
|
});
|
|
2487
2693
|
dialog.querySelector("[data-richclay-remove-link]")?.addEventListener("click", remove);
|
|
2488
|
-
dialog.querySelector("[data-richclay-cancel]").addEventListener("click",
|
|
2694
|
+
dialog.querySelector("[data-richclay-cancel]").addEventListener("click", cancel);
|
|
2489
2695
|
dialog.addEventListener("keydown", (event) => {
|
|
2490
2696
|
if (event.key === "Escape") {
|
|
2491
2697
|
event.preventDefault();
|
|
2492
|
-
|
|
2698
|
+
cancel();
|
|
2493
2699
|
} else if (event.key === "Tab") {
|
|
2494
2700
|
trapTab(event, dialog);
|
|
2495
2701
|
}
|
|
@@ -2531,6 +2737,23 @@ var RichClayBundle = (() => {
|
|
|
2531
2737
|
input.select();
|
|
2532
2738
|
return KEEP_FOCUS;
|
|
2533
2739
|
}
|
|
2740
|
+
// Unwraps one link and keeps its text, as one undo step. The link form's Remove and the
|
|
2741
|
+
// link popover's Remove are this same call.
|
|
2742
|
+
removeLinkElement(anchor) {
|
|
2743
|
+
if (!anchor || !this.element.contains(anchor)) return false;
|
|
2744
|
+
this.selectElement(anchor);
|
|
2745
|
+
this._squire.removeLink();
|
|
2746
|
+
announce(this.liveRegion, "Link removed");
|
|
2747
|
+
return true;
|
|
2748
|
+
}
|
|
2749
|
+
updateLinkPopover() {
|
|
2750
|
+
if (this.options.linkPopover === false || this.options.readOnly || !this._squire) return;
|
|
2751
|
+
if (!this.linkPopover) {
|
|
2752
|
+
if (this.dialog || !this.currentLinkElement()) return;
|
|
2753
|
+
this.linkPopover = new LinkPopover(this);
|
|
2754
|
+
}
|
|
2755
|
+
this.linkPopover.update();
|
|
2756
|
+
}
|
|
2534
2757
|
currentLinkElement() {
|
|
2535
2758
|
const range = getSquireSelection(this._squire);
|
|
2536
2759
|
if (!range || !this.element.contains(range.commonAncestorContainer)) return null;
|
|
@@ -2649,6 +2872,7 @@ var RichClayBundle = (() => {
|
|
|
2649
2872
|
this.element.addEventListener("blur", this._onBlur);
|
|
2650
2873
|
this.element.addEventListener("click", this._onLinkActivation, true);
|
|
2651
2874
|
this.element.addEventListener("auxclick", this._onLinkActivation, true);
|
|
2875
|
+
this.element.addEventListener("clay:external-write", this._onExternalWrite);
|
|
2652
2876
|
}
|
|
2653
2877
|
cleanupEditorAttributes() {
|
|
2654
2878
|
removeRuntimeState(this.element, "destroy");
|
|
@@ -2656,6 +2880,41 @@ var RichClayBundle = (() => {
|
|
|
2656
2880
|
this.element.removeEventListener("blur", this._onBlur);
|
|
2657
2881
|
this.element.removeEventListener("click", this._onLinkActivation, true);
|
|
2658
2882
|
this.element.removeEventListener("auxclick", this._onLinkActivation, true);
|
|
2883
|
+
this.element.removeEventListener("clay:external-write", this._onExternalWrite);
|
|
2884
|
+
}
|
|
2885
|
+
// A link label holds one link around all of its text, the shape its store reads back
|
|
2886
|
+
// as text and address. Squire puts a space typed at a link's end, and a paste, outside
|
|
2887
|
+
// the link (or splits it), and typing over the whole label deletes it; each of those
|
|
2888
|
+
// is folded back into the one link, with the caret kept at the same character.
|
|
2889
|
+
keepOneLink() {
|
|
2890
|
+
const element = this.element;
|
|
2891
|
+
const doc = element.ownerDocument;
|
|
2892
|
+
const first = element.querySelector("a[href]");
|
|
2893
|
+
const href = first?.getAttribute("href") ?? this._labelHref;
|
|
2894
|
+
if (!href) return;
|
|
2895
|
+
this._labelHref = href;
|
|
2896
|
+
const text = element.textContent.replace(/\u200B/g, "").replace(/\s*[\r\n]+\s*/g, " ");
|
|
2897
|
+
const only = element.childNodes.length === 1 && element.firstChild === first ? first : null;
|
|
2898
|
+
const clean = only && [...only.childNodes].every((node) => node.nodeType === 3) && !only.textContent.includes("\u200B");
|
|
2899
|
+
if (clean || !text) return;
|
|
2900
|
+
const selection = this._squire.getSelection();
|
|
2901
|
+
let caret = text.length;
|
|
2902
|
+
if (selection && element.contains(selection.startContainer)) {
|
|
2903
|
+
const before = doc.createRange();
|
|
2904
|
+
before.selectNodeContents(element);
|
|
2905
|
+
before.setEnd(selection.startContainer, selection.startOffset);
|
|
2906
|
+
caret = before.toString().replace(/\u200B/g, "").replace(/\s*[\r\n]+\s*/g, " ").length;
|
|
2907
|
+
}
|
|
2908
|
+
this._squire.modifyDocument(() => {
|
|
2909
|
+
const anchor = first ?? doc.createElement("a");
|
|
2910
|
+
if (!first) anchor.setAttribute("href", href);
|
|
2911
|
+
anchor.replaceChildren(doc.createTextNode(text));
|
|
2912
|
+
element.replaceChildren(anchor);
|
|
2913
|
+
const range = doc.createRange();
|
|
2914
|
+
range.setStart(anchor.firstChild, Math.min(caret, text.length));
|
|
2915
|
+
range.collapse(true);
|
|
2916
|
+
this._squire.setSelection(range);
|
|
2917
|
+
});
|
|
2659
2918
|
}
|
|
2660
2919
|
bindSquire() {
|
|
2661
2920
|
const input = () => {
|
|
@@ -2663,16 +2922,19 @@ var RichClayBundle = (() => {
|
|
|
2663
2922
|
this.updatePlaceholder();
|
|
2664
2923
|
this.options.onChange?.(this.getHTML());
|
|
2665
2924
|
this.toolbar?.update();
|
|
2925
|
+
this.updateLinkPopover();
|
|
2666
2926
|
};
|
|
2667
2927
|
const pathChange = (event) => {
|
|
2668
2928
|
this.path = event.detail?.path || this._squire.getPath?.() || "";
|
|
2669
2929
|
this.saveSelection();
|
|
2670
2930
|
this.toolbar?.update();
|
|
2931
|
+
this.updateLinkPopover();
|
|
2671
2932
|
};
|
|
2672
2933
|
const selection = () => {
|
|
2673
2934
|
this.saveSelection();
|
|
2674
2935
|
this.toolbar?.update();
|
|
2675
2936
|
if (this.options.toolbarOnSelect) this.updateFloatVisibility();
|
|
2937
|
+
this.updateLinkPopover();
|
|
2676
2938
|
};
|
|
2677
2939
|
const undoState = () => this.toolbar?.update();
|
|
2678
2940
|
[
|
|
@@ -2749,7 +3011,7 @@ var RichClayBundle = (() => {
|
|
|
2749
3011
|
}, 0);
|
|
2750
3012
|
}
|
|
2751
3013
|
ownsFocusTarget(node) {
|
|
2752
|
-
return node === this.element || this.element.contains(node) || Boolean(this.float?.root.contains(node)) || Boolean(this.dialog?.contains(node));
|
|
3014
|
+
return node === this.element || this.element.contains(node) || Boolean(this.float?.root.contains(node)) || Boolean(this.dialog?.contains(node)) || Boolean(this.linkPopover?.root?.contains(node));
|
|
2753
3015
|
}
|
|
2754
3016
|
updateFloatVisibility() {
|
|
2755
3017
|
if (!this.float) return;
|
|
@@ -2773,7 +3035,7 @@ var RichClayBundle = (() => {
|
|
|
2773
3035
|
installShortcuts() {
|
|
2774
3036
|
const seen = /* @__PURE__ */ new Set();
|
|
2775
3037
|
const registryDefs = [...this.registry.values()].filter(
|
|
2776
|
-
(def) => !this.options.singleLine || presets.inline.includes(def.id)
|
|
3038
|
+
(def) => (!this.options.singleLine || presets.inline.includes(def.id)) && (!this.options.linkLabel || LINK_LABEL_CONTROLS.includes(def.id))
|
|
2777
3039
|
);
|
|
2778
3040
|
const defs = [...this.resolveToolbarControls(this.options.toolbar), ...registryDefs];
|
|
2779
3041
|
defs.forEach((def) => {
|
|
@@ -2794,6 +3056,18 @@ var RichClayBundle = (() => {
|
|
|
2794
3056
|
this._shortcutKeys.push(key);
|
|
2795
3057
|
});
|
|
2796
3058
|
}
|
|
3059
|
+
// Formatting typed into a link label would be dropped by the next save of the shared
|
|
3060
|
+
// store, which keeps only its text and address. Squire binds bold, italic and underline
|
|
3061
|
+
// on its own prototype, so each key is answered with preventDefault: a null handler
|
|
3062
|
+
// would hand it to the browser's own contenteditable formatting instead.
|
|
3063
|
+
maskFormattingShortcuts() {
|
|
3064
|
+
for (const def of this.registry.values()) {
|
|
3065
|
+
if (!def.shortcut || LINK_LABEL_CONTROLS.includes(def.id)) continue;
|
|
3066
|
+
const key = shortcutKey(def.shortcut, this.window);
|
|
3067
|
+
this._squire.setKeyHandler(key, (squire, event) => event.preventDefault());
|
|
3068
|
+
this._shortcutKeys.push(key);
|
|
3069
|
+
}
|
|
3070
|
+
}
|
|
2797
3071
|
// macOS sends Ctrl+D and Ctrl+H to the browser's own forward/backward delete,
|
|
2798
3072
|
// which merges two blocks by wrapping the moved text in a computed-style
|
|
2799
3073
|
// <span> — permanent junk in an editor whose DOM is the saved document. Point
|
|
@@ -2847,7 +3121,8 @@ var RichClayBundle = (() => {
|
|
|
2847
3121
|
});
|
|
2848
3122
|
this._onBeforeInput = (event) => {
|
|
2849
3123
|
if (!this.element.contains(event.target)) return;
|
|
2850
|
-
|
|
3124
|
+
const formats = this.options.linkLabel && event.inputType.startsWith("format");
|
|
3125
|
+
if (event.inputType === "insertParagraph" || event.inputType === "insertLineBreak" || formats) {
|
|
2851
3126
|
event.preventDefault();
|
|
2852
3127
|
event.stopPropagation();
|
|
2853
3128
|
}
|
|
@@ -2973,7 +3248,7 @@ var RichClayBundle = (() => {
|
|
|
2973
3248
|
}
|
|
2974
3249
|
|
|
2975
3250
|
// richclay.css
|
|
2976
|
-
var richclay_default = ":root {\n --richclay-surface: #ffffff;\n --richclay-surface-alt: #f6f7f9;\n --richclay-text: #0b0e14;\n --richclay-muted: #667085;\n --richclay-border: #c9ced8;\n --richclay-accent: #0f766e;\n --richclay-accent-ink: #ffffff;\n --richclay-focus: #2563eb;\n --richclay-danger: #b42318;\n --richclay-radius: 6px;\n --richclay-control-size: 34px;\n --richclay-shadow: 0 10px 24px rgb(15 23 42 / 0.16);\n --richclay-dialog-arrow: 28px;\n}\n\n@media (prefers-color-scheme: dark) {\n :root {\n --richclay-surface: #16181d;\n --richclay-surface-alt: #22252c;\n --richclay-text: #ffffff;\n --richclay-muted: #a3aab8;\n --richclay-border: #4a5260;\n --richclay-accent: #2dd4bf;\n --richclay-accent-ink: #05201d;\n --richclay-focus: #93c5fd;\n --richclay-shadow: 0 12px 28px rgb(0 0 0 / 0.35);\n }\n}\n\n.richclay-toolbar {\n align-items: center;\n background: var(--richclay-surface-alt);\n border: 1px solid var(--richclay-border);\n border-radius: var(--richclay-radius);\n color: var(--richclay-text);\n display: flex;\n flex-wrap: wrap;\n gap: 2px;\n margin-block: 0 6px;\n padding: 4px;\n}\n\n/* Seamless card: in the default layout the toolbar sits directly above the\n editor. Fuse them into a single bordered card with one divider \u2014 the\n toolbar's bottom border \u2014 instead of two stacked, separated boxes. */\n.richclay-toolbar:has(+ .richclay-editor) {\n margin-block-end: 0;\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.richclay-toolbar + .richclay-editor {\n border-top: 0;\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n/* In a fused card, draw the focus ring inside the body so it never crosses\n the toolbar divider. */\n.richclay-toolbar + .richclay-editor:focus-visible {\n outline-offset: -3px;\n}\n\n.richclay-button {\n align-items: center;\n appearance: none;\n background: transparent;\n border: 1px solid transparent;\n border-radius: 5px;\n color: currentColor;\n display: inline-flex;\n height: var(--richclay-control-size);\n justify-content: center;\n margin: 0;\n min-width: var(--richclay-control-size);\n padding: 0;\n position: relative;\n}\n\n.richclay-button:hover,\n.richclay-button[aria-expanded=\"true\"],\n.richclay-menu-item:hover {\n background: var(--richclay-surface);\n border-color: var(--richclay-border);\n}\n\n.richclay-button.is-active,\n.richclay-button[aria-pressed=\"true\"],\n.richclay-menu-item.is-active {\n background: rgb(0 0 0 / 0.13);\n border-color: rgb(0 0 0 / 0.22);\n color: var(--richclay-text);\n box-shadow: inset 0 1px 3px rgb(0 0 0 / 0.26);\n}\n\n.richclay-button:focus-visible,\n.richclay-menu-item:focus-visible,\n.richclay-editor:focus-visible,\n.richclay-input:focus-visible,\n.richclay-primary:focus-visible,\n.richclay-secondary:focus-visible {\n outline: 3px solid var(--richclay-focus);\n outline-offset: 2px;\n}\n\n.richclay-button:disabled {\n color: var(--richclay-muted);\n cursor: not-allowed;\n opacity: 0.55;\n}\n\n.richclay-separator {\n align-self: stretch;\n background: var(--richclay-border);\n display: inline-block;\n margin: 5px 4px;\n width: 1px;\n}\n\n.richclay-menu-wrap {\n display: inline-flex;\n position: relative;\n}\n\n.richclay-menu {\n background: var(--richclay-surface);\n border: 1px solid var(--richclay-border);\n border-radius: var(--richclay-radius);\n box-shadow: var(--richclay-shadow);\n color: var(--richclay-text);\n display: grid;\n gap: 2px;\n inset-block-start: calc(100% + 4px);\n inset-inline-start: 0;\n min-width: 160px;\n padding: 4px;\n position: absolute;\n z-index: 1000;\n}\n\n.richclay-menu[hidden] {\n display: none;\n}\n\n.richclay-menu-item {\n align-items: center;\n appearance: none;\n background: transparent;\n border: 1px solid transparent;\n border-radius: 4px;\n color: inherit;\n display: flex;\n font: inherit;\n gap: 8px;\n min-height: 32px;\n padding: 5px 9px;\n text-align: start;\n}\n\n.richclay-editor {\n background: var(--richclay-surface);\n border: 1px solid var(--richclay-border);\n border-radius: var(--richclay-radius);\n color: var(--richclay-text);\n min-height: 9rem;\n /* Break a long unbreakable string (e.g. a bare URL) in prose rather than let\n it widen the editor. `anywhere` also lowers min-content, so the editor can't\n grow even as a flex/grid item. Inert on the code <pre> (white-space: pre). */\n overflow-wrap: anywhere;\n padding: 12px;\n position: relative;\n}\n\n.richclay-editor.richclay-empty::before {\n color: var(--richclay-muted);\n content: attr(data-richclay-placeholder);\n inset-block-start: 12px;\n inset-inline-start: 12px;\n pointer-events: none;\n position: absolute;\n}\n\n.richclay-editor p,\n.richclay-editor h1,\n.richclay-editor h2,\n.richclay-editor h3,\n.richclay-editor h4,\n.richclay-editor h5,\n.richclay-editor h6,\n.richclay-editor blockquote,\n.richclay-editor pre,\n.richclay-editor ul,\n.richclay-editor ol {\n margin-block: 0 0.75em;\n}\n\n.richclay-editor > :last-child {\n margin-block-end: 0;\n}\n\n/* Inline editors ([editable]) inherit the page's own styling. richclay adds\n only a caret, a discoverability outline, and empty-state affordances. */\n.richclay-inline {\n caret-color: currentColor;\n}\n\n.richclay-inline:hover {\n outline: 1px dashed var(--richclay-border);\n outline-offset: 2px;\n}\n\n.richclay-inline.richclay-focused,\n.richclay-inline:focus-visible {\n outline: 2px dashed var(--richclay-focus);\n outline-offset: 2px;\n}\n\n.richclay-inline.richclay-empty {\n min-height: 1em;\n min-width: 1ch;\n}\n\n.richclay-inline.richclay-empty::before {\n color: var(--richclay-muted);\n content: attr(data-richclay-placeholder);\n pointer-events: none;\n}\n\n/* Floating toolbar shell for inline editors. Fixed-position and body-mounted:\n fixed elements never create scrollbars, and body mounting avoids transformed\n ancestors silently turning fixed into ancestor-relative. */\n.richclay-float {\n left: 0;\n position: fixed;\n top: 0;\n will-change: transform;\n z-index: 99999;\n}\n\n.richclay-float .richclay-toolbar {\n box-shadow: var(--richclay-shadow);\n margin-block: 0;\n}\n\n/* Compact vertical rail for narrow margins. */\n.richclay-float-rail .richclay-toolbar {\n --richclay-control-size: 28px;\n flex-direction: column;\n flex-wrap: nowrap;\n}\n\n.richclay-float-rail .richclay-toolbar svg {\n height: 15px;\n width: 15px;\n}\n\n.richclay-float-rail .richclay-separator {\n align-self: stretch;\n height: 1px;\n margin: 4px 5px;\n width: auto;\n}\n\n.richclay-float-rail .richclay-menu {\n inset-block-start: 0;\n inset-inline-start: calc(100% + 4px);\n}\n\n.richclay-editor blockquote {\n border-inline-start: 3px solid var(--richclay-border);\n color: var(--richclay-muted);\n margin-inline: 0;\n padding-inline-start: 12px;\n}\n\n/* Code blocks scroll horizontally inside the editor instead of widening it.\n `width: 0; min-width: 100%` zeroes the pre's intrinsic-width contribution so a\n long unbreakable line can't push an intrinsically-sized ancestor (flex/grid\n item, inline-block, table cell) wider, while still filling the editor and\n scrolling its own overflow. box-sizing keeps padding inside that 100%.\n Containment applies to inline editors too: `white-space: pre` never wraps, so\n without it one code block runs off the side of the page. Only the decoration\n below stays card-scoped, since inline mode keeps the page's own typography. */\n.richclay-editor pre,\n.richclay-inline pre {\n box-sizing: border-box;\n min-width: 100%;\n overflow: auto;\n width: 0;\n}\n\n.richclay-editor pre {\n background: var(--richclay-surface-alt);\n border-radius: 4px;\n padding: 10px;\n}\n\n.richclay-dialog {\n background: var(--richclay-surface);\n border: 1px solid var(--richclay-border);\n border-radius: var(--richclay-radius);\n box-shadow: var(--richclay-shadow);\n color: var(--richclay-text);\n display: grid;\n gap: 10px;\n box-sizing: border-box;\n width: min(420px, calc(100vw - 16px));\n z-index: 99999;\n padding: 12px;\n}\n\n.richclay-dialog-title {\n font-weight: 650;\n}\n\n.richclay-field {\n display: grid;\n gap: 4px;\n}\n\n.richclay-field span {\n color: var(--richclay-muted);\n font-size: 0.875rem;\n}\n\n.richclay-input {\n background: var(--richclay-surface);\n border: 1px solid var(--richclay-border);\n border-radius: 4px;\n color: var(--richclay-text);\n font: inherit;\n min-height: 36px;\n padding: 6px 8px;\n}\n\n.richclay-dialog-actions {\n display: flex;\n gap: 8px;\n justify-content: flex-end;\n}\n\n.richclay-dialog-actions [data-richclay-remove-link] {\n margin-inline-end: auto;\n}\n\n.richclay-primary,\n.richclay-secondary {\n appearance: none;\n border: 1px solid var(--richclay-border);\n border-radius: 4px;\n font: inherit;\n min-height: 34px;\n padding: 5px 10px;\n}\n\n.richclay-primary {\n background: var(--richclay-accent);\n border-color: var(--richclay-accent);\n color: var(--richclay-accent-ink);\n}\n\n.richclay-secondary {\n background: var(--richclay-surface-alt);\n color: var(--richclay-text);\n}\n\n.richclay-sr-only {\n border: 0;\n clip: rect(0 0 0 0);\n clip-path: inset(50%);\n height: 1px;\n margin: -1px;\n overflow: hidden;\n padding: 0;\n position: absolute;\n white-space: nowrap;\n width: 1px;\n}\n\n@media (forced-colors: active) {\n .richclay-toolbar,\n .richclay-editor,\n .richclay-menu,\n .richclay-dialog,\n .richclay-input {\n border-color: CanvasText;\n }\n\n .richclay-button.is-active,\n .richclay-button[aria-pressed=\"true\"],\n .richclay-menu-item.is-active,\n .richclay-primary {\n background: Highlight;\n color: HighlightText;\n }\n\n .richclay-button:focus-visible,\n .richclay-menu-item:focus-visible,\n .richclay-editor:focus-visible,\n .richclay-input:focus-visible {\n outline-color: Highlight;\n }\n}\n";
|
|
3251
|
+
var richclay_default = ":root {\n --richclay-surface: #ffffff;\n --richclay-surface-alt: #f6f7f9;\n --richclay-text: #0b0e14;\n --richclay-muted: #667085;\n --richclay-border: #c9ced8;\n --richclay-accent: #0f766e;\n --richclay-accent-ink: #ffffff;\n --richclay-focus: #2563eb;\n --richclay-danger: #b42318;\n --richclay-radius: 6px;\n --richclay-control-size: 34px;\n --richclay-shadow: 0 10px 24px rgb(15 23 42 / 0.16);\n --richclay-dialog-arrow: 28px;\n}\n\n@media (prefers-color-scheme: dark) {\n :root {\n --richclay-surface: #16181d;\n --richclay-surface-alt: #22252c;\n --richclay-text: #ffffff;\n --richclay-muted: #a3aab8;\n --richclay-border: #4a5260;\n --richclay-accent: #2dd4bf;\n --richclay-accent-ink: #05201d;\n --richclay-focus: #93c5fd;\n --richclay-shadow: 0 12px 28px rgb(0 0 0 / 0.35);\n }\n}\n\n.richclay-toolbar {\n align-items: center;\n background: var(--richclay-surface-alt);\n border: 1px solid var(--richclay-border);\n border-radius: var(--richclay-radius);\n color: var(--richclay-text);\n display: flex;\n flex-wrap: wrap;\n gap: 2px;\n margin-block: 0 6px;\n padding: 4px;\n}\n\n/* Seamless card: in the default layout the toolbar sits directly above the\n editor. Fuse them into a single bordered card with one divider \u2014 the\n toolbar's bottom border \u2014 instead of two stacked, separated boxes. */\n.richclay-toolbar:has(+ .richclay-editor) {\n margin-block-end: 0;\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.richclay-toolbar + .richclay-editor {\n border-top: 0;\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n/* In a fused card, draw the focus ring inside the body so it never crosses\n the toolbar divider. */\n.richclay-toolbar + .richclay-editor:focus-visible {\n outline-offset: -3px;\n}\n\n.richclay-button {\n align-items: center;\n appearance: none;\n background: transparent;\n border: 1px solid transparent;\n border-radius: 5px;\n color: currentColor;\n display: inline-flex;\n height: var(--richclay-control-size);\n justify-content: center;\n margin: 0;\n min-width: var(--richclay-control-size);\n padding: 0;\n position: relative;\n}\n\n.richclay-button:hover,\n.richclay-button[aria-expanded=\"true\"],\n.richclay-menu-item:hover {\n background: var(--richclay-surface);\n border-color: var(--richclay-border);\n}\n\n.richclay-button.is-active,\n.richclay-button[aria-pressed=\"true\"],\n.richclay-menu-item.is-active {\n background: rgb(0 0 0 / 0.13);\n border-color: rgb(0 0 0 / 0.22);\n color: var(--richclay-text);\n box-shadow: inset 0 1px 3px rgb(0 0 0 / 0.26);\n}\n\n.richclay-button:focus-visible,\n.richclay-menu-item:focus-visible,\n.richclay-editor:focus-visible,\n.richclay-input:focus-visible,\n.richclay-primary:focus-visible,\n.richclay-secondary:focus-visible {\n outline: 3px solid var(--richclay-focus);\n outline-offset: 2px;\n}\n\n.richclay-button:disabled {\n color: var(--richclay-muted);\n cursor: not-allowed;\n opacity: 0.55;\n}\n\n.richclay-separator {\n align-self: stretch;\n background: var(--richclay-border);\n display: inline-block;\n margin: 5px 4px;\n width: 1px;\n}\n\n.richclay-menu-wrap {\n display: inline-flex;\n position: relative;\n}\n\n.richclay-menu {\n background: var(--richclay-surface);\n border: 1px solid var(--richclay-border);\n border-radius: var(--richclay-radius);\n box-shadow: var(--richclay-shadow);\n color: var(--richclay-text);\n display: grid;\n gap: 2px;\n inset-block-start: calc(100% + 4px);\n inset-inline-start: 0;\n min-width: 160px;\n padding: 4px;\n position: absolute;\n z-index: 1000;\n}\n\n.richclay-menu[hidden] {\n display: none;\n}\n\n.richclay-menu-item {\n align-items: center;\n appearance: none;\n background: transparent;\n border: 1px solid transparent;\n border-radius: 4px;\n color: inherit;\n display: flex;\n font: inherit;\n gap: 8px;\n min-height: 32px;\n padding: 5px 9px;\n text-align: start;\n}\n\n.richclay-editor {\n background: var(--richclay-surface);\n border: 1px solid var(--richclay-border);\n border-radius: var(--richclay-radius);\n color: var(--richclay-text);\n min-height: 9rem;\n /* Break a long unbreakable string (e.g. a bare URL) in prose rather than let\n it widen the editor. `anywhere` also lowers min-content, so the editor can't\n grow even as a flex/grid item. Inert on the code <pre> (white-space: pre). */\n overflow-wrap: anywhere;\n padding: 12px;\n position: relative;\n}\n\n.richclay-editor.richclay-empty::before {\n color: var(--richclay-muted);\n content: attr(data-richclay-placeholder);\n inset-block-start: 12px;\n inset-inline-start: 12px;\n pointer-events: none;\n position: absolute;\n}\n\n.richclay-editor p,\n.richclay-editor h1,\n.richclay-editor h2,\n.richclay-editor h3,\n.richclay-editor h4,\n.richclay-editor h5,\n.richclay-editor h6,\n.richclay-editor blockquote,\n.richclay-editor pre,\n.richclay-editor ul,\n.richclay-editor ol {\n margin-block: 0 0.75em;\n}\n\n.richclay-editor > :last-child {\n margin-block-end: 0;\n}\n\n/* Inline editors ([editable]) inherit the page's own styling. richclay adds\n only a caret, a discoverability outline, and empty-state affordances. */\n.richclay-inline {\n caret-color: currentColor;\n}\n\n.richclay-inline:hover {\n outline: 1px dashed var(--richclay-border);\n outline-offset: 2px;\n}\n\n.richclay-inline.richclay-focused,\n.richclay-inline:focus-visible {\n outline: 2px dashed var(--richclay-focus);\n outline-offset: 2px;\n}\n\n.richclay-inline.richclay-empty {\n min-height: 1em;\n min-width: 1ch;\n}\n\n.richclay-inline.richclay-empty::before {\n color: var(--richclay-muted);\n content: attr(data-richclay-placeholder);\n pointer-events: none;\n}\n\n/* Floating toolbar shell for inline editors. Fixed-position and body-mounted:\n fixed elements never create scrollbars, and body mounting avoids transformed\n ancestors silently turning fixed into ancestor-relative. */\n.richclay-float {\n left: 0;\n position: fixed;\n top: 0;\n will-change: transform;\n z-index: 99999;\n}\n\n.richclay-float .richclay-toolbar {\n box-shadow: var(--richclay-shadow);\n margin-block: 0;\n}\n\n/* Compact vertical rail for narrow margins. */\n.richclay-float-rail .richclay-toolbar {\n --richclay-control-size: 28px;\n flex-direction: column;\n flex-wrap: nowrap;\n}\n\n.richclay-float-rail .richclay-toolbar svg {\n height: 15px;\n width: 15px;\n}\n\n.richclay-float-rail .richclay-separator {\n align-self: stretch;\n height: 1px;\n margin: 4px 5px;\n width: auto;\n}\n\n.richclay-float-rail .richclay-menu {\n inset-block-start: 0;\n inset-inline-start: calc(100% + 4px);\n}\n\n.richclay-editor blockquote {\n border-inline-start: 3px solid var(--richclay-border);\n color: var(--richclay-muted);\n margin-inline: 0;\n padding-inline-start: 12px;\n}\n\n/* Code blocks scroll horizontally inside the editor instead of widening it.\n `width: 0; min-width: 100%` zeroes the pre's intrinsic-width contribution so a\n long unbreakable line can't push an intrinsically-sized ancestor (flex/grid\n item, inline-block, table cell) wider, while still filling the editor and\n scrolling its own overflow. box-sizing keeps padding inside that 100%.\n Containment applies to inline editors too: `white-space: pre` never wraps, so\n without it one code block runs off the side of the page. Only the decoration\n below stays card-scoped, since inline mode keeps the page's own typography. */\n.richclay-editor pre,\n.richclay-inline pre {\n box-sizing: border-box;\n min-width: 100%;\n overflow: auto;\n width: 0;\n}\n\n.richclay-editor pre {\n background: var(--richclay-surface-alt);\n border-radius: 4px;\n padding: 10px;\n}\n\n.richclay-dialog {\n background: var(--richclay-surface);\n border: 1px solid var(--richclay-border);\n border-radius: var(--richclay-radius);\n box-shadow: var(--richclay-shadow);\n color: var(--richclay-text);\n display: grid;\n gap: 10px;\n box-sizing: border-box;\n width: min(420px, calc(100vw - 16px));\n z-index: 99999;\n padding: 12px;\n}\n\n.richclay-dialog-title {\n font-weight: 650;\n}\n\n.richclay-dialog.richclay-link-popover {\n align-items: center;\n display: flex;\n gap: 6px;\n max-width: min(420px, calc(100vw - 16px));\n padding: 6px;\n width: auto;\n}\n\n.richclay-link-address {\n color: inherit;\n font-size: 0.875rem;\n max-width: 260px;\n overflow: hidden;\n padding: 0 6px;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.richclay-link-popover .richclay-secondary {\n min-height: 30px;\n padding: 3px 9px;\n}\n\n.richclay-field {\n display: grid;\n gap: 4px;\n}\n\n.richclay-field span {\n color: var(--richclay-muted);\n font-size: 0.875rem;\n}\n\n.richclay-input {\n background: var(--richclay-surface);\n border: 1px solid var(--richclay-border);\n border-radius: 4px;\n color: var(--richclay-text);\n font: inherit;\n min-height: 36px;\n padding: 6px 8px;\n}\n\n.richclay-dialog-actions {\n display: flex;\n gap: 8px;\n justify-content: flex-end;\n}\n\n.richclay-dialog-actions [data-richclay-remove-link] {\n margin-inline-end: auto;\n}\n\n.richclay-primary,\n.richclay-secondary {\n appearance: none;\n border: 1px solid var(--richclay-border);\n border-radius: 4px;\n font: inherit;\n min-height: 34px;\n padding: 5px 10px;\n}\n\n.richclay-primary {\n background: var(--richclay-accent);\n border-color: var(--richclay-accent);\n color: var(--richclay-accent-ink);\n}\n\n.richclay-secondary {\n background: var(--richclay-surface-alt);\n color: var(--richclay-text);\n}\n\n.richclay-sr-only {\n border: 0;\n clip: rect(0 0 0 0);\n clip-path: inset(50%);\n height: 1px;\n margin: -1px;\n overflow: hidden;\n padding: 0;\n position: absolute;\n white-space: nowrap;\n width: 1px;\n}\n\n@media (forced-colors: active) {\n .richclay-toolbar,\n .richclay-editor,\n .richclay-menu,\n .richclay-dialog,\n .richclay-input {\n border-color: CanvasText;\n }\n\n .richclay-button.is-active,\n .richclay-button[aria-pressed=\"true\"],\n .richclay-menu-item.is-active,\n .richclay-primary {\n background: Highlight;\n color: HighlightText;\n }\n\n .richclay-button:focus-visible,\n .richclay-menu-item:focus-visible,\n .richclay-editor:focus-visible,\n .richclay-input:focus-visible {\n outline-color: Highlight;\n }\n}\n";
|
|
2977
3252
|
|
|
2978
3253
|
// src/browser-global.js
|
|
2979
3254
|
setRichClayStyles(richclay_default);
|