js-draw 1.19.1 → 1.20.1

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 (43) hide show
  1. package/README.md +52 -1
  2. package/dist/Editor.css +49 -7
  3. package/dist/bundle.js +2 -2
  4. package/dist/bundledStyles.js +1 -1
  5. package/dist/cjs/SVGLoader/index.js +3 -1
  6. package/dist/cjs/image/EditorImage.d.ts +2 -1
  7. package/dist/cjs/image/EditorImage.js +101 -5
  8. package/dist/cjs/rendering/renderers/CanvasRenderer.js +4 -4
  9. package/dist/cjs/toolbar/localization.d.ts +1 -0
  10. package/dist/cjs/toolbar/localization.js +1 -0
  11. package/dist/cjs/toolbar/widgets/InsertImageWidget/ImageWrapper.d.ts +1 -0
  12. package/dist/cjs/toolbar/widgets/InsertImageWidget/ImageWrapper.js +7 -0
  13. package/dist/cjs/toolbar/widgets/InsertImageWidget/index.js +11 -3
  14. package/dist/cjs/toolbar/widgets/components/makeFileInput.js +12 -1
  15. package/dist/cjs/toolbar/widgets/components/makeSnappedList.js +69 -4
  16. package/dist/cjs/tools/Eraser.js +22 -5
  17. package/dist/cjs/tools/PanZoom.d.ts +54 -0
  18. package/dist/cjs/tools/PanZoom.js +54 -2
  19. package/dist/cjs/util/ReactiveValue.d.ts +4 -0
  20. package/dist/cjs/util/ReactiveValue.js +5 -0
  21. package/dist/cjs/version.js +1 -1
  22. package/dist/mjs/SVGLoader/index.mjs +3 -1
  23. package/dist/mjs/image/EditorImage.d.ts +2 -1
  24. package/dist/mjs/image/EditorImage.mjs +101 -5
  25. package/dist/mjs/rendering/renderers/CanvasRenderer.mjs +4 -4
  26. package/dist/mjs/toolbar/localization.d.ts +1 -0
  27. package/dist/mjs/toolbar/localization.mjs +1 -0
  28. package/dist/mjs/toolbar/widgets/InsertImageWidget/ImageWrapper.d.ts +1 -0
  29. package/dist/mjs/toolbar/widgets/InsertImageWidget/ImageWrapper.mjs +7 -0
  30. package/dist/mjs/toolbar/widgets/InsertImageWidget/index.mjs +11 -3
  31. package/dist/mjs/toolbar/widgets/components/makeFileInput.mjs +12 -1
  32. package/dist/mjs/toolbar/widgets/components/makeSnappedList.mjs +69 -4
  33. package/dist/mjs/tools/Eraser.mjs +22 -5
  34. package/dist/mjs/tools/PanZoom.d.ts +54 -0
  35. package/dist/mjs/tools/PanZoom.mjs +54 -2
  36. package/dist/mjs/util/ReactiveValue.d.ts +4 -0
  37. package/dist/mjs/util/ReactiveValue.mjs +5 -0
  38. package/dist/mjs/version.mjs +1 -1
  39. package/docs/img/readme-images/unsupported-elements--in-editor.png +0 -0
  40. package/package.json +2 -2
  41. package/src/toolbar/EdgeToolbar.scss +8 -3
  42. package/src/toolbar/widgets/components/makeFileInput.scss +3 -2
  43. package/src/toolbar/widgets/components/makeSnappedList.scss +58 -12
@@ -176,13 +176,21 @@ class InsertImageWidget extends BaseWidget {
176
176
  currentImage?.reset();
177
177
  };
178
178
  this.statusView.replaceChildren(sizeText);
