@citisen/litearea 0.1.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.
Files changed (109) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +514 -0
  3. package/README.zh.md +362 -0
  4. package/dist/grammars.cjs +1228 -0
  5. package/dist/grammars.cjs.map +1 -0
  6. package/dist/grammars.js +1213 -0
  7. package/dist/grammars.js.map +1 -0
  8. package/dist/index.cjs +3103 -0
  9. package/dist/index.cjs.map +1 -0
  10. package/dist/index.js +3040 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/react.cjs +3032 -0
  13. package/dist/react.cjs.map +1 -0
  14. package/dist/react.js +3010 -0
  15. package/dist/react.js.map +1 -0
  16. package/dist/styles.cjs +453 -0
  17. package/dist/styles.cjs.map +1 -0
  18. package/dist/styles.css +432 -0
  19. package/dist/styles.js +447 -0
  20. package/dist/styles.js.map +1 -0
  21. package/dist/types/core/complete.d.ts +70 -0
  22. package/dist/types/core/complete.d.ts.map +1 -0
  23. package/dist/types/core/format.d.ts +34 -0
  24. package/dist/types/core/format.d.ts.map +1 -0
  25. package/dist/types/core/grammar.d.ts +14 -0
  26. package/dist/types/core/grammar.d.ts.map +1 -0
  27. package/dist/types/core/hover.d.ts +23 -0
  28. package/dist/types/core/hover.d.ts.map +1 -0
  29. package/dist/types/core/index.d.ts +12 -0
  30. package/dist/types/core/index.d.ts.map +1 -0
  31. package/dist/types/core/inspect.d.ts +34 -0
  32. package/dist/types/core/inspect.d.ts.map +1 -0
  33. package/dist/types/core/rank.d.ts +82 -0
  34. package/dist/types/core/rank.d.ts.map +1 -0
  35. package/dist/types/core/scan.d.ts +51 -0
  36. package/dist/types/core/scan.d.ts.map +1 -0
  37. package/dist/types/core/segments.d.ts +44 -0
  38. package/dist/types/core/segments.d.ts.map +1 -0
  39. package/dist/types/core/text.d.ts +113 -0
  40. package/dist/types/core/text.d.ts.map +1 -0
  41. package/dist/types/core/types.d.ts +604 -0
  42. package/dist/types/core/types.d.ts.map +1 -0
  43. package/dist/types/core/vocabulary.d.ts +82 -0
  44. package/dist/types/core/vocabulary.d.ts.map +1 -0
  45. package/dist/types/dom/create.d.ts +17 -0
  46. package/dist/types/dom/create.d.ts.map +1 -0
  47. package/dist/types/dom/editing.d.ts +89 -0
  48. package/dist/types/dom/editing.d.ts.map +1 -0
  49. package/dist/types/dom/editor.d.ts +366 -0
  50. package/dist/types/dom/editor.d.ts.map +1 -0
  51. package/dist/types/dom/index.d.ts +9 -0
  52. package/dist/types/dom/index.d.ts.map +1 -0
  53. package/dist/types/dom/mirror.d.ts +107 -0
  54. package/dist/types/dom/mirror.d.ts.map +1 -0
  55. package/dist/types/dom/overlay.d.ts +52 -0
  56. package/dist/types/dom/overlay.d.ts.map +1 -0
  57. package/dist/types/dom/popup.d.ts +95 -0
  58. package/dist/types/dom/popup.d.ts.map +1 -0
  59. package/dist/types/dom/support.d.ts +41 -0
  60. package/dist/types/dom/support.d.ts.map +1 -0
  61. package/dist/types/dom/tooltip.d.ts +39 -0
  62. package/dist/types/dom/tooltip.d.ts.map +1 -0
  63. package/dist/types/grammars/dshFont.d.ts +127 -0
  64. package/dist/types/grammars/dshFont.d.ts.map +1 -0
  65. package/dist/types/grammars/dshSentry.d.ts +84 -0
  66. package/dist/types/grammars/dshSentry.d.ts.map +1 -0
  67. package/dist/types/grammars/index.d.ts +3 -0
  68. package/dist/types/grammars/index.d.ts.map +1 -0
  69. package/dist/types/index.d.ts +15 -0
  70. package/dist/types/index.d.ts.map +1 -0
  71. package/dist/types/react/index.d.ts +91 -0
  72. package/dist/types/react/index.d.ts.map +1 -0
  73. package/dist/types/styles.d.ts +29 -0
  74. package/dist/types/styles.d.ts.map +1 -0
  75. package/docs/architecture.md +316 -0
  76. package/docs/completion.md +320 -0
  77. package/docs/grammar.md +823 -0
  78. package/package.json +105 -0
  79. package/scripts/browser-check.mjs +838 -0
  80. package/scripts/build-css.mjs +35 -0
  81. package/scripts/release.mjs +91 -0
  82. package/scripts/verify-package.mjs +253 -0
  83. package/src/core/complete.ts +286 -0
  84. package/src/core/format.ts +71 -0
  85. package/src/core/grammar.ts +40 -0
  86. package/src/core/hover.ts +129 -0
  87. package/src/core/index.ts +98 -0
  88. package/src/core/inspect.ts +198 -0
  89. package/src/core/rank.ts +317 -0
  90. package/src/core/scan.ts +720 -0
  91. package/src/core/segments.ts +185 -0
  92. package/src/core/text.ts +238 -0
  93. package/src/core/types.ts +681 -0
  94. package/src/core/vocabulary.ts +196 -0
  95. package/src/dom/create.ts +31 -0
  96. package/src/dom/editing.ts +213 -0
  97. package/src/dom/editor.ts +1143 -0
  98. package/src/dom/index.ts +46 -0
  99. package/src/dom/mirror.ts +305 -0
  100. package/src/dom/overlay.ts +106 -0
  101. package/src/dom/popup.ts +323 -0
  102. package/src/dom/support.ts +88 -0
  103. package/src/dom/tooltip.ts +112 -0
  104. package/src/grammars/dshFont.ts +1004 -0
  105. package/src/grammars/dshSentry.ts +742 -0
  106. package/src/grammars/index.ts +57 -0
  107. package/src/index.ts +122 -0
  108. package/src/react/index.tsx +248 -0
  109. package/src/styles.ts +529 -0
