@openleaf-editor/ui 0.1.0-beta.2 → 0.1.0-beta.4

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 (78) hide show
  1. package/README.md +85 -0
  2. package/dist/block-type.d.ts +5 -0
  3. package/dist/block-type.d.ts.map +1 -0
  4. package/dist/block-type.js +133 -0
  5. package/dist/block-type.js.map +1 -0
  6. package/dist/content-css.d.ts +18 -0
  7. package/dist/content-css.d.ts.map +1 -1
  8. package/dist/content-css.js +231 -16
  9. package/dist/content-css.js.map +1 -1
  10. package/dist/css.d.ts +23 -0
  11. package/dist/css.d.ts.map +1 -0
  12. package/dist/css.js +846 -0
  13. package/dist/css.js.map +1 -0
  14. package/dist/dialog.d.ts +93 -0
  15. package/dist/dialog.d.ts.map +1 -1
  16. package/dist/dialog.js +414 -86
  17. package/dist/dialog.js.map +1 -1
  18. package/dist/floating.d.ts +4 -0
  19. package/dist/floating.d.ts.map +1 -1
  20. package/dist/floating.js +50 -2
  21. package/dist/floating.js.map +1 -1
  22. package/dist/help.d.ts +1 -1
  23. package/dist/help.d.ts.map +1 -1
  24. package/dist/help.js +29 -5
  25. package/dist/help.js.map +1 -1
  26. package/dist/i18n.d.ts +8 -0
  27. package/dist/i18n.d.ts.map +1 -1
  28. package/dist/i18n.js +32 -7
  29. package/dist/i18n.js.map +1 -1
  30. package/dist/icons.d.ts +7 -1
  31. package/dist/icons.d.ts.map +1 -1
  32. package/dist/icons.js +6 -2
  33. package/dist/icons.js.map +1 -1
  34. package/dist/index.d.ts +10 -8
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +8 -6
  37. package/dist/index.js.map +1 -1
  38. package/dist/items.d.ts +0 -1
  39. package/dist/items.d.ts.map +1 -1
  40. package/dist/items.js +211 -20
  41. package/dist/items.js.map +1 -1
  42. package/dist/live.d.ts +42 -0
  43. package/dist/live.d.ts.map +1 -0
  44. package/dist/live.js +83 -0
  45. package/dist/live.js.map +1 -0
  46. package/dist/menu.d.ts +31 -2
  47. package/dist/menu.d.ts.map +1 -1
  48. package/dist/menu.js +163 -23
  49. package/dist/menu.js.map +1 -1
  50. package/dist/openleaf.css +147 -12
  51. package/dist/overflow.d.ts +47 -4
  52. package/dist/overflow.d.ts.map +1 -1
  53. package/dist/overflow.js +338 -83
  54. package/dist/overflow.js.map +1 -1
  55. package/dist/registry.d.ts +72 -18
  56. package/dist/registry.d.ts.map +1 -1
  57. package/dist/registry.js +7 -6
  58. package/dist/registry.js.map +1 -1
  59. package/dist/skins.d.ts.map +1 -1
  60. package/dist/skins.js +48 -14
  61. package/dist/skins.js.map +1 -1
  62. package/dist/styles.d.ts +61 -1
  63. package/dist/styles.d.ts.map +1 -1
  64. package/dist/styles.js +77 -637
  65. package/dist/styles.js.map +1 -1
  66. package/dist/testing.d.ts +3 -0
  67. package/dist/testing.d.ts.map +1 -0
  68. package/dist/testing.js +3 -0
  69. package/dist/testing.js.map +1 -0
  70. package/dist/toolbar.d.ts +71 -12
  71. package/dist/toolbar.d.ts.map +1 -1
  72. package/dist/toolbar.js +410 -166
  73. package/dist/toolbar.js.map +1 -1
  74. package/dist/upload.d.ts +32 -7
  75. package/dist/upload.d.ts.map +1 -1
  76. package/dist/upload.js +64 -8
  77. package/dist/upload.js.map +1 -1
  78. package/package.json +17 -3
