autumnnote 1.0.1 → 1.0.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.
@@ -21,14 +21,23 @@ export class VideoResizer {
21
21
  this._activeWrapper = null;
22
22
  this._overlay = null;
23
23
  this._disposers = [];
24
+ this._positionRaf = null;
24
25
  }
25
26
 
26
27
  initialize() {
27
28
  this._overlay = this._buildOverlay();
28
- document.body.appendChild(this._overlay);
29
+ const container = this.context.layoutInfo.editable.closest('.an-container') || document.body;
30
+ container.appendChild(this._overlay);
31
+ this._container = container;
29
32
 
30
33
  const editable = this.context.layoutInfo.editable;
31
34
 
35
+ let _resizeDebounce = null;
36
+ const onWindowResize = () => {
37
+ clearTimeout(_resizeDebounce);
38
+ _resizeDebounce = setTimeout(() => this._updateOverlayPosition(), 100);
39
+ };
40
+
32
41
  this._disposers.push(
33
42
  on(editable, 'click', (e) => this._onEditorClick(e)),
34
43
  on(editable, 'contextmenu', (e) => {
@@ -37,7 +46,7 @@ export class VideoResizer {
37
46
  }),
38
47
  on(document, 'click', (e) => this._onDocClick(e)),
39
48
  on(window, 'scroll', () => this._updateOverlayPosition(), { passive: true }),
40
- on(window, 'resize', () => this._updateOverlayPosition()),
49
+ on(window, 'resize', onWindowResize),
41
50
  on(editable, 'scroll', () => this._updateOverlayPosition(), { passive: true }),
42
51
  );
43
52
 
@@ -51,6 +60,10 @@ export class VideoResizer {
51
60
  this._dragDisposers.forEach((d) => d());
52
61
  this._dragDisposers = null;
53
62
  }
63
+ if (this._positionRaf) {
64
+ cancelAnimationFrame(this._positionRaf);
65
+ this._positionRaf = null;
66
+ }
54
67
  this._deselect();
55
68
  if (this._overlay && this._overlay.parentNode) {
56
69
  this._overlay.parentNode.removeChild(this._overlay);
@@ -152,10 +165,22 @@ export class VideoResizer {
152
165
  }
153
166
 
154
167
  _updateOverlayPosition() {
168
+ if (this._positionRaf) cancelAnimationFrame(this._positionRaf);
169
+ this._positionRaf = requestAnimationFrame(() => {
170
+ this._positionRaf = null;
171
+ this._updateOverlayPositionNow();
172
+ });
173
+ }
174
+
175
+ _updateOverlayPositionNow() {
155
176
  if (!this._activeWrapper || !this._overlay) return;
177
+ const offsetParent = this._overlay.offsetParent || this._container;
178
+ const containerRect = offsetParent.getBoundingClientRect();
156
179
  const rect = this._activeWrapper.getBoundingClientRect();
157
- this._overlay.style.left = `${rect.left}px`;
158
- this._overlay.style.top = `${rect.top}px`;
180
+ const left = rect.left - containerRect.left + offsetParent.scrollLeft;
181
+ const top = rect.top - containerRect.top + offsetParent.scrollTop;
182
+ this._overlay.style.left = `${left}px`;
183
+ this._overlay.style.top = `${top}px`;
159
184
  this._overlay.style.width = `${rect.width}px`;
160
185
  this._overlay.style.height = `${rect.height}px`;
161
186
  }
@@ -173,43 +198,49 @@ export class VideoResizer {
173
198
  const isCorner = pos.length === 2;
174
199
 
175
200
  const editable = this.context.layoutInfo.editable;
201
+ let _raf = null; // rAF handle — at most one write per paint frame
202
+
176
203
  const onMove = (me) => {
177
- const dx = me.clientX - startX;
178
- const dy = me.clientY - startY;
179
- const maxW = editable.clientWidth || Infinity;
180
- let newW = startW;
181
- let newH = startH;
182
-
183
- if (pos.includes('e')) newW = Math.max(80, startW + dx);
184
- if (pos.includes('w')) newW = Math.max(80, startW - dx);
185
- if (pos.includes('s')) newH = Math.max(45, startH + dy);
186
- if (pos.includes('n')) newH = Math.max(45, startH - dy);
187
-
188
- // Clamp to container width
189
- newW = Math.min(newW, maxW);
190
-
191
- if (isCorner) {
192
- if (Math.abs(dx) >= Math.abs(dy)) {
193
- newH = Math.max(45, Math.round(newW / aspectRatio));
194
- } else {
195
- newW = Math.min(Math.max(80, Math.round(newH * aspectRatio)), maxW);
196
- newH = Math.max(45, Math.round(newW / aspectRatio));
204
+ if (_raf !== null) return;
205
+ const clientX = me.clientX;
206
+ const clientY = me.clientY;
207
+ _raf = requestAnimationFrame(() => {
208
+ _raf = null;
209
+ const dx = clientX - startX;
210
+ const dy = clientY - startY;
211
+ const maxW = editable.clientWidth || Infinity;
212
+ let newW = startW;
213
+ let newH = startH;
214
+
215
+ if (pos.includes('e')) newW = Math.max(80, startW + dx);
216
+ if (pos.includes('w')) newW = Math.max(80, startW - dx);
217
+ if (pos.includes('s')) newH = Math.max(45, startH + dy);
218
+ if (pos.includes('n')) newH = Math.max(45, startH - dy);
219
+
220
+ newW = Math.min(newW, maxW);
221
+
222
+ if (isCorner) {
223
+ if (Math.abs(dx) >= Math.abs(dy)) {
224
+ newH = Math.max(45, Math.round(newW / aspectRatio));
225
+ } else {
226
+ newW = Math.min(Math.max(80, Math.round(newH * aspectRatio)), maxW);
227
+ newH = Math.max(45, Math.round(newW / aspectRatio));
228
+ }
229
+ }
230
+
231
+ // Resize wrapper and inner embed via CSS only — attribute writes are redundant
232
+ wrapper.style.width = `${newW}px`;
233
+ wrapper.style.height = `${newH}px`;
234
+ if (embed) {
235
+ embed.style.width = `${newW}px`;
236
+ embed.style.height = `${newH}px`;
197
237
  }
198
- }
199
-
200
- // Resize both the wrapper and the inner embed element
201
- wrapper.style.width = `${newW}px`;
202
- wrapper.style.height = `${newH}px`;
203
- if (embed) {
204
- embed.width = newW;
205
- embed.height = newH;
206
- embed.style.width = `${newW}px`;
207
- embed.style.height = `${newH}px`;
208
- }
209
- this._updateOverlayPosition();
238
+ this._updateOverlayPosition();
239
+ });
210
240
  };
211
241
 
212
242
  const onUp = () => {
243
+ if (_raf !== null) { cancelAnimationFrame(_raf); _raf = null; }
213
244
  document.removeEventListener('mousemove', onMove);
214
245
  document.removeEventListener('mouseup', onUp);
215
246
  this._dragDisposers = null;
@@ -10,6 +10,7 @@ const ICONS = {
10
10
  alignCenter: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="7" y="4" width="10" height="8" rx="1"/><line x1="3" y1="16" x2="21" y2="16"/><line x1="6" y1="20" x2="18" y2="20"/></svg>`,
11
11
  originalSize:`<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 3 21 3 21 9"/><polyline points="9 21 3 21 3 15"/><line x1="21" y1="3" x2="14" y2="10"/><line x1="3" y1="21" x2="10" y2="14"/></svg>`,
12
12
  deleteVideo: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/></svg>`,
13
+ preview: `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"/></svg>`,
13
14
  };
14
15
 
15
16
  const SHOW_DELAY = 100;
@@ -24,6 +25,8 @@ export class VideoTooltip {
24
25
  this._showTimer = null;
25
26
  this._hideTimer = null;
26
27
  this._disposers = [];
28
+ this._previewMode = false;
29
+ this._previewClickOff = null;
27
30
  }
28
31
 
29
32
  initialize() {
@@ -39,13 +42,13 @@ export class VideoTooltip {
39
42
  if (wrapper && editable.contains(wrapper)) {
40
43
  this._scheduleShow(wrapper);
41
44
  }
42
- }),
45
+ }, { passive: true }),
43
46
  on(editable, 'mouseout', (e) => {
44
47
  const to = e.relatedTarget;
45
48
  if (!to || (!editable.contains(to) && !this._el.contains(to))) {
46
49
  this._scheduleHide();
47
50
  }
48
- }),
51
+ }, { passive: true }),
49
52
  on(document, 'click', (e) => {
50
53
  if (
51
54
  this._activeWrapper &&
@@ -61,6 +64,7 @@ export class VideoTooltip {
61
64
  }
62
65
 
63
66
  destroy() {
67
+ if (this._previewMode) this._exitPreview();
64
68
  this._clearTimers();
65
69
  this._disposers.forEach((d) => d());
66
70
  this._disposers = [];
@@ -105,6 +109,11 @@ export class VideoTooltip {
105
109
 
106
110
  el.appendChild(createElement('div', { class: 'an-link-tooltip-sep' }));
107
111
 
112
+ this._previewBtn = this._makeBtn(ICONS.preview, 'Preview Video', () => this._togglePreview());
113
+ el.appendChild(this._previewBtn);
114
+
115
+ el.appendChild(createElement('div', { class: 'an-link-tooltip-sep' }));
116
+
108
117
  this._deleteBtn = this._makeBtn(ICONS.deleteVideo, 'Delete Video', () => this._delete(), true);
109
118
 
110
119
  el.appendChild(this._deleteBtn);
@@ -155,6 +164,8 @@ export class VideoTooltip {
155
164
  }
156
165
 
157
166
  _scheduleHide() {
167
+ // Keep tooltip alive while preview mode is active
168
+ if (this._previewMode) return;
158
169
  clearTimeout(this._showTimer);
159
170
  this._showTimer = null;
160
171
  if (this._hideTimer) return;
@@ -163,10 +174,14 @@ export class VideoTooltip {
163
174
 
164
175
  _show(wrapper) {
165
176
  this._el.style.display = 'flex';
166
- this._positionNear(wrapper);
177
+ // Defer: offsetWidth on a newly-visible element forces synchronous layout
178
+ requestAnimationFrame(() => {
179
+ if (this._activeWrapper) this._positionNear(this._activeWrapper);
180
+ });
167
181
  }
168
182
 
169
183
  _hide() {
184
+ if (this._previewMode) this._exitPreview();
170
185
  this._el.style.display = 'none';
171
186
  this._activeWrapper = null;
172
187
  this._clearTimers();
@@ -209,7 +224,7 @@ export class VideoTooltip {
209
224
  wrapper.style.marginRight = value === 'left' ? '12px' : '';
210
225
  this.context.invoke('editor.afterCommand');
211
226
  this.context.invoke('videoResizer.updateOverlay');
212
- this._positionNear(wrapper);
227
+ requestAnimationFrame(() => { if (this._activeWrapper) this._positionNear(this._activeWrapper); });
213
228
  }
214
229
 
215
230
  _setCenter() {
@@ -221,7 +236,7 @@ export class VideoTooltip {
221
236
  wrapper.style.marginRight = 'auto';
222
237
  this.context.invoke('editor.afterCommand');
223
238
  this.context.invoke('videoResizer.updateOverlay');
224
- this._positionNear(wrapper);
239
+ requestAnimationFrame(() => { if (this._activeWrapper) this._positionNear(this._activeWrapper); });
225
240
  }
226
241
 
227
242
  _resetSize() {
@@ -238,7 +253,7 @@ export class VideoTooltip {
238
253
  }
239
254
  this.context.invoke('editor.afterCommand');
240
255
  this.context.invoke('videoResizer.updateOverlay');
241
- this._positionNear(wrapper);
256
+ requestAnimationFrame(() => { if (this._activeWrapper) this._positionNear(this._activeWrapper); });
242
257
  }
243
258
 
244
259
  _delete() {
@@ -249,4 +264,65 @@ export class VideoTooltip {
249
264
  if (wrapper.parentNode) wrapper.parentNode.removeChild(wrapper);
250
265
  this.context.invoke('editor.afterCommand');
251
266
  }
267
+
268
+ // ---------------------------------------------------------------------------
269
+ // Preview mode — temporarily disables the shield so the video is interactive
270
+ // ---------------------------------------------------------------------------
271
+
272
+ _togglePreview() {
273
+ if (this._previewMode) {
274
+ this._exitPreview();
275
+ } else {
276
+ this._enterPreview();
277
+ }
278
+ }
279
+
280
+ _enterPreview() {
281
+ const wrapper = this._activeWrapper;
282
+ if (!wrapper) return;
283
+ this._previewMode = true;
284
+
285
+ // Hide the shield so the iframe / video receives pointer events directly
286
+ const shield = wrapper.querySelector('.an-video-shield');
287
+ if (shield) shield.style.display = 'none';
288
+
289
+ // Hide the resize overlay — it would block interaction with the embed
290
+ this.context.invoke('videoResizer.deselect');
291
+
292
+ // Visual feedback: button turns primary-coloured
293
+ this._previewBtn.classList.add('an-link-tooltip-btn--copied');
294
+ this._previewBtn.title = 'Exit Preview';
295
+
296
+ // Exit preview on mousedown outside the wrapper.
297
+ // Using 'mousedown' (not 'click') so clicks inside an iframe
298
+ // — which never bubble to the outer document — don't accidentally
299
+ // leave preview mode stuck. For <video> elements, mousedown on the
300
+ // video is inside the wrapper so contains() returns true → no exit.
301
+ this._previewClickOff = (e) => {
302
+ if (!wrapper.contains(e.target) && !this._el.contains(e.target)) {
303
+ this._exitPreview();
304
+ }
305
+ };
306
+ document.addEventListener('mousedown', this._previewClickOff, true);
307
+ }
308
+
309
+ _exitPreview() {
310
+ this._previewMode = false;
311
+
312
+ const wrapper = this._activeWrapper;
313
+ if (wrapper) {
314
+ // Restore the shield
315
+ const shield = wrapper.querySelector('.an-video-shield');
316
+ if (shield) shield.style.display = '';
317
+ }
318
+
319
+ // Reset button appearance
320
+ this._previewBtn.classList.remove('an-link-tooltip-btn--copied');
321
+ this._previewBtn.title = 'Preview Video';
322
+
323
+ if (this._previewClickOff) {
324
+ document.removeEventListener('mousedown', this._previewClickOff, true);
325
+ this._previewClickOff = null;
326
+ }
327
+ }
252
328
  }
@@ -99,6 +99,11 @@ export function renderLayout(targetEl, options) {
99
99
  }
100
100
  }
101
101
 
102
+ // Custom focus ring colour
103
+ if (options.focusColor) {
104
+ container.style.setProperty('--an-focus-color', options.focusColor);
105
+ }
106
+
102
107
  // Hide the original element; keep it in DOM for form submission
103
108
  targetEl.style.display = 'none';
104
109
  targetEl.insertAdjacentElement('afterend', container);
@@ -45,6 +45,7 @@ import { defaultToolbar } from './module/Buttons.js';
45
45
  * @property {Function} [onDestroy] - Callback fired when the editor is destroyed: (context) => void
46
46
  * @property {Function} [onCharLimitReached] - Callback fired when the character limit is hit: (context) => void
47
47
  * @property {Function} [onWordLimitReached] - Callback fired when the word limit is hit: (context) => void
48
+ * @property {string} [focusColor] - Custom focus ring colour, e.g. '#f97316'. Overrides the default blue.
48
49
  */
49
50
 
50
51
  /** @type {AsnOptions} */
@@ -131,4 +132,7 @@ export const defaultOptions = {
131
132
  onCharLimitReached: null,
132
133
  // Callback fired when the word limit is reached: function(context)
133
134
  onWordLimitReached: null,
135
+ // Custom focus ring colour — overrides the default blue when set.
136
+ // Accepts any valid CSS colour string, e.g. '#f97316', 'hsl(25,90%,55%)'.
137
+ focusColor: null,
134
138
  };
@@ -12,13 +12,13 @@
12
12
  .an-container {
13
13
  display: flex;
14
14
  flex-direction: column;
15
+ position: relative;
15
16
  border: 1px solid $an-border;
16
17
  border-radius: $an-radius;
17
18
  font-family: $an-font-family;
18
19
  font-size: $an-font-size;
19
20
  box-sizing: border-box;
20
21
  background: $an-bg;
21
- overflow: hidden;
22
22
  // Prevent the container from participating in ancestor layout recalculations.
23
23
  // This limits reflow thrashing when editing large documents.
24
24
  contain: layout;
@@ -30,14 +30,27 @@
30
30
  }
31
31
 
32
32
  &.an-focused {
33
- border-color: $an-primary;
34
- box-shadow: 0 0 0 3px rgba($an-primary, 0.15);
33
+ border-color: var(--an-focus-color, #{$an-primary});
34
+ // color-mix() blends the focus colour with transparent for the glow ring.
35
+ // Falls back gracefully to the default blue glow in older browsers.
36
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--an-focus-color, #{$an-primary}) 18%, transparent);
35
37
  }
36
38
 
37
39
  &.an-disabled {
38
- opacity: 0.6;
39
- pointer-events: none;
40
- user-select: none;
40
+ // Hide toolbar only — read-only view has no editing controls.
41
+ .an-toolbar {
42
+ display: none;
43
+ }
44
+
45
+ // Allow the user to select and copy text but block actual editing.
46
+ // Do NOT set pointer-events:none or user-select:none on the container —
47
+ // that would prevent text selection inside the editable area.
48
+ .an-editable {
49
+ cursor: text;
50
+ user-select: text;
51
+ // Subtle visual cue that the area is read-only.
52
+ background: #fafafa;
53
+ }
41
54
  }
42
55
 
43
56
  &.an-fullscreen {
@@ -234,9 +247,9 @@
234
247
 
235
248
  // Swatch popup panel
236
249
  .an-color-popup {
237
- position: absolute;
238
- top: calc(100% + 4px);
239
- left: 0;
250
+ position: fixed;
251
+ // top / left are set dynamically by JS (getBoundingClientRect) so the popup
252
+ // escapes overflow:hidden / overflow:auto ancestors (toolbar scroll mode, etc.)
240
253
  z-index: 1100;
241
254
  background: $an-bg;
242
255
  border: 1px solid $an-border;
@@ -377,19 +390,25 @@
377
390
  .an-editable {
378
391
  flex: 1;
379
392
  padding: $an-editable-pad;
380
- min-height: 200px;
393
+ min-height: 0;
381
394
  outline: none;
382
395
  line-height: $an-line-height;
383
396
  color: $an-text;
384
397
  overflow-y: auto;
385
398
  word-break: break-word;
386
399
 
387
- // Placeholder
388
- &.an-placeholder::before {
400
+ // Placeholder — only shown when not focused.
401
+ // float:left is the standard contenteditable placeholder technique (used by
402
+ // Quill.js): the pseudo-element is out of normal flow so it never shifts the
403
+ // cursor, yet it renders visually at the start of the empty editing area.
404
+ // Avoid position:absolute here — without position:relative on the parent it
405
+ // would be positioned relative to .an-container (contain:layout) and overlap
406
+ // the toolbar.
407
+ &.an-placeholder:not(:focus)::before {
389
408
  content: attr(data-placeholder);
390
409
  color: $an-muted;
391
410
  pointer-events: none;
392
- position: absolute;
411
+ float: left;
393
412
  }
394
413
 
395
414
  // Content styles
@@ -435,7 +454,7 @@
435
454
 
436
455
  .an-video-wrapper {
437
456
  position: relative;
438
- display: inline-block;
457
+ display: block;
439
458
  max-width: 100%;
440
459
  margin: 4px 0;
441
460
 
@@ -667,6 +686,7 @@
667
686
  display: flex;
668
687
  align-items: center;
669
688
  justify-content: space-between;
689
+ flex-shrink: 0;
670
690
  height: $an-statusbar-h;
671
691
  padding: 0 8px;
672
692
  background: $an-statusbar-bg;
@@ -919,7 +939,7 @@
919
939
  // ---------------------------------------------------------------------------
920
940
 
921
941
  .an-image-resizer {
922
- position: fixed;
942
+ position: absolute;
923
943
  z-index: 10000;
924
944
  box-sizing: border-box;
925
945
  border: 2px solid $an-primary;
@@ -956,7 +976,7 @@
956
976
  $an-video-accent: #8b5cf6; // violet-500
957
977
 
958
978
  .an-video-resizer {
959
- position: fixed;
979
+ position: absolute;
960
980
  z-index: 10000;
961
981
  box-sizing: border-box;
962
982
  border: 2px solid $an-video-accent;
@@ -986,11 +1006,10 @@ $an-video-accent: #8b5cf6; // violet-500
986
1006
  .an-resize-w { top: calc(50% - 4px); left: -4px; cursor: w-resize; }
987
1007
  }
988
1008
 
989
- // Selected video wrapper highlight (applied to .an-video-wrapper)
990
- .an-video-selected {
991
- outline: 2px solid $an-video-accent;
992
- outline-offset: 1px;
993
- }
1009
+ // .an-video-selected is applied by VideoResizer for JS state tracking.
1010
+ // Visual selection is handled entirely by the .an-video-resizer overlay,
1011
+ // so no CSS outline is needed here (two visible frames would appear otherwise).
1012
+ .an-video-selected {}
994
1013
 
995
1014
  // ---------------------------------------------------------------------------
996
1015
  // Icon dialog
@@ -1082,7 +1101,7 @@ $an-video-accent: #8b5cf6; // violet-500
1082
1101
  display: grid;
1083
1102
  grid-template-columns: repeat(auto-fill, 56px);
1084
1103
  gap: 3px;
1085
- max-height: 256px;
1104
+ height: 256px;
1086
1105
  overflow-y: auto;
1087
1106
  margin-bottom: 10px;
1088
1107
  padding: 4px;
@@ -1244,7 +1263,7 @@ $an-video-accent: #8b5cf6; // violet-500
1244
1263
  display: grid;
1245
1264
  grid-template-columns: repeat(auto-fill, 42px);
1246
1265
  gap: 2px;
1247
- max-height: 288px;
1266
+ height: 288px;
1248
1267
  overflow-y: auto;
1249
1268
  margin-bottom: 10px;
1250
1269
  padding: 4px;
@@ -1572,6 +1591,11 @@ $an-video-accent: #8b5cf6; // violet-500
1572
1591
  .an-table-cell {
1573
1592
  background: #24273a;
1574
1593
  border-color: #3f3f5f;
1594
+
1595
+ &.active {
1596
+ background: rgba(99, 102, 241, 0.3);
1597
+ border-color: #6366f1;
1598
+ }
1575
1599
  }
1576
1600
 
1577
1601
  .an-context-item { color: #cdd6f4; }
@@ -1670,25 +1694,31 @@ $an-video-accent: #8b5cf6; // violet-500
1670
1694
  .an-editable {
1671
1695
  ul.an-checklist {
1672
1696
  list-style: none;
1673
- padding-left: 0.5em;
1697
+ padding-left: 0;
1674
1698
  margin: 0.5em 0;
1675
1699
 
1676
1700
  li {
1677
- display: flex;
1678
- align-items: baseline;
1679
- gap: 6px;
1680
- padding: 2px 0;
1701
+ // Use padding-left + absolute checkbox so text is a normal inline flow
1702
+ // (not an anonymous flex item). This ensures font-size, line-height and
1703
+ // all inline formats are identical to regular paragraph text.
1704
+ position: relative;
1705
+ padding-left: 22px;
1706
+ padding-top: 1px;
1707
+ padding-bottom: 1px;
1708
+ min-height: 1.4em;
1681
1709
  line-height: inherit;
1710
+ font-size: inherit;
1711
+ font-family: inherit;
1682
1712
 
1683
1713
  input[type='checkbox'] {
1684
- flex-shrink: 0;
1714
+ position: absolute;
1715
+ left: 0;
1716
+ top: 0.25em;
1685
1717
  width: 14px;
1686
1718
  height: 14px;
1687
1719
  margin: 0;
1688
1720
  cursor: pointer;
1689
1721
  accent-color: $an-primary;
1690
- position: relative;
1691
- top: 2px;
1692
1722
  }
1693
1723
 
1694
1724
  // Checked item: dim + strike-through the text node
package/types/index.d.ts CHANGED
@@ -43,7 +43,7 @@ export interface AsnOptions {
43
43
  /** Number of spaces inserted when Tab is pressed (outside a list). */
44
44
  tabSize?: number;
45
45
  /** Callback on content change. */
46
- onChange?: (context: Context) => void;
46
+ onChange?: (html: string) => void;
47
47
  /** Callback on editor focus. */
48
48
  onFocus?: (context: Context) => void;
49
49
  /** Callback fired on editor blur. */
@@ -59,9 +59,9 @@ export interface AsnOptions {
59
59
  /** Callback fired when the word limit is reached. */
60
60
  onWordLimitReached?: (context: Context) => void;
61
61
  /** Custom image upload handler. */
62
- onImageUpload?: (files: FileList) => void;
62
+ onImageUpload?: (files: File[]) => void;
63
63
  /** Callback when an image upload error occurs. */
64
- onImageError?: (error: Error) => void;
64
+ onImageError?: (error: { file?: File; message: string; error?: unknown }) => void;
65
65
  /** Stick the toolbar to the viewport top when scrolling. */
66
66
  stickyToolbar?: boolean;
67
67
  /** Top offset in px for sticky toolbar (e.g. height of a fixed nav bar). */
@@ -101,7 +101,7 @@ export interface AsnOptions {
101
101
  /** Insert a header row (<thead>) when creating new tables. */
102
102
  tableHeaderRow?: boolean;
103
103
  /** Callback fired after every paste event. */
104
- onPaste?: (data: { text: string; html: string }) => void;
104
+ onPaste?: (data: { text: string; html: string | null }) => void;
105
105
  /** Additional color swatches shown at the top of the color picker. */
106
106
  colorSwatches?: string[];
107
107
  }