@formicoidea/labre-core 0.39.1 → 0.39.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_pkgs/affine-block-list/list-block.d.ts +8 -1
- package/dist/_pkgs/affine-block-list/list-block.js +27 -17
- package/dist/_pkgs/affine-block-paragraph/paragraph-block.d.ts +8 -1
- package/dist/_pkgs/affine-block-paragraph/paragraph-block.js +30 -15
- package/dist/_pkgs/affine-block-root/edgeless/clipboard/clipboard.js +3 -3
- package/dist/_pkgs/affine-block-root/keyboard/keymap.js +20 -0
- package/dist/_pkgs/affine-block-surface/extensions/export-manager/export-manager.d.ts +6 -4
- package/dist/_pkgs/affine-block-surface/extensions/export-manager/export-manager.js +15 -10
- package/dist/_pkgs/affine-block-surface/utils/priority-queue.js +2 -2
- package/dist/_pkgs/affine-components/toggle-button/toggle-button.d.ts +5 -0
- package/dist/_pkgs/affine-components/toggle-button/toggle-button.js +31 -18
- package/dist/_pkgs/affine-shared/adapters/middlewares/proxy.d.ts +10 -0
- package/dist/_pkgs/affine-shared/adapters/middlewares/proxy.js +19 -12
- package/dist/_pkgs/affine-shared/adapters/utils/fetch.js +4 -0
- package/dist/_pkgs/std/gfx/model/surface/element-model.d.ts +2 -2
- package/dist/_pkgs/std/gfx/model/surface/element-model.js +2 -2
- package/dist/_pkgs/std/gfx/model/surface/surface-model.d.ts +1 -1
- package/dist/_pkgs/std/gfx/model/surface/surface-model.js +1 -1
- package/package.json +1 -1
|
@@ -6,6 +6,14 @@ import { type TemplateResult } from 'lit';
|
|
|
6
6
|
export declare class ListBlockComponent extends CaptionedBlockComponent<ListBlockModel> {
|
|
7
7
|
static styles: import("lit").CSSResult;
|
|
8
8
|
private _inlineRangeProvider;
|
|
9
|
+
/**
|
|
10
|
+
* The collapse state a READER holds locally. Same contract as
|
|
11
|
+
* `affine-paragraph`: a readonly document is never written, so the toggle
|
|
12
|
+
* carries the reader's own expansion here instead of in the model. A signal
|
|
13
|
+
* rather than a Lit `@state()`, so BlockSuite effects can depend on it.
|
|
14
|
+
*/
|
|
15
|
+
private readonly _readonlyCollapsed;
|
|
16
|
+
private _setReadonlyCollapsed;
|
|
9
17
|
private readonly _onClickIcon;
|
|
10
18
|
get attributeRenderer(): import("../std/inline/types.js").AttributeRenderer<import("../affine-shared/types/index.js").AffineTextAttributes>;
|
|
11
19
|
get attributesSchema(): import("zod").ZodType<any, import("zod").ZodTypeDef, any>;
|
|
@@ -16,7 +24,6 @@ export declare class ListBlockComponent extends CaptionedBlockComponent<ListBloc
|
|
|
16
24
|
connectedCallback(): void;
|
|
17
25
|
getUpdateComplete(): Promise<boolean>;
|
|
18
26
|
renderBlock(): TemplateResult<1>;
|
|
19
|
-
private accessor _readonlyCollapsed;
|
|
20
27
|
private accessor _richTextElement;
|
|
21
28
|
accessor blockContainerStyles: {
|
|
22
29
|
margin: string;
|
|
@@ -42,9 +42,9 @@ import { DocModeProvider } from '../affine-shared/services/index.js';
|
|
|
42
42
|
import { getViewportElement } from '../affine-shared/utils/index.js';
|
|
43
43
|
import { BlockSelection, TextSelection } from '../std/index.js';
|
|
44
44
|
import { getInlineRangeProvider, } from '../std/inline/index.js';
|
|
45
|
-
import { effect } from '@preact/signals-core';
|
|
45
|
+
import { effect, signal } from '@preact/signals-core';
|
|
46
46
|
import { html, nothing } from 'lit';
|
|
47
|
-
import { query
|
|
47
|
+
import { query } from 'lit/decorators.js';
|
|
48
48
|
import { classMap } from 'lit/directives/class-map.js';
|
|
49
49
|
import { styleMap } from 'lit/directives/style-map.js';
|
|
50
50
|
import { correctNumberedListsOrderToPrev } from './commands/utils.js';
|
|
@@ -52,9 +52,6 @@ import { listBlockStyles } from './styles.js';
|
|
|
52
52
|
import { getListIcon } from './utils/get-list-icon.js';
|
|
53
53
|
let ListBlockComponent = (() => {
|
|
54
54
|
let _classSuper = CaptionedBlockComponent;
|
|
55
|
-
let __readonlyCollapsed_decorators;
|
|
56
|
-
let __readonlyCollapsed_initializers = [];
|
|
57
|
-
let __readonlyCollapsed_extraInitializers = [];
|
|
58
55
|
let __richTextElement_decorators;
|
|
59
56
|
let __richTextElement_initializers = [];
|
|
60
57
|
let __richTextElement_extraInitializers = [];
|
|
@@ -62,12 +59,19 @@ let ListBlockComponent = (() => {
|
|
|
62
59
|
constructor() {
|
|
63
60
|
super(...arguments);
|
|
64
61
|
this._inlineRangeProvider = null;
|
|
62
|
+
/**
|
|
63
|
+
* The collapse state a READER holds locally. Same contract as
|
|
64
|
+
* `affine-paragraph`: a readonly document is never written, so the toggle
|
|
65
|
+
* carries the reader's own expansion here instead of in the model. A signal
|
|
66
|
+
* rather than a Lit `@state()`, so BlockSuite effects can depend on it.
|
|
67
|
+
*/
|
|
68
|
+
this._readonlyCollapsed = signal(false);
|
|
65
69
|
this._onClickIcon = (e) => {
|
|
66
70
|
e.stopPropagation();
|
|
67
71
|
e.preventDefault();
|
|
68
72
|
if (this.model.props.type === 'toggle') {
|
|
69
73
|
if (this.store.readonly) {
|
|
70
|
-
this.
|
|
74
|
+
this._setReadonlyCollapsed(!this._readonlyCollapsed.value);
|
|
71
75
|
}
|
|
72
76
|
else {
|
|
73
77
|
this.store.captureSync();
|
|
@@ -93,21 +97,22 @@ let ListBlockComponent = (() => {
|
|
|
93
97
|
}
|
|
94
98
|
this._select();
|
|
95
99
|
};
|
|
96
|
-
this.#
|
|
97
|
-
this.#_richTextElement_accessor_storage = (__runInitializers(this, __readonlyCollapsed_extraInitializers), __runInitializers(this, __richTextElement_initializers, null));
|
|
100
|
+
this.#_richTextElement_accessor_storage = __runInitializers(this, __richTextElement_initializers, null);
|
|
98
101
|
this.#blockContainerStyles_accessor_storage = (__runInitializers(this, __richTextElement_extraInitializers), {
|
|
99
102
|
margin: 'var(--affine-list-margin, 10px 0)',
|
|
100
103
|
});
|
|
101
104
|
}
|
|
102
105
|
static {
|
|
103
106
|
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
104
|
-
__readonlyCollapsed_decorators = [state()];
|
|
105
107
|
__richTextElement_decorators = [query('rich-text')];
|
|
106
|
-
__esDecorate(this, null, __readonlyCollapsed_decorators, { kind: "accessor", name: "_readonlyCollapsed", static: false, private: false, access: { has: obj => "_readonlyCollapsed" in obj, get: obj => obj._readonlyCollapsed, set: (obj, value) => { obj._readonlyCollapsed = value; } }, metadata: _metadata }, __readonlyCollapsed_initializers, __readonlyCollapsed_extraInitializers);
|
|
107
108
|
__esDecorate(this, null, __richTextElement_decorators, { kind: "accessor", name: "_richTextElement", static: false, private: false, access: { has: obj => "_richTextElement" in obj, get: obj => obj._richTextElement, set: (obj, value) => { obj._richTextElement = value; } }, metadata: _metadata }, __richTextElement_initializers, __richTextElement_extraInitializers);
|
|
108
109
|
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
109
110
|
}
|
|
110
111
|
static { this.styles = listBlockStyles; }
|
|
112
|
+
_setReadonlyCollapsed(collapsed) {
|
|
113
|
+
this._readonlyCollapsed.value = collapsed;
|
|
114
|
+
this.requestUpdate();
|
|
115
|
+
}
|
|
111
116
|
get attributeRenderer() {
|
|
112
117
|
return this.inlineManager.getRenderer();
|
|
113
118
|
}
|
|
@@ -137,9 +142,14 @@ let ListBlockComponent = (() => {
|
|
|
137
142
|
connectedCallback() {
|
|
138
143
|
super.connectedCallback();
|
|
139
144
|
this._inlineRangeProvider = getInlineRangeProvider(this);
|
|
145
|
+
// Only ENTERING readonly mode (or a later change of the persisted value)
|
|
146
|
+
// seeds the reader's local state; while readonly holds, the reader's own
|
|
147
|
+
// expansion is never overwritten. Aligned on `affine-paragraph`, where the
|
|
148
|
+
// unguarded version was a real defect (#262).
|
|
140
149
|
this.disposables.add(effect(() => {
|
|
141
|
-
|
|
142
|
-
|
|
150
|
+
if (this.store.readonly$.value) {
|
|
151
|
+
this._setReadonlyCollapsed(this.model.props.collapsed$.value);
|
|
152
|
+
}
|
|
143
153
|
}));
|
|
144
154
|
this.disposables.add(effect(() => {
|
|
145
155
|
const type = this.model.props.type$.value;
|
|
@@ -162,10 +172,12 @@ let ListBlockComponent = (() => {
|
|
|
162
172
|
renderBlock() {
|
|
163
173
|
const { model, _onClickIcon } = this;
|
|
164
174
|
const collapsed = this.store.readonly
|
|
165
|
-
? this._readonlyCollapsed
|
|
175
|
+
? this._readonlyCollapsed.value
|
|
166
176
|
: model.props.collapsed;
|
|
167
177
|
const listIcon = getListIcon(model, !collapsed, _onClickIcon);
|
|
178
|
+
const childrenId = `list-children-${this.model.id}`;
|
|
168
179
|
const children = html `<div
|
|
180
|
+
id=${childrenId}
|
|
169
181
|
class="affine-block-children-container"
|
|
170
182
|
style=${styleMap({
|
|
171
183
|
paddingLeft: `${BLOCK_CHILDREN_CONTAINER_PADDING_LEFT}px`,
|
|
@@ -187,9 +199,10 @@ let ListBlockComponent = (() => {
|
|
|
187
199
|
? html `
|
|
188
200
|
<blocksuite-toggle-button
|
|
189
201
|
.collapsed=${collapsed}
|
|
202
|
+
.controls=${childrenId}
|
|
190
203
|
.updateCollapsed=${(value) => {
|
|
191
204
|
if (this.store.readonly) {
|
|
192
|
-
this.
|
|
205
|
+
this._setReadonlyCollapsed(value);
|
|
193
206
|
}
|
|
194
207
|
else {
|
|
195
208
|
this.store.captureSync();
|
|
@@ -222,9 +235,6 @@ let ListBlockComponent = (() => {
|
|
|
222
235
|
</div>
|
|
223
236
|
`;
|
|
224
237
|
}
|
|
225
|
-
#_readonlyCollapsed_accessor_storage;
|
|
226
|
-
get _readonlyCollapsed() { return this.#_readonlyCollapsed_accessor_storage; }
|
|
227
|
-
set _readonlyCollapsed(value) { this.#_readonlyCollapsed_accessor_storage = value; }
|
|
228
238
|
#_richTextElement_accessor_storage;
|
|
229
239
|
get _richTextElement() { return this.#_richTextElement_accessor_storage; }
|
|
230
240
|
set _richTextElement(value) { this.#_richTextElement_accessor_storage = value; }
|
|
@@ -7,9 +7,17 @@ export declare class ParagraphBlockComponent extends CaptionedBlockComponent<Par
|
|
|
7
7
|
focused$: import("@preact/signals-core").ReadonlySignal<boolean>;
|
|
8
8
|
private readonly _composing;
|
|
9
9
|
private readonly _displayPlaceholder;
|
|
10
|
+
/**
|
|
11
|
+
* The collapse state a READER holds locally. A readonly document must never
|
|
12
|
+
* be written, so the chevron cannot touch `model.props.collapsed`; this
|
|
13
|
+
* signal carries the reader's own expansion instead. It is a signal rather
|
|
14
|
+
* than a Lit `@state()` so BlockSuite effects can depend on it as well.
|
|
15
|
+
*/
|
|
16
|
+
private readonly _readonlyCollapsed;
|
|
10
17
|
private _inlineRangeProvider;
|
|
11
18
|
private readonly _isInDatabase;
|
|
12
19
|
private get _placeholder();
|
|
20
|
+
private _setReadonlyCollapsed;
|
|
13
21
|
get citationService(): import("../affine-shared/services/index.js").CitationViewService;
|
|
14
22
|
get attributeRenderer(): import("../std/inline/types.js").AttributeRenderer<import("../affine-shared/types/index.js").AffineTextAttributes>;
|
|
15
23
|
get attributesSchema(): import("zod").ZodType<any, import("zod").ZodTypeDef, any>;
|
|
@@ -24,7 +32,6 @@ export declare class ParagraphBlockComponent extends CaptionedBlockComponent<Par
|
|
|
24
32
|
connectedCallback(): void;
|
|
25
33
|
getUpdateComplete(): Promise<boolean>;
|
|
26
34
|
renderBlock(): TemplateResult<1>;
|
|
27
|
-
private accessor _readonlyCollapsed;
|
|
28
35
|
private accessor _richTextElement;
|
|
29
36
|
accessor blockContainerStyles: {
|
|
30
37
|
margin: string;
|
|
@@ -42,7 +42,7 @@ import { TextSelection } from '../std/index.js';
|
|
|
42
42
|
import { getInlineRangeProvider, } from '../std/inline/index.js';
|
|
43
43
|
import { computed, effect, signal } from '@preact/signals-core';
|
|
44
44
|
import { html, nothing } from 'lit';
|
|
45
|
-
import { query
|
|
45
|
+
import { query } from 'lit/decorators.js';
|
|
46
46
|
import { classMap } from 'lit/directives/class-map.js';
|
|
47
47
|
import { styleMap } from 'lit/directives/style-map.js';
|
|
48
48
|
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
|
|
@@ -50,9 +50,6 @@ import { ParagraphBlockConfigExtension } from './paragraph-block-config.js';
|
|
|
50
50
|
import { paragraphBlockStyles } from './styles.js';
|
|
51
51
|
let ParagraphBlockComponent = (() => {
|
|
52
52
|
let _classSuper = CaptionedBlockComponent;
|
|
53
|
-
let __readonlyCollapsed_decorators;
|
|
54
|
-
let __readonlyCollapsed_initializers = [];
|
|
55
|
-
let __readonlyCollapsed_extraInitializers = [];
|
|
56
53
|
let __richTextElement_decorators;
|
|
57
54
|
let __richTextElement_initializers = [];
|
|
58
55
|
let __richTextElement_extraInitializers = [];
|
|
@@ -67,6 +64,13 @@ let ParagraphBlockComponent = (() => {
|
|
|
67
64
|
});
|
|
68
65
|
this._composing = signal(false);
|
|
69
66
|
this._displayPlaceholder = signal(false);
|
|
67
|
+
/**
|
|
68
|
+
* The collapse state a READER holds locally. A readonly document must never
|
|
69
|
+
* be written, so the chevron cannot touch `model.props.collapsed`; this
|
|
70
|
+
* signal carries the reader's own expansion instead. It is a signal rather
|
|
71
|
+
* than a Lit `@state()` so BlockSuite effects can depend on it as well.
|
|
72
|
+
*/
|
|
73
|
+
this._readonlyCollapsed = signal(false);
|
|
70
74
|
this._inlineRangeProvider = null;
|
|
71
75
|
this._isInDatabase = () => {
|
|
72
76
|
let parent = this.parentElement;
|
|
@@ -78,17 +82,14 @@ let ParagraphBlockComponent = (() => {
|
|
|
78
82
|
}
|
|
79
83
|
return false;
|
|
80
84
|
};
|
|
81
|
-
this.#
|
|
82
|
-
this.#_richTextElement_accessor_storage = (__runInitializers(this, __readonlyCollapsed_extraInitializers), __runInitializers(this, __richTextElement_initializers, null));
|
|
85
|
+
this.#_richTextElement_accessor_storage = __runInitializers(this, __richTextElement_initializers, null);
|
|
83
86
|
this.#blockContainerStyles_accessor_storage = (__runInitializers(this, __richTextElement_extraInitializers), {
|
|
84
87
|
margin: 'var(--affine-paragraph-margin, 10px 0)',
|
|
85
88
|
});
|
|
86
89
|
}
|
|
87
90
|
static {
|
|
88
91
|
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
89
|
-
__readonlyCollapsed_decorators = [state()];
|
|
90
92
|
__richTextElement_decorators = [query('rich-text')];
|
|
91
|
-
__esDecorate(this, null, __readonlyCollapsed_decorators, { kind: "accessor", name: "_readonlyCollapsed", static: false, private: false, access: { has: obj => "_readonlyCollapsed" in obj, get: obj => obj._readonlyCollapsed, set: (obj, value) => { obj._readonlyCollapsed = value; } }, metadata: _metadata }, __readonlyCollapsed_initializers, __readonlyCollapsed_extraInitializers);
|
|
92
93
|
__esDecorate(this, null, __richTextElement_decorators, { kind: "accessor", name: "_richTextElement", static: false, private: false, access: { has: obj => "_richTextElement" in obj, get: obj => obj._richTextElement, set: (obj, value) => { obj._richTextElement = value; } }, metadata: _metadata }, __richTextElement_initializers, __richTextElement_extraInitializers);
|
|
93
94
|
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
94
95
|
}
|
|
@@ -98,6 +99,10 @@ let ParagraphBlockComponent = (() => {
|
|
|
98
99
|
.get(ParagraphBlockConfigExtension.identifier)
|
|
99
100
|
?.getPlaceholder(this.model);
|
|
100
101
|
}
|
|
102
|
+
_setReadonlyCollapsed(collapsed) {
|
|
103
|
+
this._readonlyCollapsed.value = collapsed;
|
|
104
|
+
this.requestUpdate();
|
|
105
|
+
}
|
|
101
106
|
get citationService() {
|
|
102
107
|
return this.std.get(CitationProvider);
|
|
103
108
|
}
|
|
@@ -176,9 +181,19 @@ let ParagraphBlockComponent = (() => {
|
|
|
176
181
|
this.model.props.collapsed = false;
|
|
177
182
|
}
|
|
178
183
|
}));
|
|
184
|
+
// Entering readonly mode (and any later change of the persisted value)
|
|
185
|
+
// seeds the reader's local state. Deliberately NOT re-run on every
|
|
186
|
+
// selection change: this effect must not depend on the selection, or the
|
|
187
|
+
// reader's own expansion would be overwritten mid-drag.
|
|
188
|
+
this.disposables.add(effect(() => {
|
|
189
|
+
if (this.store.readonly$.value) {
|
|
190
|
+
this._setReadonlyCollapsed(this.model.props.collapsed$.value);
|
|
191
|
+
}
|
|
192
|
+
}));
|
|
179
193
|
this.disposables.add(effect(() => {
|
|
180
|
-
const collapsed = this.
|
|
181
|
-
|
|
194
|
+
const collapsed = this.store.readonly
|
|
195
|
+
? this._readonlyCollapsed.value
|
|
196
|
+
: this.model.props.collapsed$.value;
|
|
182
197
|
// reset text selection when selected block is collapsed
|
|
183
198
|
if (this.model.props.type$.value.startsWith('h') && collapsed) {
|
|
184
199
|
const collapsedSiblings = this.collapsedSiblings;
|
|
@@ -216,7 +231,7 @@ let ParagraphBlockComponent = (() => {
|
|
|
216
231
|
renderBlock() {
|
|
217
232
|
const { type$ } = this.model.props;
|
|
218
233
|
const collapsed = this.store.readonly
|
|
219
|
-
? this._readonlyCollapsed
|
|
234
|
+
? this._readonlyCollapsed.value
|
|
220
235
|
: this.model.props.collapsed;
|
|
221
236
|
const collapsedSiblings = this.collapsedSiblings;
|
|
222
237
|
let style = html ``;
|
|
@@ -231,7 +246,9 @@ let ParagraphBlockComponent = (() => {
|
|
|
231
246
|
</style>
|
|
232
247
|
`;
|
|
233
248
|
}
|
|
249
|
+
const childrenId = `heading-children-${this.model.id}`;
|
|
234
250
|
const children = html `<div
|
|
251
|
+
id=${childrenId}
|
|
235
252
|
class="affine-block-children-container"
|
|
236
253
|
style=${styleMap({
|
|
237
254
|
paddingLeft: `${BLOCK_CHILDREN_CONTAINER_PADDING_LEFT}px`,
|
|
@@ -275,9 +292,10 @@ let ParagraphBlockComponent = (() => {
|
|
|
275
292
|
? html `
|
|
276
293
|
<blocksuite-toggle-button
|
|
277
294
|
.collapsed=${collapsed}
|
|
295
|
+
.controls=${childrenId}
|
|
278
296
|
.updateCollapsed=${(value) => {
|
|
279
297
|
if (this.store.readonly) {
|
|
280
|
-
this.
|
|
298
|
+
this._setReadonlyCollapsed(value);
|
|
281
299
|
}
|
|
282
300
|
else {
|
|
283
301
|
this.store.captureSync();
|
|
@@ -328,9 +346,6 @@ let ParagraphBlockComponent = (() => {
|
|
|
328
346
|
</div>
|
|
329
347
|
`;
|
|
330
348
|
}
|
|
331
|
-
#_readonlyCollapsed_accessor_storage;
|
|
332
|
-
get _readonlyCollapsed() { return this.#_readonlyCollapsed_accessor_storage; }
|
|
333
|
-
set _readonlyCollapsed(value) { this.#_readonlyCollapsed_accessor_storage = value; }
|
|
334
349
|
#_richTextElement_accessor_storage;
|
|
335
350
|
get _richTextElement() { return this.#_richTextElement_accessor_storage; }
|
|
336
351
|
set _richTextElement(value) { this.#_richTextElement_accessor_storage = value; }
|
|
@@ -4,7 +4,7 @@ import { addImages } from '../../../affine-block-image/index.js';
|
|
|
4
4
|
import { CanvasRenderer, DefaultTool, EdgelessCRUDIdentifier, ExportManager, getSurfaceComponent, } from '../../../affine-block-surface/index.js';
|
|
5
5
|
import { splitIntoLines } from '../../../affine-gfx-text/index.js';
|
|
6
6
|
import { BookmarkStyles, DEFAULT_NOTE_HEIGHT, DEFAULT_NOTE_WIDTH, FrameBlockModel, MAX_IMAGE_WIDTH, } from '../../../affine-model/index.js';
|
|
7
|
-
import { ClipboardAdapter, decodeClipboardBlobs, } from '../../../affine-shared/adapters/index.js';
|
|
7
|
+
import { ClipboardAdapter, decodeClipboardBlobs, ImageProxyService, } from '../../../affine-shared/adapters/index.js';
|
|
8
8
|
import { CANVAS_EXPORT_IGNORE_TAGS, EMBED_CARD_HEIGHT, EMBED_CARD_WIDTH, } from '../../../affine-shared/consts/index.js';
|
|
9
9
|
import { EmbedOptionProvider, ParseDocUrlProvider, TelemetryProvider, } from '../../../affine-shared/services/index.js';
|
|
10
10
|
import { convertToPng, isInsidePageEditor, isTopLevelBlock, isUrlInClipboard, matchModels, referenceToNode, } from '../../../affine-shared/utils/index.js';
|
|
@@ -324,7 +324,7 @@ export class EdgelessClipboardController extends PageClipboard {
|
|
|
324
324
|
}
|
|
325
325
|
ctx.scale(dpr, dpr);
|
|
326
326
|
const replaceImgSrcWithSvg = this._exportManager?.replaceImgSrcWithSvg;
|
|
327
|
-
const imageProxy = host.std.
|
|
327
|
+
const imageProxy = host.std.get(ImageProxyService).imageProxyURL;
|
|
328
328
|
const html2canvasOption = {
|
|
329
329
|
ignoreElements: function (element) {
|
|
330
330
|
if (CANVAS_EXPORT_IGNORE_TAGS.includes(element.tagName) ||
|
|
@@ -351,7 +351,7 @@ export class EdgelessClipboardController extends PageClipboard {
|
|
|
351
351
|
await replaceImgSrcWithSvg?.(element);
|
|
352
352
|
},
|
|
353
353
|
backgroundColor: 'transparent',
|
|
354
|
-
useCORS: imageProxy
|
|
354
|
+
useCORS: !imageProxy,
|
|
355
355
|
proxy: imageProxy,
|
|
356
356
|
};
|
|
357
357
|
const _drawTopLevelBlock = async (block, isInFrame = false) => {
|
|
@@ -1,13 +1,33 @@
|
|
|
1
1
|
import { KeymapExtension } from '../../std/index.js';
|
|
2
|
+
const NATIVE_FOCUSABLE_SELECTOR = 'button, a[href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
|
|
3
|
+
/**
|
|
4
|
+
* The editor host swallows Tab and Shift-Tab so that indentation stays under
|
|
5
|
+
* the control of the block-scoped handlers. That would however trap the focus
|
|
6
|
+
* on the native controls the editor renders (the collapse button of headings
|
|
7
|
+
* and lists, for instance), which then could neither be reached nor left with
|
|
8
|
+
* the keyboard. When the event originates from such a control — and not from
|
|
9
|
+
* editable text — the native tabbing behaviour is let through.
|
|
10
|
+
*/
|
|
11
|
+
const isNativeFocusableTarget = (target) => {
|
|
12
|
+
if (!(target instanceof Element))
|
|
13
|
+
return false;
|
|
14
|
+
if (target instanceof HTMLElement && target.isContentEditable)
|
|
15
|
+
return false;
|
|
16
|
+
return !!target.closest(NATIVE_FOCUSABLE_SELECTOR);
|
|
17
|
+
};
|
|
2
18
|
export const fallbackKeymap = KeymapExtension(() => {
|
|
3
19
|
return {
|
|
4
20
|
Tab: ctx => {
|
|
5
21
|
const event = ctx.get('defaultState').event;
|
|
22
|
+
if (isNativeFocusableTarget(event.target))
|
|
23
|
+
return;
|
|
6
24
|
event.stopPropagation();
|
|
7
25
|
event.preventDefault();
|
|
8
26
|
},
|
|
9
27
|
'Shift-Tab': ctx => {
|
|
10
28
|
const event = ctx.get('defaultState').event;
|
|
29
|
+
if (isNativeFocusableTarget(event.target))
|
|
30
|
+
return;
|
|
11
31
|
event.stopPropagation();
|
|
12
32
|
event.preventDefault();
|
|
13
33
|
},
|
|
@@ -3,12 +3,14 @@ import { type BlockStdScope, type EditorHost } from '../../../std/index.js';
|
|
|
3
3
|
import { GfxBlockElementModel, type GfxController, GfxPrimitiveElementModel } from '../../../std/gfx/index.js';
|
|
4
4
|
import type { ExtensionType, Store } from '../../../store/index.js';
|
|
5
5
|
import { CanvasRenderer } from '../../renderer/canvas-renderer.js';
|
|
6
|
-
export type ExportOptions = {
|
|
7
|
-
imageProxyEndpoint: string;
|
|
8
|
-
};
|
|
9
6
|
export declare class ExportManager {
|
|
10
7
|
readonly std: BlockStdScope;
|
|
11
|
-
|
|
8
|
+
/**
|
|
9
|
+
* The image proxy every remote-image fetch of the export goes through —
|
|
10
|
+
* resolved from {@link ImageProxyService}, the one host seam for it, never
|
|
11
|
+
* from a local constant. Empty means « fetch direct » (issue #258).
|
|
12
|
+
*/
|
|
13
|
+
private get _imageProxyURL();
|
|
12
14
|
replaceImgSrcWithSvg: (element: HTMLElement) => Promise<void>;
|
|
13
15
|
get doc(): Store;
|
|
14
16
|
get editorHost(): EditorHost;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ImageBlockModel } from '../../../affine-model/index.js';
|
|
2
|
-
import { FetchUtils } from '../../../affine-shared/adapters/index.js';
|
|
3
|
-
import { CANVAS_EXPORT_IGNORE_TAGS
|
|
2
|
+
import { FetchUtils, ImageProxyService } from '../../../affine-shared/adapters/index.js';
|
|
3
|
+
import { CANVAS_EXPORT_IGNORE_TAGS } from '../../../affine-shared/consts/index.js';
|
|
4
4
|
import { isInsidePageEditor, matchModels } from '../../../affine-shared/utils/index.js';
|
|
5
5
|
import { BlockSuiteError, ErrorCode } from '../../../global/exceptions/index.js';
|
|
6
6
|
import { deserializeXYWH } from '../../../global/gfx/index.js';
|
|
@@ -10,6 +10,14 @@ import { CanvasRenderer } from '../../renderer/canvas-renderer.js';
|
|
|
10
10
|
import { getBgGridGap } from '../../utils/get-bg-grip-gap.js';
|
|
11
11
|
import { FileExporter } from './file-exporter.js';
|
|
12
12
|
export class ExportManager {
|
|
13
|
+
/**
|
|
14
|
+
* The image proxy every remote-image fetch of the export goes through —
|
|
15
|
+
* resolved from {@link ImageProxyService}, the one host seam for it, never
|
|
16
|
+
* from a local constant. Empty means « fetch direct » (issue #258).
|
|
17
|
+
*/
|
|
18
|
+
get _imageProxyURL() {
|
|
19
|
+
return this.std.get(ImageProxyService).imageProxyURL;
|
|
20
|
+
}
|
|
13
21
|
get doc() {
|
|
14
22
|
return this.std.store;
|
|
15
23
|
}
|
|
@@ -18,14 +26,11 @@ export class ExportManager {
|
|
|
18
26
|
}
|
|
19
27
|
constructor(std) {
|
|
20
28
|
this.std = std;
|
|
21
|
-
this._exportOptions = {
|
|
22
|
-
imageProxyEndpoint: DEFAULT_IMAGE_PROXY_ENDPOINT,
|
|
23
|
-
};
|
|
24
29
|
this.replaceImgSrcWithSvg = async (element) => {
|
|
25
30
|
const imgList = Array.from(element.querySelectorAll('img'));
|
|
26
31
|
// Create an array of promises
|
|
27
32
|
const promises = imgList.map(img => {
|
|
28
|
-
return FetchUtils.fetchImage(img.src, undefined, this.
|
|
33
|
+
return FetchUtils.fetchImage(img.src, undefined, this._imageProxyURL)
|
|
29
34
|
.then(response => response && response.blob())
|
|
30
35
|
.then(async (blob) => {
|
|
31
36
|
if (!blob)
|
|
@@ -159,8 +164,8 @@ export class ExportManager {
|
|
|
159
164
|
x: pageLeft - viewport.left,
|
|
160
165
|
width: pageWidth,
|
|
161
166
|
height: viewportHeight,
|
|
162
|
-
useCORS: this.
|
|
163
|
-
proxy: this.
|
|
167
|
+
useCORS: !this._imageProxyURL,
|
|
168
|
+
proxy: this._imageProxyURL,
|
|
164
169
|
};
|
|
165
170
|
let data;
|
|
166
171
|
try {
|
|
@@ -228,8 +233,8 @@ export class ExportManager {
|
|
|
228
233
|
});
|
|
229
234
|
await this.replaceImgSrcWithSvg(element);
|
|
230
235
|
},
|
|
231
|
-
useCORS: this.
|
|
232
|
-
proxy: this.
|
|
236
|
+
useCORS: !this._imageProxyURL,
|
|
237
|
+
proxy: this._imageProxyURL,
|
|
233
238
|
};
|
|
234
239
|
let data;
|
|
235
240
|
try {
|
|
@@ -21,9 +21,9 @@ export class PriorityQueue {
|
|
|
21
21
|
if (rightChildIndex < length) {
|
|
22
22
|
leftChild = this.heap[leftChildIndex];
|
|
23
23
|
rightChild = this.heap[rightChildIndex];
|
|
24
|
-
if ((swap ===
|
|
24
|
+
if ((swap === -1 &&
|
|
25
25
|
this._compare(rightChild.priority, element.priority) < 0) ||
|
|
26
|
-
(swap !==
|
|
26
|
+
(swap !== -1 &&
|
|
27
27
|
this._compare(rightChild.priority, leftChild.priority) < 0)) {
|
|
28
28
|
swap = rightChildIndex;
|
|
29
29
|
}
|
|
@@ -6,6 +6,11 @@ export declare class ToggleButton extends ToggleButton_base {
|
|
|
6
6
|
render(): import("lit-html").TemplateResult<1>;
|
|
7
7
|
accessor collapsed: boolean;
|
|
8
8
|
accessor updateCollapsed: (collapsed: boolean) => void;
|
|
9
|
+
/**
|
|
10
|
+
* Id of the children container this button expands and collapses,
|
|
11
|
+
* exposed to assistive technologies through `aria-controls`.
|
|
12
|
+
*/
|
|
13
|
+
accessor controls: string;
|
|
9
14
|
}
|
|
10
15
|
declare global {
|
|
11
16
|
interface HTMLElementTagNameMap {
|
|
@@ -48,13 +48,18 @@ let ToggleButton = (() => {
|
|
|
48
48
|
let _updateCollapsed_decorators;
|
|
49
49
|
let _updateCollapsed_initializers = [];
|
|
50
50
|
let _updateCollapsed_extraInitializers = [];
|
|
51
|
+
let _controls_decorators;
|
|
52
|
+
let _controls_initializers = [];
|
|
53
|
+
let _controls_extraInitializers = [];
|
|
51
54
|
return class ToggleButton extends _classSuper {
|
|
52
55
|
static {
|
|
53
56
|
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
|
54
57
|
_collapsed_decorators = [property({ attribute: false })];
|
|
55
58
|
_updateCollapsed_decorators = [property({ attribute: false })];
|
|
59
|
+
_controls_decorators = [property({ attribute: false })];
|
|
56
60
|
__esDecorate(this, null, _collapsed_decorators, { kind: "accessor", name: "collapsed", static: false, private: false, access: { has: obj => "collapsed" in obj, get: obj => obj.collapsed, set: (obj, value) => { obj.collapsed = value; } }, metadata: _metadata }, _collapsed_initializers, _collapsed_extraInitializers);
|
|
57
61
|
__esDecorate(this, null, _updateCollapsed_decorators, { kind: "accessor", name: "updateCollapsed", static: false, private: false, access: { has: obj => "updateCollapsed" in obj, get: obj => obj.updateCollapsed, set: (obj, value) => { obj.updateCollapsed = value; } }, metadata: _metadata }, _updateCollapsed_initializers, _updateCollapsed_extraInitializers);
|
|
62
|
+
__esDecorate(this, null, _controls_decorators, { kind: "accessor", name: "controls", static: false, private: false, access: { has: obj => "controls" in obj, get: obj => obj.controls, set: (obj, value) => { obj.controls = value; } }, metadata: _metadata }, _controls_initializers, _controls_extraInitializers);
|
|
58
63
|
if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
59
64
|
}
|
|
60
65
|
static { this.styles = css `
|
|
@@ -63,6 +68,11 @@ let ToggleButton = (() => {
|
|
|
63
68
|
align-items: start;
|
|
64
69
|
justify-content: start;
|
|
65
70
|
position: absolute;
|
|
71
|
+
padding: 0;
|
|
72
|
+
border: 0;
|
|
73
|
+
color: inherit;
|
|
74
|
+
background: transparent;
|
|
75
|
+
font: inherit;
|
|
66
76
|
width: 16px;
|
|
67
77
|
height: 16px;
|
|
68
78
|
top: calc((1em - 16px) / 2 + 5px);
|
|
@@ -78,6 +88,11 @@ let ToggleButton = (() => {
|
|
|
78
88
|
background: var(--affine-hover-color);
|
|
79
89
|
}
|
|
80
90
|
|
|
91
|
+
.toggle-icon:focus-visible {
|
|
92
|
+
opacity: 1;
|
|
93
|
+
outline: 1px solid var(--affine-primary-color);
|
|
94
|
+
}
|
|
95
|
+
|
|
81
96
|
.toggle-icon[data-collapsed='true'] {
|
|
82
97
|
opacity: 1;
|
|
83
98
|
}
|
|
@@ -100,32 +115,23 @@ let ToggleButton = (() => {
|
|
|
100
115
|
}
|
|
101
116
|
`; }
|
|
102
117
|
render() {
|
|
103
|
-
|
|
104
|
-
<
|
|
105
|
-
|
|
106
|
-
class="toggle-icon"
|
|
107
|
-
@click=${() => this.updateCollapsed(!this.collapsed)}
|
|
108
|
-
>
|
|
109
|
-
${ToggleDownIcon({
|
|
110
|
-
width: '16px',
|
|
111
|
-
height: '16px',
|
|
112
|
-
})}
|
|
113
|
-
</div>
|
|
114
|
-
`;
|
|
115
|
-
const toggleRightTemplate = html `
|
|
116
|
-
<div
|
|
118
|
+
return html `
|
|
119
|
+
<button
|
|
120
|
+
type="button"
|
|
117
121
|
contenteditable="false"
|
|
118
122
|
class="toggle-icon"
|
|
119
123
|
data-collapsed=${this.collapsed}
|
|
124
|
+
aria-label=${this.collapsed ? 'Expand content' : 'Collapse content'}
|
|
125
|
+
aria-expanded=${!this.collapsed}
|
|
126
|
+
aria-controls=${this.controls}
|
|
120
127
|
@click=${() => this.updateCollapsed(!this.collapsed)}
|
|
121
128
|
>
|
|
122
|
-
${ToggleRightIcon({
|
|
129
|
+
${(this.collapsed ? ToggleRightIcon : ToggleDownIcon)({
|
|
123
130
|
width: '16px',
|
|
124
131
|
height: '16px',
|
|
125
132
|
})}
|
|
126
|
-
</
|
|
133
|
+
</button>
|
|
127
134
|
`;
|
|
128
|
-
return this.collapsed ? toggleRightTemplate : toggleDownTemplate;
|
|
129
135
|
}
|
|
130
136
|
#collapsed_accessor_storage = __runInitializers(this, _collapsed_initializers, void 0);
|
|
131
137
|
get collapsed() { return this.#collapsed_accessor_storage; }
|
|
@@ -133,9 +139,16 @@ let ToggleButton = (() => {
|
|
|
133
139
|
#updateCollapsed_accessor_storage = (__runInitializers(this, _collapsed_extraInitializers), __runInitializers(this, _updateCollapsed_initializers, void 0));
|
|
134
140
|
get updateCollapsed() { return this.#updateCollapsed_accessor_storage; }
|
|
135
141
|
set updateCollapsed(value) { this.#updateCollapsed_accessor_storage = value; }
|
|
142
|
+
#controls_accessor_storage = (__runInitializers(this, _updateCollapsed_extraInitializers), __runInitializers(this, _controls_initializers, void 0));
|
|
143
|
+
/**
|
|
144
|
+
* Id of the children container this button expands and collapses,
|
|
145
|
+
* exposed to assistive technologies through `aria-controls`.
|
|
146
|
+
*/
|
|
147
|
+
get controls() { return this.#controls_accessor_storage; }
|
|
148
|
+
set controls(value) { this.#controls_accessor_storage = value; }
|
|
136
149
|
constructor() {
|
|
137
150
|
super(...arguments);
|
|
138
|
-
__runInitializers(this,
|
|
151
|
+
__runInitializers(this, _controls_extraInitializers);
|
|
139
152
|
}
|
|
140
153
|
};
|
|
141
154
|
})();
|
|
@@ -2,7 +2,17 @@ import type { TransformerMiddleware } from '../../../store/index.js';
|
|
|
2
2
|
import { StoreExtension } from '../../../store/index.js';
|
|
3
3
|
export declare const customImageProxyMiddleware: (imageProxyURL: string) => TransformerMiddleware;
|
|
4
4
|
export declare const setImageProxyMiddlewareURL: (url: string) => void;
|
|
5
|
+
/**
|
|
6
|
+
* Reads the URL at run time: a `setImageProxyURL` made after module load
|
|
7
|
+
* reaches every job using this middleware.
|
|
8
|
+
*/
|
|
5
9
|
export declare const defaultImageProxyMiddleware: TransformerMiddleware;
|
|
10
|
+
/**
|
|
11
|
+
* The ONE seam governing every remote-image fetch the library performs on the
|
|
12
|
+
* host's behalf: link-card favicons and og:images, image import, canvas
|
|
13
|
+
* PNG/PDF export, copy-as-image. An empty URL means « fetch direct ».
|
|
14
|
+
* See `docs/integrate/04-host-seams.md`.
|
|
15
|
+
*/
|
|
6
16
|
export declare class ImageProxyService extends StoreExtension {
|
|
7
17
|
static key: string;
|
|
8
18
|
private _imageProxyURL;
|
|
@@ -5,18 +5,25 @@ export const customImageProxyMiddleware = (imageProxyURL) => {
|
|
|
5
5
|
adapterConfigs.set('imageProxy', imageProxyURL);
|
|
6
6
|
};
|
|
7
7
|
};
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
8
|
+
// Module-wide so that transformer jobs built outside a store (playground
|
|
9
|
+
// panels, host-side importers) follow `setImageProxyURL` too.
|
|
10
|
+
let imageProxyMiddlewareURL = DEFAULT_IMAGE_PROXY_ENDPOINT;
|
|
11
|
+
export const setImageProxyMiddlewareURL = (url) => {
|
|
12
|
+
imageProxyMiddlewareURL = url;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Reads the URL at run time: a `setImageProxyURL` made after module load
|
|
16
|
+
* reaches every job using this middleware.
|
|
17
|
+
*/
|
|
18
|
+
export const defaultImageProxyMiddleware = ({ adapterConfigs, }) => {
|
|
19
|
+
adapterConfigs.set('imageProxy', imageProxyMiddlewareURL);
|
|
16
20
|
};
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
/**
|
|
22
|
+
* The ONE seam governing every remote-image fetch the library performs on the
|
|
23
|
+
* host's behalf: link-card favicons and og:images, image import, canvas
|
|
24
|
+
* PNG/PDF export, copy-as-image. An empty URL means « fetch direct ».
|
|
25
|
+
* See `docs/integrate/04-host-seams.md`.
|
|
26
|
+
*/
|
|
20
27
|
// TODO(@mirone): this should be configured when setup instead of runtime
|
|
21
28
|
export class ImageProxyService extends StoreExtension {
|
|
22
29
|
constructor() {
|
|
@@ -29,7 +36,7 @@ export class ImageProxyService extends StoreExtension {
|
|
|
29
36
|
setImageProxyMiddlewareURL(url);
|
|
30
37
|
}
|
|
31
38
|
buildUrl(imageUrl) {
|
|
32
|
-
if (imageUrl.startsWith(this.imageProxyURL)) {
|
|
39
|
+
if (!this.imageProxyURL || imageUrl.startsWith(this.imageProxyURL)) {
|
|
33
40
|
return imageUrl;
|
|
34
41
|
}
|
|
35
42
|
return `${this.imageProxyURL}?url=${encodeURIComponent(imageUrl)}`;
|
|
@@ -12,6 +12,10 @@ const fetchImage = async (url, init, proxy) => {
|
|
|
12
12
|
if (url.startsWith(window.location.origin)) {
|
|
13
13
|
return await fetch(url, init);
|
|
14
14
|
}
|
|
15
|
+
// Already proxied (a link card renders `buildUrl(...)`): do not wrap twice.
|
|
16
|
+
if (url.startsWith(proxy)) {
|
|
17
|
+
return await fetch(url, init);
|
|
18
|
+
}
|
|
15
19
|
return await fetch(proxy + '?url=' + encodeURIComponent(url), init)
|
|
16
20
|
.then(res => {
|
|
17
21
|
if (!res.ok) {
|
|
@@ -227,7 +227,7 @@ export declare abstract class GfxPrimitiveElementModel<Props extends BaseElement
|
|
|
227
227
|
* This is a direct write path into the element's Y.Map, on the class that
|
|
228
228
|
* carries the document format, exported by `@labre/std` and therefore
|
|
229
229
|
* callable by a host. `_assignElementProp` learned the same lesson in the
|
|
230
|
-
* unknown-props change (see
|
|
230
|
+
* unknown-props change (see the unknown-props spike (PR #73),
|
|
231
231
|
* whose deny-list is explicitly security rather than hygiene); an unguarded
|
|
232
232
|
* delete re-opens the door from the other side. So:
|
|
233
233
|
*
|
|
@@ -467,7 +467,7 @@ export declare abstract class GfxPrimitiveElementModel<Props extends BaseElement
|
|
|
467
467
|
* An element re-created from props (paste, duplicate, alt-drag clone,
|
|
468
468
|
* template insertion) only reaches the Y.Map through keys that have a
|
|
469
469
|
* declared accessor, so a payload declared per subclass would be silently
|
|
470
|
-
* dropped on copy — the exact loss
|
|
470
|
+
* dropped on copy — the exact loss the unknown-props spike (PR #73)
|
|
471
471
|
* exists to prevent. Declaring it, rather than leaning on that spike's
|
|
472
472
|
* verbatim fallback for undeclared keys, is what makes it a contract: a field
|
|
473
473
|
* nothing declares is a field nothing can test, strip or find.
|
|
@@ -407,7 +407,7 @@ let GfxPrimitiveElementModel = (() => {
|
|
|
407
407
|
* This is a direct write path into the element's Y.Map, on the class that
|
|
408
408
|
* carries the document format, exported by `@labre/std` and therefore
|
|
409
409
|
* callable by a host. `_assignElementProp` learned the same lesson in the
|
|
410
|
-
* unknown-props change (see
|
|
410
|
+
* unknown-props change (see the unknown-props spike (PR #73),
|
|
411
411
|
* whose deny-list is explicitly security rather than hygiene); an unguarded
|
|
412
412
|
* delete re-opens the door from the other side. So:
|
|
413
413
|
*
|
|
@@ -692,7 +692,7 @@ let GfxPrimitiveElementModel = (() => {
|
|
|
692
692
|
* An element re-created from props (paste, duplicate, alt-drag clone,
|
|
693
693
|
* template insertion) only reaches the Y.Map through keys that have a
|
|
694
694
|
* declared accessor, so a payload declared per subclass would be silently
|
|
695
|
-
* dropped on copy — the exact loss
|
|
695
|
+
* dropped on copy — the exact loss the unknown-props spike (PR #73)
|
|
696
696
|
* exists to prevent. Declaring it, rather than leaning on that spike's
|
|
697
697
|
* verbatim fallback for undeclared keys, is what makes it a contract: a field
|
|
698
698
|
* nothing declares is a field nothing can test, strip or find.
|
|
@@ -134,7 +134,7 @@ export declare class SurfaceBlockModel extends BlockModel<SurfaceBlockProps> {
|
|
|
134
134
|
* The value is already Y-converted at this point: both call sites run the
|
|
135
135
|
* whole props object through {@link _propsToY} first, which is key-agnostic.
|
|
136
136
|
*
|
|
137
|
-
* See
|
|
137
|
+
* See the unknown-props spike (PR #73).
|
|
138
138
|
*/
|
|
139
139
|
private _assignElementProp;
|
|
140
140
|
private _createElementFromYMap;
|
|
@@ -294,7 +294,7 @@ export class SurfaceBlockModel extends BlockModel {
|
|
|
294
294
|
* The value is already Y-converted at this point: both call sites run the
|
|
295
295
|
* whole props object through {@link _propsToY} first, which is key-agnostic.
|
|
296
296
|
*
|
|
297
|
-
* See
|
|
297
|
+
* See the unknown-props spike (PR #73).
|
|
298
298
|
*/
|
|
299
299
|
_assignElementProp(element, key, value) {
|
|
300
300
|
if (UNSAFE_ELEMENT_PROP_KEYS.has(key)) {
|