js-draw 1.19.1 → 1.20.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. package/README.md +51 -0
  2. package/dist/Editor.css +46 -5
  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 +10 -2
  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 +10 -2
  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/package.json +2 -2
  40. package/src/toolbar/EdgeToolbar.scss +8 -3
  41. 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 = () => {
@@ -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.0',
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.0",
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": "5bf4f42d0c3367a074c9e9bbb50370421d8bf7d0"
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
  }
@@ -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
  }