@quillmark/svelte 0.26.0 → 0.26.2

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.
@@ -13,6 +13,9 @@ const WHEEL_RATE = [0.0025, 0.08, 0.25];
13
13
  * rate that inverts the pinch's encoding. */
14
14
  const PINCH_DELTA = 50;
15
15
  const PINCH_RATE = 0.01;
16
+ /** How far, as a log of the spread's ratio, two fingers part or close before a touch
17
+ * pinch zooms rather than only pans. */
18
+ const PINCH_SLOP = 0.04;
16
19
  const px = (value) => parseFloat(value) || 0;
17
20
  export function createViewport(container, slots, setScale, resetLabel) {
18
21
  let zoom;
@@ -154,14 +157,13 @@ export function createViewport(container, slots, setScale, resetLabel) {
154
157
  const factor = Math.exp(-e.deltaY * (pinched ? PINCH_RATE : WHEEL_RATE[e.deltaMode]));
155
158
  glideTo((g) => g.raw * factor, e.clientX, e.clientY);
156
159
  }
157
- // The keys reach the pane under the pointer as well as the focused one: a click on a
158
- // page lands its caret in the editor, taking the focus with it.
159
- // The first pane to take a key claims it, so two previews never both step, and a key
160
- // a host's own handler took is left to it.
160
+ // The keys are the focused pane's alone: elsewhere they are the browser's own zoom,
161
+ // the pointer over the pane notwithstanding. A key a host's own handler took is left
162
+ // to it.
161
163
  function onKeyDown(e) {
162
164
  if (!(e.ctrlKey || e.metaKey) || e.altKey || e.defaultPrevented || e.isComposing)
163
165
  return;
164
- if (!pointer && !container.contains(document.activeElement))
166
+ if (!container.contains(document.activeElement))
165
167
  return;
166
168
  const dir = e.key === '=' || e.key === '+' ? 1 : e.key === '-' || e.key === '_' ? -1 : 0;
167
169
  if ((!dir && e.key !== '0') || !bounds())
@@ -169,30 +171,54 @@ export function createViewport(container, slots, setScale, resetLabel) {
169
171
  e.preventDefault();
170
172
  step((b) => (dir ? stepZoom(b.current, dir, b.floor) : undefined));
171
173
  }
172
- // A two-finger pinch on a touch screen. The browser's own page zoom is held off by the
173
- // desk's `touch-action` and by cancelling the move, which also stops the pan a second
174
- // finger would otherwise start.
175
174
  let pinch;
176
- const spread = (t) => Math.hypot(t[0].clientX - t[1].clientX, t[0].clientY - t[1].clientY);
177
- function onTouchStart(e) {
178
- if (e.touches.length !== 2)
179
- return;
175
+ // The fingers on this pane, not one resting on the editor beside it.
176
+ const fingers = (e) => [...e.touches].filter((t) => t.target instanceof Node && container.contains(t.target));
177
+ const spread = ([a, b]) => Math.hypot(a.clientX - b.clientX, a.clientY - b.clientY);
178
+ const midpoint = ([a, b]) => ({
179
+ x: (a.clientX + b.clientX) / 2,
180
+ y: (a.clientY + b.clientY) / 2
181
+ });
182
+ function grip(at) {
183
+ const page = pageAt(at.y);
184
+ const r = page?.getBoundingClientRect();
185
+ if (!page || !r?.width || !r.height)
186
+ return undefined;
187
+ return { page, fx: (at.x - r.left) / r.width, fy: (at.y - r.top) / r.height };
188
+ }
189
+ // A finger landing or lifting starts the pinch over with the two that remain.
190
+ function onTouches(e) {
191
+ pinch = undefined;
192
+ const t = fingers(e);
180
193
  const b = bounds();
181
- if (b)
182
- pinch = { spread: spread(e.touches), raw: from(b).raw };
194
+ const held = t.length === 2 && b ? grip(midpoint(t)) : undefined;
195
+ if (held)
196
+ pinch = { spread: spread(t), raw: from(b).raw, zooming: false, ...held };
183
197
  }
184
198
  function onTouchMove(e) {
185
- if (!pinch || e.touches.length !== 2)
199
+ const t = fingers(e);
200
+ if (!pinch || t.length !== 2)
186
201
  return;
187
202
  e.preventDefault();
188
- const [a, b] = [e.touches[0], e.touches[1]];
189
- const ratio = spread(e.touches) / pinch.spread;
190
- const start = pinch.raw;
191
- glideTo(() => start * ratio, (a.clientX + b.clientX) / 2, (a.clientY + b.clientY) / 2);
192
- }
193
- function onTouchEnd(e) {
194
- if (e.touches.length < 2)
195
- pinch = undefined;
203
+ const mid = midpoint(t);
204
+ const ratio = spread(t) / pinch.spread;
205
+ if (pinch.zooming) {
206
+ const start = pinch.raw;
207
+ glideTo(() => start * ratio, mid.x, mid.y);
208
+ }
209
+ else if (Math.abs(Math.log(ratio)) >= PINCH_SLOP) {
210
+ pinch.zooming = true;
211
+ pinch.spread = spread(t);
212
+ }
213
+ // A move the browser is already scrolling cannot be cancelled: its pan stands, and
214
+ // the pinch holds whatever is under the fingers now.
215
+ if (!e.cancelable) {
216
+ Object.assign(pinch, grip(mid));
217
+ return;
218
+ }
219
+ const r = pinch.page.getBoundingClientRect();
220
+ container.scrollLeft += r.left + pinch.fx * r.width - mid.x;
221
+ container.scrollTop += r.top + pinch.fy * r.height - mid.y;
196
222
  }
197
223
  // Safari's trackpad pinch. iOS sends these beside the touches, which are the ones read
198
224
  // there; either way the page zoom is cancelled.
@@ -231,10 +257,10 @@ export function createViewport(container, slots, setScale, resetLabel) {
231
257
  const listeners = [
232
258
  [container, 'wheel', onWheel, { passive: false }],
233
259
  [window, 'keydown', onKeyDown],
234
- [container, 'touchstart', onTouchStart, { passive: true }],
260
+ [container, 'touchstart', onTouches, { passive: true }],
235
261
  [container, 'touchmove', onTouchMove, { passive: false }],
236
- [container, 'touchend', onTouchEnd],
237
- [container, 'touchcancel', onTouchEnd],
262
+ [container, 'touchend', onTouches],
263
+ [container, 'touchcancel', onTouches],
238
264
  [container, 'gesturestart', onGestureStart],
239
265
  [container, 'gesturechange', onGestureChange],
240
266
  [container, 'gestureend', onGestureEnd],
@@ -170,8 +170,14 @@
170
170
  view.dispatch(view.state.tr.setSelection(sel).scrollIntoView());
171
171
  }
172
172
 
173
+ /** What the view draws from the props. The effect below writes it and the view reads
174
+ * it, never the props: ProseMirror's `destroy` redraws the decorations and attributes
175
+ * once more, and a prop read there re-derives the card over handles the host has
176
+ * already released. */
177
+ let drawn = untrack(() => ({ label, labelledBy, describedBy, placeholder }));
178
+
173
179
  $effect(() => {
174
- void [label, labelledBy, describedBy, placeholder];
180
+ drawn = { label, labelledBy, describedBy, placeholder };
175
181
  view?.setProps({});
176
182
  });
177
183
 
@@ -181,7 +187,7 @@
181
187
  const leaf = { inline, plaintext, islands: false };
182
188
  // Read per attribute pass, not captured: an element's name carries its index,
183
189
  // which a remove above it moves.
184
- const names = () => ({ label, labelledBy, describedBy });
190
+ const names = () => drawn;
185
191
  const shown = (stored: Content | undefined): Content => stored ?? fallback ?? emptyContent();
186
192
  const stored = content();
187
193
  let seen = value;
@@ -212,7 +218,7 @@
212
218
  ? []
213
219
  : proseLeafPlugins(schema, {
214
220
  inline,
215
- placeholder: () => placeholder,
221
+ placeholder: () => drawn.placeholder,
216
222
  cap
217
223
  })
218
224
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quillmark/svelte",
3
- "version": "0.26.0",
3
+ "version": "0.26.2",
4
4
  "description": "Editor, live-preview and source surfaces over a Quillmark WASM session.",
5
5
  "repository": {
6
6
  "type": "git",