179
- const largeImageThreshold = 0.12 * 1024 * 1024; // 0.12 MiB
180
- if (imageData.length > largeImageThreshold) {
179
+ if (currentImage?.isLarge()) {
181
180
  this.statusView.appendChild(decreaseSizeButton);
182
181
  }
183
182
  else if (currentImage?.isChanged()) {
184
183
  this.statusView.appendChild(resetSizeButton);
185
184
  }
185
+ else {
186
+ const hasLargeOrChangedImages = this.images.get().some(image => image.data?.isChanged() || image.data?.isLarge());
187
+ if (hasLargeOrChangedImages) {
188
+ // Still show the button -- prevents the layout from readjusting while
189
+ // scrolling through the image list
190
+ decreaseSizeButton.disabled = true;
191
+ this.statusView.appendChild(decreaseSizeButton);
192
+ }
193
+ }
186
194
  }
187
195
  updateInputs() {
188
196
  const resetInputs = () => {
@@ -220,7 +228,7 @@ class InsertImageWidget extends BaseWidget {
220
228
  }
221
229
  const image = new Image();
222
230
  image.src = imageWrapper.getBase64Url();
223
- image.setAttribute('alt', this.imageAltTextInput.value);
231
+ image.setAttribute('alt', imageWrapper.getAltText());
224
232
  let component;
225
233
  try {
226
234
  component = await ImageComponent.fromImage(image, transform);
@@ -42,7 +42,18 @@ const makeFileInput = (labelText, context, { accepts = '*', allowMultiSelect = f
42
42
  icon.style.display = 'none';
43
43
  }
44
44
  else if (files.length > 0) {
45
- descriptionText.textContent = files.map(file => file.name).join('\n');
45
+ const fileNames = files.map(file => file.name);
46
+ const maxNames = 5;
47
+ if (fileNames.length <= maxNames) {
48
+ descriptionText.textContent = fileNames.join('\n');
49
+ }
50
+ else {
51
+ const fileNamesToShow = fileNames.slice(0, maxNames - 1);
52
+ descriptionText.textContent = [
53
+ ...fileNamesToShow,
54
+ context.localization.fileInput__andNMoreFiles(fileNames.length - fileNamesToShow.length),
55
+ ].join('\n');
56
+ }
46
57
  // Only show the icon when there are files
47
58
  icon.style.display = 'none';
48
59
  }
@@ -6,8 +6,72 @@ import { MutableReactiveValue, ReactiveValue } from '../../../util/ReactiveVal
6
6
  const makeSnappedList = (itemsValue) => {
7
7
  const container = document.createElement('div');
8
8
  container.classList.add('toolbar-snapped-scroll-list');
9
+ const scroller = document.createElement('div');
10
+ scroller.classList.add('scroller');
9
11
  const visibleIndex = MutableReactiveValue.fromInitialValue(0);
10
12
  let observer = null;
13
+ const makePageMarkers = () => {
14
+ const markerContainer = document.createElement('div');
15
+ markerContainer.classList.add('page-markers');
16
+ // Keyboard focus should go to the main scrolling list.
17
+ // TODO: Does it make sense for the page marker list to be focusable?
18
+ markerContainer.setAttribute('tabindex', '-1');
19
+ const markers = [];
20
+ const pairedItems = ReactiveValue.union([visibleIndex, itemsValue]);
21
+ pairedItems.onUpdateAndNow(([currentVisibleIndex, items]) => {
22
+ let addedOrRemovedMarkers = false;
23
+ // Items may have been removed from the list of pages. Make the markers reflect that.
24
+ while (items.length < markers.length) {
25
+ markers.pop();
26
+ addedOrRemovedMarkers = true;
27
+ }
28
+ let activeMarker;
29
+ for (let i = 0; i < items.length; i++) {
30
+ let marker;
31
+ if (i >= markers.length) {
32
+ marker = document.createElement('div');
33
+ // Use a separate content element to increase the clickable size of
34
+ // the marker.
35
+ const content = document.createElement('div');
36
+ content.classList.add('content');
37
+ marker.replaceChildren(content);
38
+ markers.push(marker);
39
+ addedOrRemovedMarkers = true;
40
+ }
41
+ else {
42
+ marker = markers[i];
43
+ }
44
+ marker.classList.add('marker');
45
+ if (i === currentVisibleIndex) {
46
+ marker.classList.add('-active');
47
+ activeMarker = marker;
48
+ }
49
+ else {
50
+ marker.classList.remove('-active');
51
+ }
52
+ const markerIndex = i;
53
+ marker.onclick = () => {
54
+ wrappedItems.get()[markerIndex]?.element?.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
55
+ };
56
+ }
57
+ // Only call .replaceChildren when necessary -- doing so on every change would
58
+ // break transitions.
59
+ if (addedOrRemovedMarkers) {
60
+ markerContainer.replaceChildren(...markers);
61
+ }
62
+ // Handles the case where there are many markers and the current is offscreen
63
+ if (activeMarker && markerContainer.scrollHeight > container.clientHeight) {
64
+ activeMarker.scrollIntoView({ block: 'nearest' });
65
+ }
66
+ if (markers.length === 1) {
67
+ markerContainer.classList.add('-one-element');
68
+ }
69
+ else {
70
+ markerContainer.classList.remove('-one-element');
71
+ }
72
+ });
73
+ return markerContainer;
74
+ };
11
75
  const createObserver = () => {
12
76
  observer = new IntersectionObserver((entries) => {
13
77
  for (const entry of entries) {
@@ -23,7 +87,7 @@ const makeSnappedList = (itemsValue) => {
23
87
  }, {
24
88
  // Element to use as the boudning box with which to intersect.
25
89
  // See https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
26
- root: container,
90
+ root: scroller,
27
91
  // Fraction of an element that must be visible to trigger the callback:
28
92
  threshold: 0.9,
29
93
  });
@@ -55,7 +119,7 @@ const makeSnappedList = (itemsValue) => {
55
119
  for (const item of lastItems) {
56
120
  observer?.unobserve(item.element);
57
121
  }
58
- container.replaceChildren();
122
+ scroller.replaceChildren();
59
123
  // An observer is only necessary if there are multiple items to scroll through.
60
124
  if (items.length > 1) {
61
125
  createObserver();
@@ -71,7 +135,7 @@ const makeSnappedList = (itemsValue) => {
71
135
  container.classList.remove('-empty');
72
136
  }
73
137
  for (const item of items) {
74
- container.appendChild(item.element);
138
+ scroller.appendChild(item.element);
75
139
  }
76
140
  visibleIndex.set(0);
77
141
  if (observer) {
@@ -89,7 +153,8 @@ const makeSnappedList = (itemsValue) => {
89
153
  });
90
154
  // makeSnappedList is generally shown within the toolbar. This allows users to
91
155
  // scroll it with a touchpad.
92
- stopPropagationOfScrollingWheelEvents(container);
156
+ stopPropagationOfScrollingWheelEvents(scroller);
157
+ container.replaceChildren(makePageMarkers(), scroller);
93
158
  return {
94
159
  container,
95
160
  visibleItem,
@@ -65,6 +65,7 @@ export default class Eraser extends BaseTool {
65
65
  this.editor = editor;
66
66
  this.lastPoint = null;
67
67
  this.isFirstEraseEvt = true;
68
+ this.toAdd = new Set();
68
69
  // Commands that each remove one element
69
70
  this.eraseCommands = [];
70
71
  this.addCommands = [];
@@ -174,15 +175,17 @@ export default class Eraser extends BaseTool {
174
175
  newAddCommands.forEach(command => command.apply(this.editor));
175
176
  const finalToErase = [];
176
177
  for (const item of toErase) {
177
- if (this.toAdd.includes(item)) {
178
- this.toAdd = this.toAdd.filter(i => i !== item);
178
+ if (this.toAdd.has(item)) {
179
+ this.toAdd.delete(item);
179
180
  }
180
181
  else {
181
182
  finalToErase.push(item);
182
183
  }
183
184
  }
184
185
  this.toRemove.push(...finalToErase);
185
- this.toAdd.push(...toAdd);
186
+ for (const item of toAdd) {
187
+ this.toAdd.add(item);
188
+ }
186
189
  this.eraseCommands.push(new Erase(finalToErase));
187
190
  this.addCommands.push(...newAddCommands);
188
191
  }
@@ -193,7 +196,7 @@ export default class Eraser extends BaseTool {
193
196
  if (event.allPointers.length === 1 || event.current.device === PointerDevice.Eraser) {
194
197
  this.lastPoint = event.current.canvasPos;
195
198
  this.toRemove = [];
196
- this.toAdd = [];
199
+ this.toAdd.clear();
197
200
  this.isFirstEraseEvt = true;
198
201
  this.drawPreviewAt(event.current.canvasPos);
199
202
  return true;
@@ -209,7 +212,21 @@ export default class Eraser extends BaseTool {
209
212
  const commands = [];
210
213
  if (this.addCommands.length > 0) {
211
214
  this.addCommands.forEach(cmd => cmd.unapply(this.editor));
212
- commands.push(...this.toAdd.map(a => EditorImage.addElement(a)));
215
+ // Remove items from toAdd that are also present in toRemove -- adding, then
216
+ // removing these does nothing, and can break undo/redo.
217
+ for (const item of this.toAdd) {
218
+ if (this.toRemove.includes(item)) {
219
+ this.toAdd.delete(item);
220
+ this.toRemove = this.toRemove.filter(other => other !== item);
221
+ }
222
+ }
223
+ for (const item of this.toRemove) {
224
+ if (this.toAdd.has(item)) {
225
+ this.toAdd.delete(item);
226
+ this.toRemove = this.toRemove.filter(other => other !== item);
227
+ }
228
+ }
229
+ commands.push(...[...this.toAdd].map(a => EditorImage.addElement(a)));
213
230
  this.addCommands = [];
214
231
  }
215
232
  if (this.eraseCommands.length > 0) {
@@ -10,13 +10,27 @@ interface PinchData {
10
10
  dist: number;
11
11
  }
12
12
  export declare enum PanZoomMode {
13
+ /** Touch gestures with a single pointer. Ignores non-touch gestures. */
13
14
  OneFingerTouchGestures = 1,
15
+ /** Touch gestures with exactly two pointers. Ignores non-touch gestures. */
14
16
  TwoFingerTouchGestures = 2,
15
17
  RightClickDrags = 4,
18
+ /** Single-pointer gestures of *any* type (including touch). */
16
19
  SinglePointerGestures = 8,
20
+ /** Keyboard navigation (e.g. LeftArrow to move left). */
17
21
  Keyboard = 16,
22
+ /** If provided, prevents **this** tool from rotating the viewport (other tools may still do so). */
18
23
  RotationLocked = 32
19
24
  }
25
+ /**
26
+ * This tool moves the viewport in response to touchpad, touchscreen, mouse, and keyboard events.
27
+ *
28
+ * Which events are handled, and which are skipped, are determined by the tool's `mode`. For example,
29
+ * a `PanZoom` tool with `mode = PanZoomMode.TwoFingerTouchGestures|PanZoomMode.RightClickDrags` would
30
+ * respond to right-click drag events and two-finger touch gestures.
31
+ *
32
+ * @see {@link setModeEnabled}
33
+ */
20
34
  export default class PanZoom extends BaseTool {
21
35
  private editor;
22
36
  private mode;
@@ -58,8 +72,48 @@ export default class PanZoom extends BaseTool {
58
72
  onWheel({ delta, screenPos }: WheelEvt): boolean;
59
73
  onKeyPress(event: KeyPressEvent): boolean;
60
74
  private isRotationLocked;
75
+ /**
76
+ * Changes the types of gestures used by this pan/zoom tool.
77
+ *
78
+ * @see {@link PanZoomMode} {@link setMode}
79
+ *
80
+ * @example
81
+ * ```ts,runnable
82
+ * import { Editor, PanZoomTool, PanZoomMode } from 'js-draw';
83
+ *
84
+ * const editor = new Editor(document.body);
85
+ *
86
+ * // By default, there are multiple PanZoom tools that handle different events.
87
+ * // This gets all PanZoomTools.
88
+ * const panZoomToolList = editor.toolController.getMatchingTools(PanZoomTool);
89
+ *
90
+ * // The first PanZoomTool is the highest priority -- by default,
91
+ * // this tool is responsible for handling multi-finger touch gestures.
92
+ * //
93
+ * // Lower-priority PanZoomTools handle one-finger touch gestures and
94
+ * // key-presses.
95
+ * const panZoomTool = panZoomToolList[0];
96
+ *
97
+ * // Lock rotation for multi-finger touch gestures.
98
+ * panZoomTool.setModeEnabled(PanZoomMode.RotationLocked, true);
99
+ * ```
100
+ */
61
101
  setModeEnabled(mode: PanZoomMode, enabled: boolean): void;
102
+ /**
103
+ * Sets all modes for this tool using a bitmask.
104
+ *
105
+ * @see {@link setModeEnabled}
106
+ *
107
+ * @example
108
+ * ```ts
109
+ * tool.setMode(PanZoomMode.RotationLocked|PanZoomMode.TwoFingerTouchGestures);
110
+ * ```
111
+ */
62
112
  setMode(mode: PanZoomMode): void;
113
+ /**
114
+ * Returns a bitmask indicating the currently-enabled modes.
115
+ * @see {@link setModeEnabled}
116
+ */
63
117
  getMode(): PanZoomMode;
64
118
  }
65
119
  export {};
@@ -7,11 +7,16 @@ import BaseTool from './BaseTool.mjs';
7
7
  import { moveDownKeyboardShortcutId, moveLeftKeyboardShortcutId, moveRightKeyboardShortcutId, moveUpKeyboardShortcutId, rotateClockwiseKeyboardShortcutId, rotateCounterClockwiseKeyboardShortcutId, zoomInKeyboardShortcutId, zoomOutKeyboardShortcutId } from './keybindings.mjs';
8
8
  export var PanZoomMode;
9
9
  (function (PanZoomMode) {
10
+ /** Touch gestures with a single pointer. Ignores non-touch gestures. */
10
11
  PanZoomMode[PanZoomMode["OneFingerTouchGestures"] = 1] = "OneFingerTouchGestures";
12
+ /** Touch gestures with exactly two pointers. Ignores non-touch gestures. */
11
13
  PanZoomMode[PanZoomMode["TwoFingerTouchGestures"] = 2] = "TwoFingerTouchGestures";
12
14
  PanZoomMode[PanZoomMode["RightClickDrags"] = 4] = "RightClickDrags";
15
+ /** Single-pointer gestures of *any* type (including touch). */
13
16
  PanZoomMode[PanZoomMode["SinglePointerGestures"] = 8] = "SinglePointerGestures";
17
+ /** Keyboard navigation (e.g. LeftArrow to move left). */
14
18
  PanZoomMode[PanZoomMode["Keyboard"] = 16] = "Keyboard";
19
+ /** If provided, prevents **this** tool from rotating the viewport (other tools may still do so). */
15
20
  PanZoomMode[PanZoomMode["RotationLocked"] = 32] = "RotationLocked";
16
21
  })(PanZoomMode || (PanZoomMode = {}));
17
22
  class InertialScroller {
@@ -59,6 +64,15 @@ class InertialScroller {
59
64
  }
60
65
  }
61
66
  }
67
+ /**
68
+ * This tool moves the viewport in response to touchpad, touchscreen, mouse, and keyboard events.
69
+ *
70
+ * Which events are handled, and which are skipped, are determined by the tool's `mode`. For example,
71
+ * a `PanZoom` tool with `mode = PanZoomMode.TwoFingerTouchGestures|PanZoomMode.RightClickDrags` would
72
+ * respond to right-click drag events and two-finger touch gestures.
73
+ *
74
+ * @see {@link setModeEnabled}
75
+ */
62
76
  export default class PanZoom extends BaseTool {
63
77
  constructor(editor, mode, description) {
64
78
  super(editor.notifier, description);
@@ -422,8 +436,32 @@ export default class PanZoom extends BaseTool {
422
436
  isRotationLocked() {
423
437
  return !!(this.mode & PanZoomMode.RotationLocked);
424
438
  }
425
- // Sets whether the given `mode` is enabled. `mode` should be a single
426
- // mode from the `PanZoomMode` enum.
439
+ /**
440
+ * Changes the types of gestures used by this pan/zoom tool.
441
+ *
442
+ * @see {@link PanZoomMode} {@link setMode}
443
+ *
444
+ * @example
445
+ * ```ts,runnable
446
+ * import { Editor, PanZoomTool, PanZoomMode } from 'js-draw';
447
+ *
448
+ * const editor = new Editor(document.body);
449
+ *
450
+ * // By default, there are multiple PanZoom tools that handle different events.
451
+ * // This gets all PanZoomTools.
452
+ * const panZoomToolList = editor.toolController.getMatchingTools(PanZoomTool);
453
+ *
454
+ * // The first PanZoomTool is the highest priority -- by default,
455
+ * // this tool is responsible for handling multi-finger touch gestures.
456
+ * //
457
+ * // Lower-priority PanZoomTools handle one-finger touch gestures and
458
+ * // key-presses.
459
+ * const panZoomTool = panZoomToolList[0];
460
+ *
461
+ * // Lock rotation for multi-finger touch gestures.
462
+ * panZoomTool.setModeEnabled(PanZoomMode.RotationLocked, true);
463
+ * ```
464
+ */
427
465
  setModeEnabled(mode, enabled) {
428
466
  let newMode = this.mode;
429
467
  if (enabled) {
@@ -434,6 +472,16 @@ export default class PanZoom extends BaseTool {
434
472
  }
435
473
  this.setMode(newMode);
436
474
  }
475
+ /**
476
+ * Sets all modes for this tool using a bitmask.
477
+ *
478
+ * @see {@link setModeEnabled}
479
+ *
480
+ * @example
481
+ * ```ts
482
+ * tool.setMode(PanZoomMode.RotationLocked|PanZoomMode.TwoFingerTouchGestures);
483
+ * ```
484
+ */
437
485
  setMode(mode) {
438
486
  if (mode !== this.mode) {
439
487
  this.mode = mode;
@@ -443,6 +491,10 @@ export default class PanZoom extends BaseTool {
443
491
  });
444
492
  }
445
493
  }
494
+ /**
495
+ * Returns a bitmask indicating the currently-enabled modes.
496
+ * @see {@link setModeEnabled}
497
+ */
446
498
  getMode() {
447
499
  return this.mode;
448
500
  }
@@ -2,6 +2,9 @@ type ListenerResult = {
2
2
  remove(): void;
3
3
  };
4
4
  type UpdateCallback<T> = (value: T) => void;
5
+ type ReactiveValuesOf<T extends unknown[]> = {
6
+ [key in keyof T]: ReactiveValue<T[key]>;
7
+ };
5
8
  /**
6
9
  * A `ReactiveValue` is a value that
7
10
  * - updates periodically,
@@ -56,6 +59,7 @@ export declare abstract class ReactiveValue<T> {
56
59
  * Returns a reactive value derived from a single `source`.
57
60
  */
58
61
  static map<A, B>(source: ReactiveValue<A>, map: (a: A) => B, inverseMap: (b: B) => A): MutableReactiveValue<B>;
62
+ static union<Values extends [...unknown[]]>(values: ReactiveValuesOf<Values>): ReactiveValue<Values>;
59
63
  }
60
64
  export declare abstract class MutableReactiveValue<T> extends ReactiveValue<T> {
61
65
  /**
@@ -103,6 +103,11 @@ export class ReactiveValue {
103
103
  }
104
104
  return result;
105
105
  }
106
+ static union(values) {
107
+ return ReactiveValue.fromCallback(() => {
108
+ return values.map(value => value.get());
109
+ }, values);
110
+ }
106
111
  }
107
112
  export class MutableReactiveValue extends ReactiveValue {
108
113
  static fromProperty(sourceValue, propertyName) {
@@ -4,5 +4,5 @@
4
4
  * @internal
5
5
  */
6
6
  export default {
7
- number: '1.19.1',
7
+ number: '1.20.1',
8
8
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-draw",
3
- "version": "1.19.1",
3
+ "version": "1.20.1",
4
4
  "description": "Draw pictures using a pen, touchscreen, or mouse! JS-draw is a drawing library for JavaScript and TypeScript. ",
5
5
  "types": "./dist/mjs/lib.d.ts",
6
6
  "main": "./dist/cjs/lib.js",
@@ -86,5 +86,5 @@
86
86
  "freehand",
87
87
  "svg"
88
88
  ],
89
- "gitHead": "50fa44a2bb68b93d24efea433760a5e45c56293f"
89
+ "gitHead": "a981ae8ef5f2ac8ef27c8ce2872a2dbf0c25050d"
90
90
  }
@@ -307,14 +307,19 @@
307
307
  border: none;
308
308
  padding: 10px;
309
309
 
310
- color: var(--foreground-color-1);
311
-
312
310
  transition: 0.2s ease box-shadow;
313
311
 
314
- &:hover {
312
+ &:not(:disabled):hover {
315
313
  box-shadow: 0 1px 2px var(--shadow-color);
316
314
  }
317
315
 
316
+ &:disabled {
317
+ opacity: 0.5;
318
+ font-weight: unset;
319
+ cursor: unset;
320
+ color: var(--foreground-color-1);
321
+ }
322
+
318
323
  font-weight: bold;
319
324
  color: var(--primary-action-foreground-color);
320
325
  }
@@ -37,8 +37,9 @@
37
37
  in srgb, var(--foreground-color-1), transparent
38
38
  );
39
39
 
40
- > .cancel-button {
41
- display: block;
40
+ .cancel-button {
41
+ padding-left: 3px;
42
+ padding-right: 3px;
42
43
  }
43
44
 
44
45
  > .toolbar--file-input-description {
@@ -2,27 +2,73 @@
2
2
  // Repeat for specificity.
3
3
  // TODO(v2): Refactor everything to use RCSS.
4
4
  :root .toolbar-snapped-scroll-list.toolbar-snapped-scroll-list.toolbar-snapped-scroll-list {
5
- overflow-y: auto;
6
- scroll-snap-type: y mandatory;
7
5
  height: min(200px, 50vh);
6
+ position: relative;
8
7
  display: flex;
9
- flex-direction: column;
8
+ align-items: center;
9
+
10
+ > .scroller {
11
+ display: flex;
12
+ flex-direction: column;
13
+ overflow-y: auto;
14
+ scroll-snap-type: y mandatory;
10
15
 
11
- > .item {
12
16
  height: 100%;
13
17
  width: 100%;
14
- flex-shrink: 0;
15
-
16
- display: flex;
17
- justify-content: center;
18
- align-items: center;
18
+ flex-grow: 1;
19
19
 
20
- scroll-snap-align: start;
21
- scroll-snap-stop: always;
22
- box-sizing: border-box;
20
+ > .item {
21
+ height: 100%;
22
+ width: 100%;
23
+ flex-shrink: 0;
24
+
25
+ display: flex;
26
+ justify-content: center;
27
+ align-items: center;
28
+
29
+ scroll-snap-align: start;
30
+ scroll-snap-stop: always;
31
+ box-sizing: border-box;
32
+ }
23
33
  }
24
34
 
25
35
  &.-empty {
26
36
  display: none;
27
37
  }
38
+
39
+ > .page-markers {
40
+ overflow: hidden;
41
+
42
+ display: flex;
43
+ flex-direction: column;
44
+ align-items: center;
45
+
46
+ max-height: 100%;
47
+ min-height: 0;
48
+
49
+ &.-one-element {
50
+ visibility: hidden;
51
+ }
52
+
53
+ > .marker {
54
+ > .content {
55
+ background-color: var(--foreground-color-1);
56
+ border-radius: 2px;
57
+ padding: 2px;
58
+ }
59
+
60
+ padding: 2px;
61
+ opacity: 0.1;
62
+ cursor: pointer;
63
+
64
+ left: 0;
65
+ transition: left 0.2s ease;
66
+
67
+ &.-active {
68
+ position: relative;
69
+ left: 2px;
70
+ opacity: 0.2;
71
+ }
72
+ }
73
+ }
28
74
  }