package/README.md ADDED
@@ -0,0 +1,85 @@
1
+ # @openleaf-editor/ui
2
+
3
+ Toolbar, menubar, dialogs, icons, skins and theme tokens. Plain CSS, no
4
+ framework, themeable by custom properties.
5
+
6
+ > **Editor output is untrusted input.** Whatever the editor produces — and
7
+ > whatever a user pasted into it — must be sanitized **on your server** before it
8
+ > is stored or rendered as HTML. Client-side sanitization is a user-experience
9
+ > feature, not a security control: anything the editor strips can be put back
10
+ > with developer tools, because the editor runs under the user's control.
11
+ >
12
+ > [`@openleaf-editor/sanitize`](https://github.com/PeytonNowlin/openleaf/tree/main/packages/sanitize) ships the
13
+ > canonical allowlist as data and generates configuration for DOMPurify, Python
14
+ > `bleach` and PHP HTMLPurifier from it, so client and server enforce the same
15
+ > rules. Read [SECURITY.md](https://github.com/PeytonNowlin/openleaf/blob/main/SECURITY.md) before you ship.
16
+
17
+ ## Install
18
+
19
+ ```sh
20
+ npm install @openleaf-editor/ui@beta
21
+ ```
22
+
23
+ Keep every `@openleaf-editor/*` package on the same version. They pin each other
24
+ exactly, so mixing versions installs two copies of the schema and the toolbar
25
+ registry -- and a node built by one is not a node type the other accepts.
26
+
27
+ ## What it is for
28
+
29
+ [`@openleaf-editor/element`](../element) already uses this. Import it directly to
30
+ register your own toolbar control, or to reuse the dialogs and icons.
31
+
32
+ `promptForLink` is the one path behind the Link toolbar button, the link
33
+ context menu, and the selection toolbar. On Save it keeps the mark's `id` and
34
+ merges window-safety tokens (`noopener noreferrer`) into `rel` instead of
35
+ replacing author tokens.
36
+
37
+ ## Registering a control
38
+
39
+ ```ts
40
+ import { registerToolbarItem } from '@openleaf-editor/ui'
41
+ import { toggleBold } from '@openleaf-editor/core'
42
+
43
+ registerToolbarItem({
44
+ id: 'bold', type: 'button', kind: 'toggle',
45
+ label: 'Bold', icon: 'bold', command: toggleBold,
46
+ })
47
+ ```
48
+
49
+ Three item types. `button` is the common case. `select` builds a native
50
+ `<select>` from `options`, `getValue` and `applyValue` -- use it for a fixed
51
+ preset list. `custom` hands you `render` and expects a `ToolbarControl` back,
52
+ which is how the colour swatch grid and the block-type control exist without the
53
+ toolbar knowing their ids. The block-type dropdown disables Heading and Paragraph
54
+ when those commands would not apply -- a captioned figure is a textblock, but
55
+ retyping it is how those entries used to destroy the `<figure>`.
56
+
57
+ Registering an item declares capability; it does not rearrange anyone's toolbar.
58
+ Layout is the integrator's `toolbar` attribute.
59
+
60
+ The built-in `image` item inserts, or edits the selected image -- the same
61
+ pattern as the insert-media control. `labelFor` is how its accessible name
62
+ becomes "Edit image" while a picture is selected.
63
+
64
+ ## The toolbar is one tab stop
65
+
66
+ It is a `role="toolbar"` with a roving tabindex, which is the whole reason the
67
+ custom-control contract is strict about focus: exactly one focusable element in
68
+ what you return, and if it is not a `button.ol-btn` then name it as `focusable`.
69
+ Two makes the bar two tab stops where the author expects one.
70
+
71
+ [Authoring OpenLeaf plugins](../../docs/authoring-plugins.md) documents the rest,
72
+ including the `mousedown` `preventDefault` that keeps the selection alive and why
73
+ `destroy` runs before every re-render rather than only on teardown.
74
+
75
+ ## Styling
76
+
77
+ A constructable stylesheet, adopted into the document, so no bundler CSS import
78
+ is required and nothing is injected inline. Every colour is a custom property;
79
+ `skin` and `theme` attributes switch them without rebuilding the editor or
80
+ costing the author their undo history. On a browser without
81
+ `adoptedStyleSheets`, link `openleaf.css` and call `markStylesExternal()`.
82
+
83
+ ## License
84
+
85
+ Apache-2.0.
@@ -0,0 +1,5 @@
1
+ import { type FormatSpec } from '@openleaf-editor/core';
2
+ import type { EditorView } from 'prosemirror-view';
3
+ import type { ToolbarControl } from './registry.js';
4
+ export declare function blockTypeControl(view: EditorView, host: HTMLElement, formats: readonly FormatSpec[]): ToolbarControl;
5
+ //# sourceMappingURL=block-type.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"block-type.d.ts","sourceRoot":"","sources":["../src/block-type.ts"],"names":[],"mappings":"AAAA,OAAO,EASL,KAAK,UAAU,EAChB,MAAM,uBAAuB,CAAA;AAE9B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAElD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAEnD,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,WAAW,EACjB,OAAO,EAAE,SAAS,UAAU,EAAE,GAC7B,cAAc,CA+FhB"}
@@ -0,0 +1,133 @@
1
+ import { activeBlockClass, activeHeadingLevel, formatParts, isNodeActive, setBlockClass, setHeading, setParagraph, toggleHeading, } from '@openleaf-editor/core';
2
+ import { t } from './i18n.js';
3
+ export function blockTypeControl(view, host, formats) {
4
+ const select = host.ownerDocument.createElement('select');
5
+ select.className = 'ol-select';
6
+ select.dataset['olId'] = 'blockType';
7
+ select.setAttribute('aria-label', t('Paragraph style'));
8
+ const options = [
9
+ ['p', t('Paragraph')], ['1', t('Heading 1')], ['2', t('Heading 2')],
10
+ ['3', t('Heading 3')], ['4', t('Heading 4')], ['5', t('Heading 5')], ['6', t('Heading 6')],
11
+ ];
12
+ const addOption = (label, value) => {
13
+ const option = host.ownerDocument.createElement('option');
14
+ option.textContent = label;
15
+ option.value = value;
16
+ select.add(option);
17
+ };
18
+ // Empty value for selections that are not a paragraph or heading (a figure
19
+ // caption). Without it the control would display "Paragraph" over a node
20
+ // setParagraph cannot convert.
21
+ addOption('', '');
22
+ select.options[0].hidden = true;
23
+ select.options[0].disabled = true;
24
+ for (const [value, label] of options)
25
+ addOption(label, value);
26
+ for (const format of formats)
27
+ addOption(t(format.label), `format:${format.token}`);
28
+ let pointerDriven = false;
29
+ select.addEventListener('pointerdown', () => { pointerDriven = true; });
30
+ select.addEventListener('mousedown', (event) => event.stopPropagation());
31
+ select.addEventListener('keydown', (event) => {
32
+ pointerDriven = false;
33
+ if (event.key === 'Enter') {
34
+ event.preventDefault();
35
+ view.focus();
36
+ }
37
+ });
38
+ select.addEventListener('change', () => {
39
+ // `select.disabled` as well as the attribute, because readonly is not the
40
+ // only reason this control goes unavailable: the toolbar disables it in
41
+ // source view too, where the document this would format is hidden behind a
42
+ // textarea that gets reparsed over the result. A native disabled select
43
+ // cannot fire `change` at all -- this is the guard for a synthetic one, and
44
+ // for the window before the first `update()`.
45
+ if (host.hasAttribute('readonly') || select.disabled)
46
+ return;
47
+ const value = select.value;
48
+ if (!value)
49
+ return;
50
+ if (value.startsWith('format:')) {
51
+ const { element, className } = formatParts(value.slice('format:'.length));
52
+ if (element === 'p')
53
+ setParagraph(view.state, view.dispatch, view);
54
+ else if (element !== null && /^h[1-6]$/.test(element)) {
55
+ setHeading(Number(element.slice(1)))(view.state, view.dispatch, view);
56
+ }
57
+ setBlockClass(className)(view.state, view.dispatch, view);
58
+ }
59
+ else {
60
+ const command = value === 'p' ? setParagraph : toggleHeading(Number(value));
61
+ command(view.state, view.dispatch, view);
62
+ }
63
+ if (pointerDriven) {
64
+ pointerDriven = false;
65
+ view.focus();
66
+ }
67
+ });
68
+ return {
69
+ el: select,
70
+ focusable: select,
71
+ update(state) {
72
+ // Both, and the real `disabled` is the load-bearing half: with only
73
+ // `aria-disabled` the control still opened and still changed its own
74
+ // displayed value, so a readonly editor showed "Heading 2" over a document
75
+ // that was still a paragraph.
76
+ const readonly = host.hasAttribute('readonly');
77
+ select.setAttribute('aria-disabled', readonly ? 'true' : 'false');
78
+ select.disabled = readonly;
79
+ for (const option of Array.from(select.options)) {
80
+ if (option.value === '')
81
+ continue;
82
+ option.disabled = readonly || !blockTypeAvailable(state, option.value);
83
+ }
84
+ const formatClass = activeBlockClass(state);
85
+ const level = activeHeadingLevel(state);
86
+ const inParagraph = isNodeActive(state, 'paragraph');
87
+ const activeElement = level === null ? (inParagraph ? 'p' : null) : `h${level}`;
88
+ const matching = formats.find((format) => {
89
+ const { element, className } = formatParts(format.token);
90
+ return className === formatClass && (element === null || element === activeElement);
91
+ });
92
+ const next = matching
93
+ ? `format:${matching.token}`
94
+ : level !== null
95
+ ? String(level)
96
+ : inParagraph
97
+ ? 'p'
98
+ : '';
99
+ if (select.value !== next)
100
+ select.value = next;
101
+ },
102
+ };
103
+ }
104
+ /**
105
+ * Whether this dropdown entry's command would apply. A figure is a textblock
106
+ * but not a paragraph; without this check Heading and Paragraph stay enabled
107
+ * in a caption and choosing one destroys the figure.
108
+ *
109
+ * A format entry runs TWO commands, so it is available when either half has
110
+ * work to do. `p.lead` over a paragraph converts nothing -- `setParagraph`
111
+ * declines a block that is already a paragraph -- but it still has a class to
112
+ * set, and testing only the element half disabled the entry in the one place
113
+ * an author would reach for it. The class half is consulted only when the
114
+ * token actually names a class: `h2` alone in a caption stays disabled rather
115
+ * than becoming an entry that strips the caption's class and nothing else.
116
+ */
117
+ function blockTypeAvailable(state, value) {
118
+ if (value.startsWith('format:')) {
119
+ const { element, className } = formatParts(value.slice('format:'.length));
120
+ const elementApplies = element === 'p'
121
+ ? setParagraph(state)
122
+ : element !== null && /^h[1-6]$/.test(element)
123
+ ? setHeading(Number(element.slice(1)))(state)
124
+ : null;
125
+ if (elementApplies === null)
126
+ return true;
127
+ return elementApplies || (className !== null && setBlockClass(className)(state));
128
+ }
129
+ if (value === 'p')
130
+ return setParagraph(state);
131
+ return setHeading(Number(value))(state);
132
+ }
133
+ //# sourceMappingURL=block-type.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"block-type.js","sourceRoot":"","sources":["../src/block-type.ts"],"names":[],"mappings":"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"}
@@ -6,6 +6,24 @@
6
6
  * stylesheet the published template uses -- a CMS "styles.css" -- that is not
7
7
  * on the admin page. Selectors are scoped under the editor canvas so a rule
8
8
  * written for `p.lead` cannot restyle the rest of the admin chrome.
9
+ *
10
+ * ## Why this is a scanner and not a regex
11
+ *
12
+ * It used to be one `String.replace`, matching a selector only where it was
13
+ * preceded by the start of the input or a `}`. A rule inside `@media` is
14
+ * preceded by the media block's `{`, so it never matched and shipped
15
+ * **unscoped** into the host page -- and nearly every real stylesheet uses
16
+ * media queries, so that was the common case rather than an edge case. The same
17
+ * pass split selector lists on every `,`, which turned `a:is(.b, .c)` into an
18
+ * invalid selector the browser drops on the floor.
19
+ *
20
+ * ## Why not CSSOM
21
+ *
22
+ * `new CSSStyleSheet()` + `replaceSync` would parse this properly and is the
23
+ * obvious answer. It is not available everywhere this function has to run:
24
+ * jsdom implements `CSSStyleSheet` without `replaceSync`, and this module is
25
+ * imported by code that runs server-side. A scoper that throws outside a
26
+ * browser is a scoper the tests cannot cover, so the parsing is done here.
9
27
  */
10
28
  export declare function scopeContentCss(css: string): string;
11
29
  export declare function loadContentCss(doc: Document, urls: readonly string[]): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"content-css.d.ts","sourceRoot":"","sources":["../src/content-css.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAcnD;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuB1F;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,EAAE,CAM7D"}
1
+ {"version":3,"file":"content-css.d.ts","sourceRoot":"","sources":["../src/content-css.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAsNH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,wBAAsB,cAAc,CAAC,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuB1F;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,EAAE,CAM7D"}
@@ -6,24 +6,239 @@
6
6
  * stylesheet the published template uses -- a CMS "styles.css" -- that is not
7
7
  * on the admin page. Selectors are scoped under the editor canvas so a rule
8
8
  * written for `p.lead` cannot restyle the rest of the admin chrome.
9
+ *
10
+ * ## Why this is a scanner and not a regex
11
+ *
12
+ * It used to be one `String.replace`, matching a selector only where it was
13
+ * preceded by the start of the input or a `}`. A rule inside `@media` is
14
+ * preceded by the media block's `{`, so it never matched and shipped
15
+ * **unscoped** into the host page -- and nearly every real stylesheet uses
16
+ * media queries, so that was the common case rather than an edge case. The same
17
+ * pass split selector lists on every `,`, which turned `a:is(.b, .c)` into an
18
+ * invalid selector the browser drops on the floor.
19
+ *
20
+ * ## Why not CSSOM
21
+ *
22
+ * `new CSSStyleSheet()` + `replaceSync` would parse this properly and is the
23
+ * obvious answer. It is not available everywhere this function has to run:
24
+ * jsdom implements `CSSStyleSheet` without `replaceSync`, and this module is
25
+ * imported by code that runs server-side. A scoper that throws outside a
26
+ * browser is a scoper the tests cannot cover, so the parsing is done here.
27
+ */
28
+ const SCOPE = '.ol-editor .ol-content .ProseMirror';
29
+ /**
30
+ * At-rules whose block is NOT a selector list. Everything else is descended into.
31
+ *
32
+ * A denylist rather than an allowlist, and the direction matters: the two ways
33
+ * of being wrong are not symmetric. Failing to descend into a rule-bearing
34
+ * at-rule ships its selectors UNSCOPED into the host page, which is the leak
35
+ * this whole function exists to prevent. Descending into a declaration block
36
+ * that merely looks like one costs nothing -- it has no inner blocks to rewrite,
37
+ * so its declarations pass through as they are. An allowlist means every at-rule
38
+ * CSS gains is a leak until somebody remembers to add it, which
39
+ * `@starting-style` had already demonstrated.
40
+ *
41
+ * `@keyframes` is why this list is not empty: `from`, `to` and `0%` are offsets,
42
+ * not selectors, and prefixing one with a class produces an offset that matches
43
+ * nothing and silently kills the animation.
44
+ */
45
+ const OPAQUE_AT_RULES = new Set([
46
+ 'keyframes', '-webkit-keyframes', '-moz-keyframes', '-o-keyframes',
47
+ 'font-face', 'font-feature-values', 'font-palette-values',
48
+ 'page', 'property', 'counter-style', 'color-profile', 'position-try', 'viewport',
49
+ ]);
50
+ /** Index just past the string literal starting at `start`. */
51
+ function endOfString(css, start) {
52
+ const quote = css[start];
53
+ let i = start + 1;
54
+ while (i < css.length) {
55
+ const ch = css[i];
56
+ if (ch === '\\') {
57
+ i += 2;
58
+ continue;
59
+ }
60
+ if (ch === quote)
61
+ return i + 1;
62
+ i += 1;
63
+ }
64
+ return css.length;
65
+ }
66
+ /** Index just past the comment starting at `start`. */
67
+ function endOfComment(css, start) {
68
+ const end = css.indexOf('*/', start + 2);
69
+ return end === -1 ? css.length : end + 2;
70
+ }
71
+ /** Index of the `}` matching the `{` at `open`, or -1 if there is none. */
72
+ function matchingBrace(css, open) {
73
+ let depth = 0;
74
+ let i = open;
75
+ while (i < css.length) {
76
+ const ch = css[i];
77
+ // A backslash escape can carry any character, brace and semicolon included:
78
+ // `.foo\{bar` is one valid class name, not the start of a block.
79
+ if (ch === '\\') {
80
+ i += 2;
81
+ continue;
82
+ }
83
+ if (ch === '/' && css[i + 1] === '*') {
84
+ i = endOfComment(css, i);
85
+ continue;
86
+ }
87
+ if (ch === '"' || ch === "'") {
88
+ i = endOfString(css, i);
89
+ continue;
90
+ }
91
+ if (ch === '{')
92
+ depth += 1;
93
+ else if (ch === '}') {
94
+ depth -= 1;
95
+ if (depth === 0)
96
+ return i;
97
+ }
98
+ i += 1;
99
+ }
100
+ return -1;
101
+ }
102
+ /**
103
+ * Leading whitespace and comments, split off so they are emitted BEFORE the
104
+ * rule rather than swept into its first selector.
9
105
  */
106
+ function splitLeading(prelude) {
107
+ let i = 0;
108
+ for (;;) {
109
+ while (i < prelude.length && /\s/.test(prelude[i] ?? ''))
110
+ i += 1;
111
+ if (prelude.startsWith('/*', i)) {
112
+ i = endOfComment(prelude, i);
113
+ continue;
114
+ }
115
+ break;
116
+ }
117
+ return [prelude.slice(0, i), prelude.slice(i)];
118
+ }
119
+ /**
120
+ * Split a selector list on its top-level commas only.
121
+ *
122
+ * The commas inside `:is()`, `:where()`, `:has()` and `:not()` belong to the
123
+ * function, and splitting on them is what produced
124
+ * `a:is(.b, SCOPE .c)` -- an invalid selector whose rule the browser drops.
125
+ * Attribute values are skipped for the same reason: `[title="a,b"]`.
126
+ */
127
+ function splitSelectorList(selectors) {
128
+ const parts = [];
129
+ let depth = 0;
130
+ let start = 0;
131
+ let i = 0;
132
+ while (i < selectors.length) {
133
+ const ch = selectors[i];
134
+ // `.foo\,bar` is one class name whose name contains a comma.
135
+ if (ch === '\\') {
136
+ i += 2;
137
+ continue;
138
+ }
139
+ if (ch === '/' && selectors[i + 1] === '*') {
140
+ i = endOfComment(selectors, i);
141
+ continue;
142
+ }
143
+ if (ch === '"' || ch === "'") {
144
+ i = endOfString(selectors, i);
145
+ continue;
146
+ }
147
+ if (ch === '(' || ch === '[')
148
+ depth += 1;
149
+ else if (ch === ')' || ch === ']')
150
+ depth = Math.max(0, depth - 1);
151
+ else if (ch === ',' && depth === 0) {
152
+ parts.push(selectors.slice(start, i));
153
+ start = i + 1;
154
+ }
155
+ i += 1;
156
+ }
157
+ parts.push(selectors.slice(start));
158
+ return parts;
159
+ }
160
+ function scopeSelectorList(selectors) {
161
+ return splitSelectorList(selectors)
162
+ .map((part) => {
163
+ const trimmed = part.trim();
164
+ if (!trimmed)
165
+ return part;
166
+ // Already scoped by the author. Left exactly as written, because a
167
+ // stylesheet that names the canvas is talking about the canvas.
168
+ if (trimmed.startsWith('.ol-editor') || trimmed.startsWith('.ol-content'))
169
+ return trimmed;
170
+ return `${SCOPE} ${trimmed}`;
171
+ })
172
+ .join(', ');
173
+ }
174
+ function scopeRules(css) {
175
+ let out = '';
176
+ let prelude = '';
177
+ let i = 0;
178
+ while (i < css.length) {
179
+ const ch = css[i];
180
+ // Escapes first, so `.foo\;bar` is one class name rather than a statement
181
+ // that ends mid-selector -- which split the rule and left both halves
182
+ // matching nothing.
183
+ if (ch === '\\') {
184
+ prelude += css.slice(i, i + 2);
185
+ i += 2;
186
+ continue;
187
+ }
188
+ if (ch === '/' && css[i + 1] === '*') {
189
+ const stop = endOfComment(css, i);
190
+ prelude += css.slice(i, stop);
191
+ i = stop;
192
+ continue;
193
+ }
194
+ if (ch === '"' || ch === "'") {
195
+ const stop = endOfString(css, i);
196
+ prelude += css.slice(i, stop);
197
+ i = stop;
198
+ continue;
199
+ }
200
+ if (ch === '{') {
201
+ const close = matchingBrace(css, i);
202
+ if (close === -1) {
203
+ // Unbalanced. Emitting the remainder verbatim is the honest failure: a
204
+ // truncated stylesheet is the integrator's to fix, and half-scoping it
205
+ // would make the result harder to recognise than leaving it alone.
206
+ return out + prelude + css.slice(i);
207
+ }
208
+ const [leading, rule] = splitLeading(prelude);
209
+ const body = css.slice(i + 1, close);
210
+ out += leading;
211
+ if (rule.startsWith('@')) {
212
+ const name = /^@(-?[\w-]+)/.exec(rule)?.[1]?.toLowerCase() ?? '';
213
+ // Descended into unless its block is known not to hold rules.
214
+ out += `${rule}{${OPAQUE_AT_RULES.has(name) ? body : scopeRules(body)}}`;
215
+ }
216
+ else {
217
+ // The body is NOT recursed into. A rule nested inside a style rule is
218
+ // relative to its parent, which this has already scoped -- scoping it
219
+ // again would put the canvas prefix in twice.
220
+ out += `${scopeSelectorList(rule)}{${body}}`;
221
+ }
222
+ prelude = '';
223
+ i = close + 1;
224
+ continue;
225
+ }
226
+ if (ch === ';') {
227
+ // A statement at-rule: @import, @charset, @namespace, `@layer a, b;`.
228
+ // None of them carries a selector.
229
+ out += prelude + ';';
230
+ prelude = '';
231
+ i += 1;
232
+ continue;
233
+ }
234
+ prelude += ch;
235
+ i += 1;
236
+ }
237
+ // Trailing whitespace, or a selector with no block. Kept as found.
238
+ return out + prelude;
239
+ }
10
240
  export function scopeContentCss(css) {
11
- return css.replace(/(^|})(\s*)([^@{}][^{]*)\{/g, (full, brace, space, selector) => {
12
- if (selector.trim().startsWith('@'))
13
- return full;
14
- const scoped = selector
15
- .split(',')
16
- .map((part) => {
17
- const trimmed = part.trim();
18
- if (!trimmed)
19
- return part;
20
- if (trimmed.startsWith('.ol-editor') || trimmed.startsWith('.ol-content'))
21
- return trimmed;
22
- return `.ol-editor .ol-content .ProseMirror ${trimmed}`;
23
- })
24
- .join(', ');
25
- return `${brace}${space}${scoped}{`;
26
- });
241
+ return scopeRules(css);
27
242
  }
28
243
  export async function loadContentCss(doc, urls) {
29
244
  if (typeof CSSStyleSheet === 'undefined' || !('adoptedStyleSheets' in Document.prototype)) {
@@ -1 +1 @@
1
- {"version":3,"file":"content-css.js","sourceRoot":"","sources":["../src/content-css.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,OAAO,GAAG,CAAC,OAAO,CAAC,4BAA4B,EAAE,CAAC,IAAI,EAAE,KAAa,EAAE,KAAa,EAAE,QAAgB,EAAE,EAAE;QACxG,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,OAAO,IAAI,CAAA;QAChD,MAAM,MAAM,GAAG,QAAQ;aACpB,KAAK,CAAC,GAAG,CAAC;aACV,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;YACZ,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAA;YAC3B,IAAI,CAAC,OAAO;gBAAE,OAAO,IAAI,CAAA;YACzB,IAAI,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC;gBAAE,OAAO,OAAO,CAAA;YACzF,OAAO,uCAAuC,OAAO,EAAE,CAAA;QACzD,CAAC,CAAC;aACD,IAAI,CAAC,IAAI,CAAC,CAAA;QACb,OAAO,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,CAAA;IACrC,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,GAAa,EAAE,IAAuB;IACzE,IAAI,OAAO,aAAa,KAAK,WAAW,IAAI,CAAC,CAAC,oBAAoB,IAAI,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QAC1F,OAAO,CAAC,IAAI,CACV,yEAAyE;YACvE,yDAAyD,CAC5D,CAAA;QACD,OAAM;IACR,CAAC;IACD,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,CAAA;YACjC,IAAI,CAAC,QAAQ,CAAC,EAAE;gBAAE,MAAM,IAAI,KAAK,CAAC,QAAQ,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAA;YAC5D,MAAM,GAAG,GAAG,eAAe,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAA;YAClD,MAAM,KAAK,GAAG,IAAI,aAAa,EAAE,CAAA;YACjC,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,CAAA;YACtB,GAAG,CAAC,kBAAkB,GAAG,CAAC,GAAG,GAAG,CAAC,kBAAkB,EAAE,KAAK,CAAC,CAAA;QAC7D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,IAAI,CACV,wCAAwC,GAAG,wBAAwB,EACnE,KAAK,CACN,CAAA;QACH,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAoB;IACjD,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,CAAA;IACrB,OAAO,KAAK;SACT,KAAK,CAAC,OAAO,CAAC;SACd,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;SAC1B,MAAM,CAAC,OAAO,CAAC,CAAA;AACpB,CAAC"}
1
+ {"version":3,"file":"content-css.js","sourceRoot":"","sources":["../src/content-css.ts"],"names":[],"mappings":"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"}
package/dist/css.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The editor stylesheet, as a string, and nothing else.
3
+ *
4
+ * ## Why this is not in `styles.ts`
5
+ *
6
+ * This is 20 KB of the ~24 KB `@openleaf-editor/ui` ships, and it is needed by
7
+ * exactly one function -- `ensureStyles`. While it lived beside `registerStyles`
8
+ * in `styles.ts`, every consumer that reached that module for any reason paid for
9
+ * the whole stylesheet: `skins.ts` imports `registerStyles`, so a bundle that
10
+ * touched a skin -- or, through the barrel, one that touched nothing but `t()` --
11
+ * carried the full sheet.
12
+ *
13
+ * Alone in a module whose only top-level binding is a string literal, it is
14
+ * trivially droppable. A bundler keeps it when something calls `ensureStyles` and
15
+ * discards it when the integrator has linked `openleaf.css` themselves, which is
16
+ * the distinction `markStylesExternal()` has always claimed to offer and could
17
+ * not previously deliver at bundle time.
18
+ *
19
+ * `dist/openleaf.css` is generated from this constant by `scripts/emit-css.mjs`,
20
+ * so the constructable-stylesheet path and the linked-file path cannot drift.
21
+ */
22
+ export declare const CSS = "\n.ol-editor {\n /* Two families, named before either is public API. A singular\n `--openleaf-font` implies \"the\" font, and source view already has a\n monospace surface -- bolting a mono token on beside a singular name later\n is the awkward outcome worth avoiding now. `--openleaf-font` is still\n honoured as a fallback. */\n --ol-font: var(--openleaf-font-ui, var(--openleaf-font, system-ui, -apple-system, \"Segoe UI\", sans-serif));\n --ol-font-mono: var(--openleaf-font-mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);\n --ol-font-size: var(--openleaf-font-size, 14px);\n --ol-radius: var(--openleaf-radius, 4px);\n --ol-text: var(--openleaf-color-text, #1f2328);\n --ol-text-muted: var(--openleaf-color-text-muted, #59636e);\n --ol-surface: var(--openleaf-color-surface, #ffffff);\n --ol-surface-hover: var(--openleaf-color-surface-hover, #f0f1f3);\n --ol-surface-active: var(--openleaf-color-surface-active, #dbe9ff);\n --ol-border: var(--openleaf-color-border, #d1d9e0);\n /* Control boundaries, not decoration. See \"Two border tokens\" above. */\n --ol-border-strong: var(--openleaf-color-border-strong, #6e7781);\n --ol-accent: var(--openleaf-color-accent, #0550ae);\n --ol-focus: var(--openleaf-color-focus, #0969da);\n --ol-danger: var(--openleaf-color-danger, #cf222e);\n --ol-button-size: var(--openleaf-button-size, 32px);\n --ol-icon-size: var(--openleaf-icon-size, 16px);\n --ol-gap: var(--openleaf-gap, 2px);\n /* A 2009 WordPress admin bar sits at z-index 99999 and Drupal's toolbar plays\n similar games. Without a sanctioned escape hatch integrators fix a toolbar\n rendered behind a sticky host header with !important -- the exact thing the\n token API exists to prevent. */\n --ol-z: var(--openleaf-z-index, 1);\n /* Thickness and offset, not just colour: \"you can change the colour but not\n the thickness\" is a poor answer to a Section 508 team citing WCAG 2.2\n Focus Appearance. */\n --ol-focus-width: var(--openleaf-focus-width, 2px);\n --ol-focus-offset: var(--openleaf-focus-offset, 1px);\n\n display: block;\n position: relative;\n color: var(--ol-text);\n}\n\n/* Three ways into the dark palette, and one way out of it.\n `data-ol-scheme` is set from the applied skin's declared scheme, and a skin\n that declares one outranks both the attribute and the system -- it has to,\n because its tokens have already replaced the palette outright. Without the\n `:not()` guards a light skin on a dark machine would take these fallbacks for\n every token it did not itself set, which is a light surface carrying dark-mode\n muted text. */\n@media (prefers-color-scheme: dark) {\n .ol-editor:not([data-ol-theme=\"light\"]):not([data-ol-scheme]) {\n --ol-text: var(--openleaf-color-text, #e6edf3);\n --ol-text-muted: var(--openleaf-color-text-muted, #9198a1);\n --ol-surface: var(--openleaf-color-surface, #0d1117);\n --ol-surface-hover: var(--openleaf-color-surface-hover, #21262d);\n --ol-surface-active: var(--openleaf-color-surface-active, #1f3a5f);\n --ol-border: var(--openleaf-color-border, #3d444d);\n --ol-border-strong: var(--openleaf-color-border-strong, #8b949e);\n --ol-accent: var(--openleaf-color-accent, #79c0ff);\n --ol-focus: var(--openleaf-color-focus, #79c0ff);\n --ol-danger: var(--openleaf-color-danger, #ff8182);\n}\n}\n\n.ol-editor[data-ol-theme=\"dark\"]:not([data-ol-scheme]) {\n --ol-text: var(--openleaf-color-text, #e6edf3);\n --ol-text-muted: var(--openleaf-color-text-muted, #9198a1);\n --ol-surface: var(--openleaf-color-surface, #0d1117);\n --ol-surface-hover: var(--openleaf-color-surface-hover, #21262d);\n --ol-surface-active: var(--openleaf-color-surface-active, #1f3a5f);\n --ol-border: var(--openleaf-color-border, #3d444d);\n --ol-border-strong: var(--openleaf-color-border-strong, #8b949e);\n --ol-accent: var(--openleaf-color-accent, #79c0ff);\n --ol-focus: var(--openleaf-color-focus, #79c0ff);\n --ol-danger: var(--openleaf-color-danger, #ff8182);\n}\n\n.ol-editor[data-ol-scheme=\"dark\"] {\n --ol-text: var(--openleaf-color-text, #e6edf3);\n --ol-text-muted: var(--openleaf-color-text-muted, #9198a1);\n --ol-surface: var(--openleaf-color-surface, #0d1117);\n --ol-surface-hover: var(--openleaf-color-surface-hover, #21262d);\n --ol-surface-active: var(--openleaf-color-surface-active, #1f3a5f);\n --ol-border: var(--openleaf-color-border, #3d444d);\n --ol-border-strong: var(--openleaf-color-border-strong, #8b949e);\n --ol-accent: var(--openleaf-color-accent, #79c0ff);\n --ol-focus: var(--openleaf-color-focus, #79c0ff);\n --ol-danger: var(--openleaf-color-danger, #ff8182);\n}\n\n/* Native widget chrome -- the popup a `select` opens, scrollbars, the caret,\n form control backgrounds -- is painted by the browser from `color-scheme`,\n not from any property we can hand an integrator. Left alone it follows the\n page, which is right while the editor's own palette also follows the page and\n wrong the moment either attribute pins the editor to one world. So it is set\n exactly when the editor stops following along, and inherited from the host\n otherwise. */\n.ol-editor[data-ol-theme=\"light\"] { color-scheme: light; }\n.ol-editor[data-ol-theme=\"dark\"] { color-scheme: dark; }\n.ol-editor[data-ol-scheme=\"light\"] { color-scheme: light; }\n.ol-editor[data-ol-scheme=\"dark\"] { color-scheme: dark; }\n\n/* Wrapping, not scrolling and not an overflow menu. Both of those hide\n controls -- one off-screen, one behind a click -- and a formatting control\n the author cannot see is a control they do not have. */\n.ol-editor .ol-toolbar {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--ol-gap);\n box-sizing: border-box;\n margin: 0;\n padding: 4px;\n border: 1px solid var(--ol-border-strong);\n border-bottom: 0;\n border-radius: var(--ol-radius) var(--ol-radius) 0 0;\n background: var(--ol-surface);\n font: inherit;\n position: relative;\n z-index: var(--ol-z);\n}\n\n.ol-editor .ol-group {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--ol-gap);\n}\n\n/* The divider is a BORDER on the group, not a standalone flex item.\n As its own element it could wrap onto the trailing edge of a row with nothing\n after it -- a stranded line floating at the end of a row. Wrapping begins\n around 690-740px, well inside ordinary desktop widths (a CMS sidebar, a\n half-width split pane), so this was not a 360px-only edge case.\n border-inline-start rather than border-left, so it flips under RTL.\n\n This selector is why the renderer emits NO element between groups. It used to\n emit an .ol-sep div, which made the two groups non-adjacent and stopped this\n rule from ever matching -- so no divider rendered in any theme, and every bar\n carried inert empty divs. Adding a separator element back here breaks this. */\n.ol-editor .ol-group + .ol-group {\n border-inline-start: 1px solid var(--ol-border);\n padding-inline-start: 5px;\n margin-inline-start: 1px;\n}\n\n/* Every property a host reset or `button {}` rule is likely to touch is set\n here explicitly. Specificity is (0,2,0) via the descendant selector, which\n beats a bare element or single-class host rule without resorting to\n !important. */\n.ol-editor .ol-btn {\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex: 0 0 auto;\n width: var(--ol-button-size);\n height: var(--ol-button-size);\n min-width: var(--ol-button-size);\n min-height: var(--ol-button-size);\n max-width: none;\n padding: 0;\n margin: 0;\n border: 1px solid transparent;\n border-radius: var(--ol-radius);\n background: transparent;\n color: var(--ol-text);\n font: inherit;\n font-family: var(--ol-font);\n font-size: var(--ol-font-size);\n font-weight: 400;\n line-height: 1;\n letter-spacing: normal;\n text-transform: none;\n text-align: center;\n text-decoration: none;\n text-shadow: none;\n box-shadow: none;\n opacity: 1;\n cursor: pointer;\n appearance: none;\n -webkit-appearance: none;\n outline-offset: var(--ol-focus-offset);\n -webkit-tap-highlight-color: transparent;\n transition: background-color 120ms ease, border-color 120ms ease;\n}\n\n.ol-editor .ol-btn:hover {\n background: var(--ol-surface-hover);\n}\n\n/* :focus-visible only, so a mouse click does not leave a ring behind, but the\n ring is never removed for keyboard users. */\n.ol-editor .ol-btn:focus-visible {\n outline: var(--ol-focus-width) solid var(--ol-focus);\n outline-offset: var(--ol-focus-offset);\n}\n\n/* Pressed state is distinguished from hover by THREE signals, not just colour:\n a filled background, a visible border, and an inset shadow that reads as\n physically depressed. Colour alone would fail for a colour-blind author and\n would be invisible in forced-colours mode. */\n.ol-editor .ol-btn[aria-pressed=\"true\"] {\n background: var(--ol-surface-active);\n border-color: var(--ol-accent);\n color: var(--ol-accent);\n box-shadow: inset 0 1px 2px rgb(0 0 0 / 12%);\n}\n\n.ol-editor .ol-btn[aria-pressed=\"true\"]:hover {\n background: var(--ol-surface-active);\n border-color: var(--ol-accent);\n}\n\n/* aria-disabled rather than the disabled attribute: a disabled button is\n removed from the roving tabindex and cannot be reached or announced, so an\n author using a screen reader cannot discover that the control exists. */\n.ol-editor .ol-btn[aria-disabled=\"true\"] {\n opacity: 0.55;\n cursor: default;\n}\n\n.ol-editor .ol-btn[aria-disabled=\"true\"]:hover {\n background: transparent;\n}\n\n.ol-editor .ol-icon {\n width: var(--ol-icon-size);\n height: var(--ol-icon-size);\n display: block;\n pointer-events: none;\n flex: 0 0 auto;\n}\n\n/* Block-type select. Sized and coloured to sit level with the icon buttons so\n it does not read as bolted on, but it stays a native <select> -- a custom\n listbox is a large amount of ARIA that would then owe real screen reader\n testing to be worth anything. */\n.ol-editor .ol-select {\n box-sizing: border-box;\n height: var(--ol-button-size);\n max-width: 11em;\n padding: 0 22px 0 6px;\n margin: 0;\n border: 1px solid var(--ol-border-strong);\n border-radius: var(--ol-radius);\n background-color: transparent;\n background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),\n linear-gradient(135deg, currentColor 50%, transparent 50%);\n background-position: right 10px center, right 6px center;\n background-size: 4px 4px, 4px 4px;\n background-repeat: no-repeat;\n color: var(--ol-text);\n font-family: var(--ol-font);\n font-size: var(--ol-font-size);\n font-weight: 400;\n line-height: 1;\n text-transform: none;\n letter-spacing: normal;\n box-shadow: none;\n cursor: pointer;\n appearance: none;\n -webkit-appearance: none;\n}\n\n.ol-editor .ol-select--wide {\n max-width: 14em;\n}\n\n.ol-editor .ol-select:hover {\n background-color: var(--ol-surface-hover);\n}\n\n.ol-editor .ol-select:focus-visible {\n outline: var(--ol-focus-width) solid var(--ol-focus);\n outline-offset: var(--ol-focus-offset);\n}\n\n/* The option list is painted by the OS, which does not inherit our tokens, so\n give it explicit colours or dark mode shows black text on black. */\n.ol-editor .ol-select option {\n background: var(--ol-surface);\n color: var(--ol-text);\n}\n\n.ol-editor .ol-content {\n box-sizing: border-box;\n border: 1px solid var(--ol-border-strong);\n border-radius: 0 0 var(--ol-radius) var(--ol-radius);\n background: var(--ol-surface);\n}\n\n/* Deliberately minimal: the content area inherits the host's typography, which\n is the entire reason this project does not use Shadow DOM. Only padding and\n the focus ring are ours. */\n.ol-editor .ol-content .ProseMirror {\n padding: 12px;\n min-height: 8rem;\n outline: none;\n}\n\n.ol-editor .ol-content:focus-within {\n outline: 2px solid var(--ol-focus);\n outline-offset: -1px;\n}\n\n/* Tables.\n These styles live in core, not in the opt-in table plugin, because table\n NODES live in core: every deployment reads and renders tables even where\n editing them is switched off. Unstyled tables would render as runs of\n undelimited text. */\n.ol-editor .ol-content .ProseMirror table {\n border-collapse: collapse;\n table-layout: fixed;\n width: 100%;\n margin: 0 0 1em;\n overflow: hidden;\n}\n\n.ol-editor .ol-content .ProseMirror td,\n.ol-editor .ol-content .ProseMirror th {\n border: 1px solid var(--ol-border);\n padding: 6px 8px;\n vertical-align: top;\n /* A zero-width cell cannot be clicked into, so it cannot be repaired. */\n min-width: 2em;\n position: relative;\n}\n\n/* Presentational valign is otherwise beaten by the rule above, which would\n make the cell-properties dialog a no-op on screen. */\n.ol-editor .ol-content .ProseMirror td[valign=\"middle\"],\n.ol-editor .ol-content .ProseMirror th[valign=\"middle\"] { vertical-align: middle; }\n.ol-editor .ol-content .ProseMirror td[valign=\"bottom\"],\n.ol-editor .ol-content .ProseMirror th[valign=\"bottom\"] { vertical-align: bottom; }\n.ol-editor .ol-content .ProseMirror td[valign=\"baseline\"],\n.ol-editor .ol-content .ProseMirror th[valign=\"baseline\"] { vertical-align: baseline; }\n\n.ol-editor .ol-content .ProseMirror th {\n background: var(--ol-surface-hover);\n font-weight: 600;\n text-align: start;\n}\n\n/* prosemirror-tables marks cells in a rectangular selection with this class.\n An ::after overlay rather than a background so it composes with a cell that\n already has one, and so a header cell still reads as a header.\n\n The ring, not the tint, is the indicator -- see \"Cell selection\" above. */\n.ol-editor .ol-content .ProseMirror .selectedCell {\n outline: 2px solid var(--ol-accent);\n outline-offset: -2px;\n}\n.ol-editor .ol-content .ProseMirror .selectedCell::after {\n content: \"\";\n position: absolute;\n inset: 0;\n background: var(--ol-surface-active);\n opacity: 0.4;\n pointer-events: none;\n}\n\n/* The column resize handle, drawn by prosemirror-tables' columnResizing. */\n.ol-editor .ol-content .ProseMirror .column-resize-handle {\n position: absolute;\n right: -2px;\n top: 0;\n bottom: 0;\n width: 4px;\n background: var(--ol-accent);\n pointer-events: none;\n z-index: 20;\n}\n\n.ol-editor .ol-content .ProseMirror.resize-cursor {\n cursor: col-resize;\n}\n\n.ol-editor .ol-content .ProseMirror img.ol-float-left {\n float: left;\n margin: 0 1em 0.5em 0;\n}\n.ol-editor .ol-content .ProseMirror img.ol-float-right {\n float: right;\n margin: 0 0 0.5em 1em;\n}\n.ol-editor .ol-content .ProseMirror img.ol-align-center {\n display: block;\n margin: 0 auto 0.5em;\n}\n.ol-editor .ol-content .ProseMirror figure {\n margin: 0 0 1em;\n}\n.ol-editor .ol-content .ProseMirror figcaption {\n font-size: .9em;\n opacity: .8;\n margin-top: .35em;\n}\n.ol-editor .ol-content .ProseMirror details {\n border: 1px solid var(--ol-border);\n border-radius: var(--ol-radius);\n padding: .5em .75em;\n margin: 0 0 1em;\n}\n.ol-editor .ol-content .ProseMirror summary {\n cursor: pointer;\n font-weight: 600;\n}\n.ol-editor .ol-content .ProseMirror hr.ol-pagebreak {\n border: 0;\n border-top: 2px dashed var(--ol-border);\n margin: 1.5em 0;\n}\n.ol-editor .ol-content .ProseMirror iframe,\n.ol-editor .ol-content .ProseMirror video,\n.ol-editor .ol-content .ProseMirror audio {\n max-width: 100%;\n}\n\n/* Preserved-but-unrecognised markup, surfaced rather than hidden. */\n.ol-editor .ol-content .ProseMirror-selectednode {\n outline: 2px solid var(--ol-focus);\n outline-offset: 2px;\n border-radius: 2px;\n}\n\n/* Gap cursor: the caret beside a block atom or isolating node, where no\n textblock exists. Hidden until the view is focused so it cannot paint over\n an inactive canvas. */\n.ol-editor .ol-content .ProseMirror-gapcursor {\n display: none;\n pointer-events: none;\n position: absolute;\n}\n.ol-editor .ol-content .ProseMirror-gapcursor:after {\n content: \"\";\n display: block;\n position: absolute;\n top: -2px;\n width: 20px;\n border-top: 1px solid var(--ol-text);\n animation: ol-gapcursor-blink 1.1s steps(2, start) infinite;\n}\n@keyframes ol-gapcursor-blink {\n to { visibility: hidden; }\n}\n.ol-editor .ol-content .ProseMirror-focused .ProseMirror-gapcursor {\n display: block;\n}\n\n.ol-editor .ol-source {\n box-sizing: border-box;\n display: block;\n width: 100%;\n min-height: 12rem;\n padding: 12px;\n border: 1px solid var(--ol-border-strong);\n border-radius: 0 0 var(--ol-radius) var(--ol-radius);\n background: var(--ol-surface);\n color: var(--ol-text);\n font-family: var(--ol-font-mono);\n font-size: 13px;\n line-height: 1.5;\n resize: vertical;\n white-space: pre-wrap;\n}\n\n/* Directional icons under RTL.\n Group order reverses for free with flexbox, but a curved undo arrow does not:\n in an RTL document \"back\" points the other way. Only genuinely directional\n icons are flipped -- bold and italic must not be mirrored. */\n.ol-editor[dir=\"rtl\"] .ol-icon--directional,\n[dir=\"rtl\"] .ol-editor .ol-icon--directional {\n transform: scaleX(-1);\n}\n\n/* The announcement region. Visually hidden but not display:none, which would\n remove it from the accessibility tree and silence it. */\n.ol-editor .ol-live {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip: rect(0 0 0 0);\n clip-path: inset(50%);\n white-space: nowrap;\n border: 0;\n}\n\n/* Menubar, menus, floating chrome, visual aids, fullscreen, inline. */\n.ol-editor .ol-menubar {\n display: flex;\n flex-wrap: wrap;\n gap: var(--ol-gap);\n box-sizing: border-box;\n margin: 0;\n padding: 2px 4px;\n border: 1px solid var(--ol-border-strong);\n border-bottom: 0;\n border-radius: var(--ol-radius) var(--ol-radius) 0 0;\n background: var(--ol-surface);\n position: relative;\n z-index: var(--ol-z);\n}\n.ol-editor .ol-menubar + .ol-toolbar {\n border-radius: 0;\n}\n.ol-editor .ol-menu-trigger {\n box-sizing: border-box;\n margin: 0;\n padding: 4px 8px;\n border: 1px solid transparent;\n border-radius: var(--ol-radius);\n background: transparent;\n color: var(--ol-text);\n font: inherit;\n font-family: var(--ol-font);\n font-size: var(--ol-font-size);\n cursor: pointer;\n appearance: none;\n}\n.ol-editor .ol-menu-trigger:hover,\n.ol-editor .ol-menu-trigger[aria-expanded=\"true\"] {\n background: var(--ol-surface-hover);\n}\n.ol-editor .ol-menu-trigger:focus-visible {\n outline: var(--ol-focus-width) solid var(--ol-focus);\n outline-offset: var(--ol-focus-offset);\n}\n.ol-editor .ol-menu {\n position: absolute;\n z-index: calc(var(--ol-z) + 20);\n min-width: 12rem;\n /* Capped, so a deep menu opened low on a short window can be flipped above\n its trigger instead of running off the bottom of the page. */\n max-height: 60vh;\n overflow-y: auto;\n margin: 0;\n padding: 4px;\n border: 1px solid var(--ol-border-strong);\n border-radius: var(--ol-radius);\n background: var(--ol-surface);\n box-shadow: 0 8px 24px rgb(0 0 0 / 12%);\n}\n.ol-editor .ol-menu-item {\n display: block;\n box-sizing: border-box;\n width: 100%;\n margin: 0;\n padding: 6px 10px;\n border: 0;\n border-radius: var(--ol-radius);\n background: transparent;\n color: var(--ol-text);\n font: inherit;\n font-family: var(--ol-font);\n font-size: var(--ol-font-size);\n text-align: start;\n cursor: pointer;\n appearance: none;\n}\n.ol-editor .ol-menu-item:hover {\n background: var(--ol-surface-hover);\n}\n/* Was outline:none plus a 1.13:1 background swap. See \"Menu focus\" above. */\n.ol-editor .ol-menu-item:focus-visible {\n background: var(--ol-surface-hover);\n outline: var(--ol-focus-width) solid var(--ol-focus);\n outline-offset: -2px;\n}\n/* 0.55, not 0.4: see \"Disabled is not invisible\" above. */\n.ol-editor .ol-menu-item[aria-disabled=\"true\"] {\n opacity: 0.55;\n cursor: default;\n}\n/* Third time for the same specificity trap, after the floating bar and the\n overflow panel below: `.ol-editor .ol-btn` is (0,2,0) and its\n `display: inline-flex` outranks the UA's `[hidden]` rule, so\n `button.hidden = true` set the attribute and painted the button anyway. A bar\n wide enough to need no overflow still showed its More trigger, and pressing\n it opened an empty panel. */\n.ol-editor .ol-btn[hidden] {\n display: none;\n}\n.ol-editor .ol-menu-sep {\n height: 1px;\n margin: 4px 0;\n background: var(--ol-border);\n}\n/* `display: flex` on .ol-toolbar above outranks the UA's `[hidden]` rule --\n a class selector beats an attribute selector on specificity -- so setting\n `el.hidden = true` in floating.ts styled a hidden bar and showed it anyway.\n Both floating bars sat over the prose at rest, with no selection to format.\n The overflow panel needed the same rule for the same reason; see below. */\n.ol-editor .ol-toolbar.ol-floating[hidden] {\n display: none;\n}\n.ol-editor .ol-toolbar.ol-floating {\n position: absolute;\n flex-wrap: nowrap;\n border: 1px solid var(--ol-border);\n border-radius: var(--ol-radius);\n box-shadow: 0 8px 24px rgb(0 0 0 / 12%);\n z-index: calc(var(--ol-z) + 10);\n}\n.ol-editor .ol-toolbar.ol-toolbar--overflow {\n flex-wrap: nowrap;\n overflow: hidden;\n}\n/* The More panel. Positioned in viewport coordinates from script, because the\n bar it belongs to may live inside a host with `overflow: hidden` -- which is\n the situation the whole feature exists for. */\n.ol-editor .ol-overflow-menu {\n display: flex;\n flex-direction: column;\n align-items: stretch;\n gap: var(--ol-gap);\n max-height: 70vh;\n overflow-y: auto;\n}\n.ol-editor .ol-overflow-menu[hidden] {\n display: none;\n}\n/* Vertical: the panel exists because there is no horizontal room left. */\n.ol-editor .ol-overflow-menu .ol-group {\n flex-direction: column;\n align-items: stretch;\n}\n.ol-editor .ol-overflow-menu .ol-group + .ol-group {\n border-inline-start: 0;\n border-block-start: 1px solid var(--ol-border);\n padding-inline-start: 0;\n margin-inline-start: 0;\n padding-block-start: 4px;\n margin-block-start: 3px;\n}\n.ol-editor.ol-visual-aids .ol-empty-block {\n outline: 1px dashed var(--ol-border);\n outline-offset: 2px;\n min-height: 1.2em;\n}\n.ol-editor.ol-visual-aids .ol-nbsp {\n background: color-mix(in srgb, var(--ol-accent) 25%, transparent);\n box-shadow: inset 0 -1px 0 var(--ol-accent);\n}\n.ol-editor.ol-visual-aids .ol-hidden-structure {\n outline: 1px dotted var(--ol-accent);\n outline-offset: 2px;\n}\n.ol-editor .ol-noneditable {\n cursor: default;\n background: repeating-linear-gradient(\n -45deg,\n transparent,\n transparent 6px,\n color-mix(in srgb, var(--ol-border) 35%, transparent) 6px,\n color-mix(in srgb, var(--ol-border) 35%, transparent) 7px\n );\n}\n.ol-editor.ol-fullscreen {\n position: fixed;\n inset: 0;\n z-index: 2147483000;\n margin: 0;\n border-radius: 0;\n background: var(--ol-surface);\n display: flex;\n flex-direction: column;\n}\n.ol-editor.ol-fullscreen .ol-content,\n.ol-editor.ol-fullscreen .ol-source {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n}\n.ol-editor.ol-autoresize .ol-content .ProseMirror {\n overflow: hidden;\n}\n.ol-editor.ol-inline:not(.ol-inline-active) .ol-toolbar,\n.ol-editor.ol-inline:not(.ol-inline-active) .ol-menubar {\n position: absolute;\n width: 1px;\n height: 1px;\n overflow: hidden;\n clip: rect(0 0 0 0);\n clip-path: inset(50%);\n}\n.ol-editor.ol-inline .ol-content {\n border-radius: var(--ol-radius);\n}\n.ol-help-table {\n width: 100%;\n border-collapse: collapse;\n font: inherit;\n}\n.ol-help-table th,\n.ol-help-table td {\n padding: 4px 8px;\n text-align: start;\n vertical-align: top;\n border-bottom: 1px solid var(--openleaf-color-border, #d1d9e0);\n}\n.ol-help-table th {\n font-weight: 600;\n white-space: nowrap;\n}\n\n/* Coarse pointers get a larger target. WCAG 2.2 SC 2.5.8 asks for 24x24 CSS px\n minimum; 32 clears that on a mouse and 40 is comfortable on a thumb. */\n@media (pointer: coarse) {\n .ol-editor {\n --ol-button-size: var(--openleaf-button-size, 40px);\n --ol-gap: var(--openleaf-gap, 4px);\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .ol-editor .ol-btn {\n transition: none;\n }\n}\n\n/* Forced colours (Windows high contrast) replaces our palette wholesale. The\n pressed state must not depend on a background we no longer control, so it is\n re-expressed as a border, which the mode preserves. */\n@media (forced-colors: active) {\n .ol-editor .ol-btn {\n border-color: ButtonBorder;\n color: ButtonText;\n }\n .ol-editor .ol-btn[aria-pressed=\"true\"] {\n border-color: Highlight;\n color: Highlight;\n box-shadow: none;\n }\n .ol-editor .ol-btn:focus-visible {\n outline-color: Highlight;\n }\n .ol-editor .ol-btn[aria-disabled=\"true\"] {\n color: GrayText;\n opacity: 1;\n }\n .ol-editor .ol-menu-item:focus-visible {\n outline-color: Highlight;\n }\n .ol-editor .ol-menu-item[aria-disabled=\"true\"] {\n color: GrayText;\n opacity: 1;\n }\n .ol-editor .ol-menu-trigger[aria-expanded=\"true\"] {\n border-color: Highlight;\n color: Highlight;\n }\n .ol-editor .ol-content .ProseMirror .selectedCell {\n outline-color: Highlight;\n }\n .ol-editor .ol-content .ProseMirror .selectedCell::after {\n background: transparent;\n }\n .ol-editor .ol-content .ProseMirror .column-resize-handle {\n background: Highlight;\n }\n .ol-editor.ol-visual-aids .ol-nbsp {\n background: transparent;\n box-shadow: inset 0 -2px 0 Highlight;\n }\n .ol-editor.ol-visual-aids .ol-hidden-structure {\n outline-color: Highlight;\n }\n .ol-editor.ol-visual-aids .ol-empty-block {\n outline-color: GrayText;\n }\n .ol-editor .ol-noneditable {\n background: transparent;\n outline: 1px dashed GrayText;\n }\n}\n";
23
+ //# sourceMappingURL=css.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"css.d.ts","sourceRoot":"","sources":["../src/css.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAkFH,eAAO,MAAM,GAAG,++yBAyuBf,CAAA"}