package/src/styles.ts ADDED
@@ -0,0 +1,529 @@
1
+ // ─── styles: the one stylesheet ─────────────────────────────────────────────
2
+ //
3
+ // The CSS lives in a TypeScript string so it can be injected with no build step
4
+ // and no CSS loader, which is what keeps the library usable from a plain script
5
+ // tag. `scripts/build-css.mjs` also writes it out as `dist/styles.css` for the
6
+ // hosts that would rather link it, from this same string, so the two can never
7
+ // disagree.
8
+ //
9
+ // The alignment contract
10
+ // ----------------------
11
+ // The painted layer sits behind the real textarea and draws the same characters, so it may
12
+ // change only what does not move a glyph: colour, background, and text-decoration. Anything
13
+ // that changes an advance — a different font, a weight, letter-spacing, a font-feature —
14
+ // slides the paint off the character it belongs to, and by a different amount on every
15
+ // character, so it reads as a rendering glitch rather than as a styling mistake.
16
+ //
17
+ // The two elements therefore share ONE rule for their typography, and that rule is the only
18
+ // thing that has to hold. THE FONT ITSELF IS THE HOST'S CHOICE: a proportional face aligns just
19
+ // as well as a monospace one, because the layer, the field, and the measuring mirror all read
20
+ // the same computed font. `--litearea-font` is a variable like any other.
21
+ //
22
+ // What is NOT the host's choice is ligatures and kerning, which are forced off, and the reason
23
+ // is subtler than "a ligature would look wrong". A ligature draws one glyph where the field
24
+ // holds two characters — and the layer splits its spans wherever a diagnostic or a decoration
25
+ // begins, which cuts a ligature in half. The field would then render one glyph and the layer
26
+ // two, at different widths, and the rest of the line would slide. Turning them off removes the
27
+ // possibility rather than relying on no span boundary ever landing inside a pair.
28
+
29
+ /**
30
+ * One colour per scope, in one list, because the variables and the rules that use them MUST
31
+ * agree and once did not.
32
+ *
33
+ * Twenty-nine scope variables were declared here with no rule anywhere that consumed them. The
34
+ * tokenizer painted the right classes, the variables were documented, the stylesheet looked
35
+ * complete — and every scope rendered in the inherited text colour, so the editor had no syntax
36
+ * colouring at all. Nothing in the DOM revealed it: the spans were correct. Generating both
37
+ * halves from this list is the fix, and it is the kind of drift a browser check cannot catch
38
+ * either, since a colourless token is a perfectly ordinary thing to find.
39
+ *
40
+ * A key is the FOLDED scope name, which is what a scope becomes as a class: `value.shape` is
41
+ * written `value-shape` here because `scopeClass` turns its dots into hyphens.
42
+ */
43
+ const SCOPE_PALETTE: ReadonlyArray<{ scope: string; light: string; dark?: string }> = [
44
+ { scope: 'text', light: 'var(--litearea-fg)' },
45
+ { scope: 'word', light: 'var(--litearea-fg)' },
46
+ { scope: 'family', light: 'var(--litearea-fg)' },
47
+ { scope: 'family-generic', light: '#7c3aed', dark: '#c4a2ff' },
48
+ { scope: 'family-unknown', light: 'var(--litearea-warning)' },
49
+ { scope: 'family-unclosed', light: 'var(--litearea-error)' },
50
+ { scope: 'weight', light: 'var(--litearea-accent)' },
51
+ { scope: 'weight-missing', light: 'var(--litearea-warning)' },
52
+ { scope: 'state', light: 'var(--litearea-accent)' },
53
+ { scope: 'property', light: '#7c3aed', dark: '#c4a2ff' },
54
+ { scope: 'operator', light: 'var(--litearea-fg-dim)' },
55
+ { scope: 'separator', light: 'var(--litearea-fg-dim)' },
56
+ { scope: 'value-shape', light: '#0f766e', dark: '#5eead4' },
57
+ { scope: 'value-color', light: '#0f766e', dark: '#5eead4' },
58
+ { scope: 'value-pattern', light: '#0f766e', dark: '#5eead4' },
59
+ { scope: 'value-motion', light: '#0f766e', dark: '#5eead4' },
60
+ { scope: 'value-number', light: '#b45309', dark: '#fbbf24' },
61
+ { scope: 'comment', light: 'var(--litearea-fg-dim)' },
62
+ { scope: 'invalid', light: 'var(--litearea-error)' },
63
+ { scope: 'keyword', light: 'var(--litearea-accent)' },
64
+ { scope: 'string', light: '#0f766e', dark: '#5eead4' },
65
+ { scope: 'number', light: '#b45309', dark: '#fbbf24' },
66
+ ]
67
+
68
+ /**
69
+ * The variable declarations for one colour scheme.
70
+ * @param scheme - `light` declares every scope; `dark` declares only those that differ.
71
+ * @returns one CSS declaration per line.
72
+ */
73
+ function scopeVariables(scheme: 'light' | 'dark'): string {
74
+ return SCOPE_PALETTE.filter((entry) => scheme === 'light' || entry.dark !== undefined)
75
+ .map((entry) => {
76
+ const value = scheme === 'light' ? entry.light : entry.dark
77
+ return ` --litearea-scope-${entry.scope}: ${String(value)};`
78
+ })
79
+ .join('\n')
80
+ }
81
+
82
+ /**
83
+ * The rules that spend those variables.
84
+ * @returns one CSS rule per line.
85
+ */
86
+ function scopeRules(): string {
87
+ return SCOPE_PALETTE.map(
88
+ (entry) =>
89
+ `.litearea-scope-${entry.scope} { color: var(--litearea-scope-${entry.scope}); }`,
90
+ ).join('\n')
91
+ }
92
+
93
+ /** Everything a scope or a mark is painted with comes from one of these. */
94
+ export const LITEAREA_STYLES = `
95
+ .litearea {
96
+ --litearea-font: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
97
+ --litearea-font-size: 13px;
98
+ --litearea-line-height: 20px;
99
+ --litearea-padding-block: 6px;
100
+ --litearea-padding-inline: 10px;
101
+ --litearea-radius: 8px;
102
+ --litearea-fg: #1f2328;
103
+ --litearea-fg-dim: #6b7280;
104
+ --litearea-fg-strong: #111827;
105
+ --litearea-bg: #ffffff;
106
+ --litearea-bg-raised: #ffffff;
107
+ --litearea-border: #d8dbe0;
108
+ --litearea-border-focus: #4d6bfe;
109
+ --litearea-accent: #4d6bfe;
110
+ --litearea-accent-soft: rgba(77, 107, 254, 0.12);
111
+ --litearea-selection: rgba(77, 107, 254, 0.22);
112
+ --litearea-error: #e5484d;
113
+ --litearea-warning: #d97706;
114
+ --litearea-info: #4d6bfe;
115
+ --litearea-hint: #8b8f97;
116
+ --litearea-shadow: 0 6px 24px rgba(15, 23, 42, 0.14);
117
+
118
+ ${scopeVariables('light')}
119
+
120
+ position: relative;
121
+ display: block;
122
+ color: var(--litearea-fg);
123
+ }
124
+
125
+ @media (prefers-color-scheme: dark) {
126
+ .litearea {
127
+ --litearea-fg: #e6e8eb;
128
+ --litearea-fg-dim: #8b919b;
129
+ --litearea-fg-strong: #ffffff;
130
+ --litearea-bg: #1b1e24;
131
+ --litearea-bg-raised: #23262c;
132
+ --litearea-border: #363b44;
133
+ ${scopeVariables('dark')}
134
+ --litearea-error: #ff6b6b;
135
+ --litearea-warning: #f59e0b;
136
+ --litearea-shadow: 0 6px 24px rgba(0, 0, 0, 0.5);
137
+ }
138
+ }
139
+
140
+ /* ── the box ─────────────────────────────────────────────────────────────── */
141
+
142
+ .litearea-box {
143
+ position: relative;
144
+ display: block;
145
+ border: 1px solid var(--litearea-border);
146
+ border-radius: var(--litearea-radius);
147
+ background: var(--litearea-bg);
148
+ transition: border-color 120ms ease;
149
+ }
150
+
151
+ .litearea-box:focus-within {
152
+ border-color: var(--litearea-border-focus);
153
+ }
154
+
155
+ .litearea-invalid .litearea-box {
156
+ border-color: var(--litearea-error);
157
+ }
158
+
159
+ /*
160
+ * The layer and the field share this rule and nothing may be added to one alone.
161
+ * Every property here is a property the mirror copies too — three elements, one
162
+ * typography, or the paint drifts.
163
+ */
164
+ .litearea-layer,
165
+ .litearea-input {
166
+ box-sizing: border-box;
167
+ width: 100%;
168
+ margin: 0;
169
+ padding: var(--litearea-padding-block) var(--litearea-padding-inline);
170
+ border: none;
171
+ font-family: var(--litearea-font);
172
+ font-size: var(--litearea-font-size);
173
+ font-weight: 400;
174
+ font-style: normal;
175
+ font-stretch: normal;
176
+ font-variant-ligatures: none;
177
+ font-kerning: none;
178
+ font-feature-settings: "liga" 0, "calt" 0, "dlig" 0;
179
+ line-height: var(--litearea-line-height);
180
+ letter-spacing: normal;
181
+ word-spacing: normal;
182
+ text-transform: none;
183
+ text-indent: 0;
184
+ text-align: left;
185
+ direction: ltr;
186
+ tab-size: 2;
187
+ white-space: pre-wrap;
188
+ overflow-wrap: break-word;
189
+ word-break: break-word;
190
+ }
191
+
192
+ .litearea-layer {
193
+ position: absolute;
194
+ inset: 0;
195
+ overflow: hidden;
196
+ pointer-events: none;
197
+ user-select: none;
198
+ }
199
+
200
+ /*
201
+ * When the box is clamped to its maximum height the field grows a scrollbar, and a
202
+ * scrollbar narrows the text. If the layer kept the full width it would wrap
203
+ * differently from the field and every colour would slide off its character, so the
204
+ * field's measured scrollbar width is published as --litearea-scrollbar and added
205
+ * here. The value is 0 whenever no scrollbar is shown.
206
+ */
207
+ .litearea-layer {
208
+ padding-right: calc(var(--litearea-padding-inline) + var(--litearea-scrollbar, 0px));
209
+ }
210
+
211
+ .litearea-paint {
212
+ position: relative;
213
+ min-height: 100%;
214
+ /* A zero-width space keeps the layer's last line box as tall as the field's:
215
+ a trailing newline would otherwise collapse in the paint alone, and the box
216
+ would jump the moment one was typed. */
217
+ will-change: transform;
218
+ }
219
+
220
+ .litearea-input {
221
+ position: relative;
222
+ display: block;
223
+ resize: none;
224
+ background: transparent;
225
+ color: transparent;
226
+ caret-color: var(--litearea-fg);
227
+ outline: none;
228
+ overflow-y: hidden;
229
+ }
230
+
231
+ .litearea-growable .litearea-input {
232
+ resize: none;
233
+ }
234
+
235
+ .litearea-resizable .litearea-input {
236
+ resize: vertical;
237
+ }
238
+
239
+ .litearea-input::placeholder {
240
+ color: var(--litearea-fg-dim);
241
+ }
242
+
243
+ .litearea-input::selection {
244
+ background: var(--litearea-selection);
245
+ }
246
+
247
+ .litearea-readonly .litearea-input {
248
+ caret-color: transparent;
249
+ }
250
+
251
+ /*
252
+ * The rules that spend the variables, generated from the same list that declares them. There is
253
+ * deliberately NO catch-all rule setting a property on every painted span: there was one, and a
254
+ * class plus an element outranks a bare class on specificity, so it silently switched off every
255
+ * squiggle in the library.
256
+ *
257
+ * A scope that is not in the list gets no colour from here, and a host writing a grammar of its
258
+ * own styles it with a plain rule — .litearea-scope-my-thing { color: … } — since nothing in
259
+ * this file competes for that selector.
260
+ */
261
+ ${scopeRules()}
262
+
263
+ .litearea-dec-effective {
264
+ border-radius: 3px;
265
+ background: var(--litearea-accent-soft);
266
+ box-shadow: 0 0 0 1px var(--litearea-accent-soft);
267
+ }
268
+
269
+ /* The four shapes a diagnostic can take. Wavy for the two that mean "fix this", dotted for
270
+ the two that mean "worth knowing" — the same distinction VSCode draws, and the reason
271
+ severity is a class rather than an inline colour. */
272
+ .litearea-diag-error {
273
+ text-decoration-line: underline;
274
+ text-decoration-style: wavy;
275
+ text-decoration-color: var(--litearea-error);
276
+ text-decoration-skip-ink: none;
277
+ text-underline-offset: 3px;
278
+ }
279
+
280
+ .litearea-diag-warning {
281
+ text-decoration-line: underline;
282
+ text-decoration-style: wavy;
283
+ text-decoration-color: var(--litearea-warning);
284
+ text-decoration-skip-ink: none;
285
+ text-underline-offset: 3px;
286
+ }
287
+
288
+ .litearea-diag-info {
289
+ text-decoration-line: underline;
290
+ text-decoration-style: dotted;
291
+ text-decoration-color: var(--litearea-info);
292
+ text-decoration-skip-ink: none;
293
+ text-underline-offset: 3px;
294
+ }
295
+
296
+ .litearea-diag-hint {
297
+ text-decoration-line: underline;
298
+ text-decoration-style: dotted;
299
+ text-decoration-color: var(--litearea-hint);
300
+ text-decoration-skip-ink: none;
301
+ text-underline-offset: 3px;
302
+ }
303
+
304
+ /* ── the completion list ─────────────────────────────────────────────────── */
305
+
306
+ /*
307
+ * The container holds two things and scrolls NEITHER of them: the list scrolls itself, and the
308
+ * documentation is pinned below it. Putting the documentation inside the scroll range — which is
309
+ * what this used to do — makes it unreachable with a long list and a long explanation: the
310
+ * arrows move the active row rather than the scrollbar, so a keyboard user never sees it, and a
311
+ * mouse user has to scroll down to read it and back up to reach the next row.
312
+ */
313
+ .litearea-popup {
314
+ position: absolute;
315
+ z-index: 30;
316
+ display: none;
317
+ flex-direction: column;
318
+ max-width: 460px;
319
+ padding: 4px;
320
+ border: 1px solid var(--litearea-border);
321
+ border-radius: 10px;
322
+ background: var(--litearea-bg-raised);
323
+ box-shadow: var(--litearea-shadow);
324
+ font-family: var(--litearea-font);
325
+ font-size: 12px;
326
+ line-height: 18px;
327
+ color: var(--litearea-fg);
328
+ overflow: hidden;
329
+ }
330
+
331
+ .litearea-popup[data-open="true"] {
332
+ display: flex;
333
+ }
334
+
335
+ /* The rows, and the only part that scrolls. Its height is bounded so the documentation below
336
+ always has somewhere to live. */
337
+ .litearea-list {
338
+ min-height: 0;
339
+ max-height: 208px;
340
+ overflow-y: auto;
341
+ }
342
+
343
+ /*
344
+ * The explanation, pinned. Its own height is bounded and it scrolls ITSELF, so a long
345
+ * explanation stays readable while the rows stay put — and arrowing to the next row swaps the
346
+ * text in place instead of requiring a scroll back up.
347
+ */
348
+ .litearea-docs {
349
+ flex: none;
350
+ max-height: 132px;
351
+ overflow-y: auto;
352
+ margin: 4px -4px -4px;
353
+ padding: 6px 10px;
354
+ border-top: 1px solid var(--litearea-border);
355
+ background: var(--litearea-bg);
356
+ border-radius: 0 0 10px 10px;
357
+ color: var(--litearea-fg);
358
+ white-space: pre-wrap;
359
+ }
360
+
361
+ .litearea-popup[data-docs="false"] .litearea-docs {
362
+ display: none;
363
+ }
364
+
365
+ .litearea-row {
366
+ display: flex;
367
+ align-items: baseline;
368
+ gap: 8px;
369
+ padding: 3px 6px;
370
+ border-radius: 6px;
371
+ cursor: pointer;
372
+ white-space: nowrap;
373
+ overflow: hidden;
374
+ }
375
+
376
+ .litearea-row[aria-selected="true"] {
377
+ background: var(--litearea-accent-soft);
378
+ }
379
+
380
+ .litearea-rowKind {
381
+ flex: none;
382
+ width: 7px;
383
+ height: 7px;
384
+ border-radius: 2px;
385
+ background: var(--litearea-fg-dim);
386
+ transform: translateY(-1px);
387
+ }
388
+
389
+ .litearea-kind-family { background: var(--litearea-scope-family-generic); }
390
+ .litearea-kind-generic { background: var(--litearea-scope-family-generic); }
391
+ .litearea-kind-weight { background: var(--litearea-scope-weight); }
392
+ .litearea-kind-state { background: var(--litearea-scope-state); }
393
+ .litearea-kind-property { background: var(--litearea-scope-property); }
394
+ .litearea-kind-value { background: var(--litearea-scope-value-shape); }
395
+ .litearea-kind-number { background: var(--litearea-scope-value-number); }
396
+ .litearea-kind-custom { background: var(--litearea-fg-dim); }
397
+
398
+ .litearea-rowLabel {
399
+ flex: 1 1 auto;
400
+ min-width: 0;
401
+ overflow: hidden;
402
+ text-overflow: ellipsis;
403
+ }
404
+
405
+ .litearea-rowMatch {
406
+ color: var(--litearea-accent);
407
+ font-weight: 600;
408
+ }
409
+
410
+ .litearea-rowDetail {
411
+ flex: none;
412
+ color: var(--litearea-fg-dim);
413
+ font-size: 11px;
414
+ }
415
+
416
+ .litearea-docsTitle {
417
+ font-weight: 600;
418
+ }
419
+
420
+ .litearea-docsDetail {
421
+ color: var(--litearea-fg-dim);
422
+ }
423
+
424
+ .litearea-docsBody {
425
+ margin-top: 2px;
426
+ color: var(--litearea-fg-dim);
427
+ }
428
+
429
+ /* ── the hover tooltip ───────────────────────────────────────────────────── */
430
+
431
+ .litearea-tooltip {
432
+ position: absolute;
433
+ z-index: 40;
434
+ display: none;
435
+ max-width: 340px;
436
+ padding: 6px 10px;
437
+ border: 1px solid var(--litearea-border);
438
+ border-radius: 8px;
439
+ background: var(--litearea-bg-raised);
440
+ box-shadow: var(--litearea-shadow);
441
+ font-family: var(--litearea-font);
442
+ font-size: 11px;
443
+ line-height: 16px;
444
+ color: var(--litearea-fg);
445
+ pointer-events: none;
446
+ white-space: pre-wrap;
447
+ }
448
+
449
+ .litearea-tooltip[data-open="true"] {
450
+ display: block;
451
+ }
452
+
453
+ .litearea-tooltipTitle {
454
+ font-weight: 600;
455
+ }
456
+
457
+ .litearea-tooltipDetail {
458
+ color: var(--litearea-fg-dim);
459
+ }
460
+
461
+ .litearea-tooltipBody {
462
+ margin-top: 3px;
463
+ color: var(--litearea-fg-dim);
464
+ }
465
+
466
+ .litearea-srOnly {
467
+ position: absolute;
468
+ width: 1px;
469
+ height: 1px;
470
+ padding: 0;
471
+ margin: -1px;
472
+ overflow: hidden;
473
+ clip: rect(0, 0, 0, 0);
474
+ white-space: nowrap;
475
+ border: 0;
476
+ }
477
+ `.trim()
478
+
479
+ /**
480
+ * Put the stylesheet in a document, once.
481
+ *
482
+ * Marked with a data attribute so two editors on one page, or two copies of the
483
+ * library, do not stack two identical sheets — and so a host can find and replace
484
+ * it.
485
+ * @param ownerDocument - the document to install into.
486
+ * @param nonce - an optional CSP nonce for the style element.
487
+ * @returns the style element, or undefined when there is no document.
488
+ */
489
+ export function injectStyles(
490
+ ownerDocument: Document,
491
+ nonce?: string,
492
+ ): HTMLStyleElement | undefined {
493
+ if (ownerDocument === null || ownerDocument === undefined) return undefined
494
+ const existing = ownerDocument.querySelector<HTMLStyleElement>('style[data-litearea-styles]')
495
+ if (existing !== null) return existing
496
+ const style = ownerDocument.createElement('style')
497
+ style.dataset.liteareaStyles = ''
498
+ if (nonce !== undefined) style.nonce = nonce
499
+ style.textContent = LITEAREA_STYLES
500
+ const head = ownerDocument.head ?? ownerDocument.documentElement
501
+ if (head === null || head === undefined) return undefined
502
+ head.appendChild(style)
503
+ return style
504
+ }
505
+
506
+ /**
507
+ * The class a scope is painted with.
508
+ *
509
+ * Dots and other punctuation are folded to hyphens, because a scope like
510
+ * `value.shape` is a nice name to write and a terrible one to select: the CSS
511
+ * would need a backslash before the dot at every use, and one forgotten escape
512
+ * silently paints nothing.
513
+ * @param scope - the scope name.
514
+ * @returns the class name.
515
+ */
516
+ export function scopeClass(scope: string): string {
517
+ const folded = scope.replace(/[^A-Za-z0-9_-]+/g, '-').replace(/^-+|-+$/g, '')
518
+ return folded === '' ? 'litearea-scope-text' : `litearea-scope-${folded}`
519
+ }
520
+
521
+ /** The class a decoration kind is painted with. */
522
+ export function decorationClass(kind: string): string {
523
+ return `litearea-dec-${kind.replace(/[^A-Za-z0-9_-]+/g, '-')}`
524
+ }
525
+
526
+ /** The class a diagnostic severity is underlined with. */
527
+ export function severityClass(severity: string): string {
528
+ return `litearea-diag-${severity.replace(/[^A-Za-z0-9_-]+/g, '-')}`
529
+ }