@teipublisher/pb-components 3.6.2 → 3.6.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teipublisher/pb-components",
3
- "version": "3.6.2",
3
+ "version": "3.6.4",
4
4
  "description": "Collection of webcomponents underlying TEI Publisher",
5
5
  "repository": "https://github.com/eeditiones/tei-publisher-components.git",
6
6
  "main": "index.html",
@@ -87,6 +87,20 @@ export class PbAuthorityLookup extends themableMixin(pbMixin(LitElement)) {
87
87
  this._authorities = {};
88
88
  this.noOccurrences = false;
89
89
  this.group = 'tei';
90
+ this._queryGeneration = 0;
91
+ this._queryDebounceMs = 300;
92
+ }
93
+
94
+ disconnectedCallback() {
95
+ super.disconnectedCallback();
96
+ if (this._queryTimer) {
97
+ clearTimeout(this._queryTimer);
98
+ this._queryTimer = null;
99
+ }
100
+ if (this._occurrencesController) {
101
+ this._occurrencesController.abort();
102
+ this._occurrencesController = null;
103
+ }
90
104
  }
91
105
 
92
106
  connectedCallback() {
@@ -101,7 +115,7 @@ export class PbAuthorityLookup extends themableMixin(pbMixin(LitElement)) {
101
115
  this.query = ev.detail.query;
102
116
  this.type = ev.detail.type;
103
117
  this._results = [];
104
- this._query();
118
+ this._scheduleQuery(true);
105
119
  });
106
120
 
107
121
  waitOnce('pb-page-ready', () => {
@@ -110,7 +124,7 @@ export class PbAuthorityLookup extends themableMixin(pbMixin(LitElement)) {
110
124
  this._authorities[connector.register] = connector;
111
125
  });
112
126
  if (this.autoLookup) {
113
- this._query();
127
+ this._scheduleQuery(true);
114
128
  }
115
129
  });
116
130
 
@@ -347,28 +361,76 @@ export class PbAuthorityLookup extends themableMixin(pbMixin(LitElement)) {
347
361
  }
348
362
  this._results = [];
349
363
  this.query = e.target.value;
350
- this._query();
364
+ this._scheduleQuery();
365
+ }
366
+
367
+ _scheduleQuery(immediate = false) {
368
+ if (this._queryTimer) {
369
+ clearTimeout(this._queryTimer);
370
+ this._queryTimer = null;
371
+ }
372
+ if (immediate) {
373
+ this._query();
374
+ return;
375
+ }
376
+ this._queryTimer = setTimeout(() => {
377
+ this._queryTimer = null;
378
+ this._query();
379
+ }, this._queryDebounceMs);
351
380
  }
352
381
 
353
382
  _query() {
383
+ const generation = ++this._queryGeneration;
384
+
385
+ if (this._occurrencesController) {
386
+ this._occurrencesController.abort();
387
+ this._occurrencesController = null;
388
+ }
389
+
390
+ const authority = this._authorities[this.type];
391
+ if (!authority) {
392
+ return;
393
+ }
394
+
354
395
  this.emitTo('pb-start-update');
355
- this._authorities[this.type].query(this.query).then(results => {
356
- this._occurrences(results.items).then(merged => {
396
+ authority
397
+ .query(this.query)
398
+ .then(results => {
399
+ if (generation !== this._queryGeneration) {
400
+ return undefined;
401
+ }
402
+ return this._occurrences(results.items, generation);
403
+ })
404
+ .then(merged => {
405
+ if (merged === undefined || generation !== this._queryGeneration) {
406
+ return;
407
+ }
357
408
  this._results = merged;
409
+ this.emitTo('pb-end-update');
410
+ })
411
+ .catch(err => {
412
+ if (generation !== this._queryGeneration) {
413
+ return;
414
+ }
415
+ console.error('<pb-authority-lookup> query failed: %s', err.message);
416
+ this.emitTo('pb-end-update');
358
417
  });
359
- this.emitTo('pb-end-update');
360
- // this.shadowRoot.getElementById('query').focus();
361
- });
362
418
  }
363
419
 
364
420
  _addEntity() {
365
421
  this.emitTo('pb-authority-new-entity', { query: this.query, type: this.type });
366
422
  }
367
423
 
368
- _occurrences(items) {
424
+ _occurrences(items, generation) {
369
425
  if (this.noOccurrences) {
370
426
  return Promise.resolve(items);
371
427
  }
428
+ if (this._occurrencesController) {
429
+ this._occurrencesController.abort();
430
+ }
431
+ const controller = new AbortController();
432
+ this._occurrencesController = controller;
433
+
372
434
  const params = new FormData();
373
435
  params.append('register', this.type);
374
436
  items.forEach(item => {
@@ -378,13 +440,27 @@ export class PbAuthorityLookup extends themableMixin(pbMixin(LitElement)) {
378
440
  fetch(`${this.getEndpoint()}/api/annotations/occurrences`, {
379
441
  method: 'POST',
380
442
  body: params,
443
+ signal: controller.signal,
381
444
  })
382
445
  .then(response => {
446
+ if (generation !== this._queryGeneration) {
447
+ resolve(undefined);
448
+ return undefined;
449
+ }
383
450
  if (response.ok) {
384
451
  return response.json();
385
452
  }
453
+ resolve(items);
454
+ return undefined;
386
455
  })
387
456
  .then(json => {
457
+ if (json === undefined) {
458
+ return;
459
+ }
460
+ if (generation !== this._queryGeneration) {
461
+ resolve(undefined);
462
+ return;
463
+ }
388
464
  items.forEach(item => {
389
465
  if (json[item.id]) {
390
466
  item.occurrences = json[item.id];
@@ -406,6 +482,13 @@ export class PbAuthorityLookup extends themableMixin(pbMixin(LitElement)) {
406
482
  return d;
407
483
  });
408
484
  resolve(items);
485
+ })
486
+ .catch(err => {
487
+ if (err.name === 'AbortError' || generation !== this._queryGeneration) {
488
+ resolve(undefined);
489
+ return;
490
+ }
491
+ resolve(items);
409
492
  });
410
493
  });
411
494
  }
@@ -405,7 +405,7 @@ class PbViewAnnotate extends PbView {
405
405
  console.warn('<pb-view-annotate> history is empty');
406
406
  return;
407
407
  }
408
- this._scrollTop = this.scrollTop;
408
+ this._captureScrollPosition();
409
409
  const lastEntry = this._history.pop();
410
410
  this._clearMarkers();
411
411
  this._ranges = JSON.parse(lastEntry);
@@ -460,11 +460,57 @@ class PbViewAnnotate extends PbView {
460
460
  });
461
461
  }
462
462
 
463
+ /**
464
+ * Return the nearest ancestor (or this element) that actually scrolls.
465
+ * In fixed-layout annotate pages the scroll container is usually `main`,
466
+ * not the pb-view-annotate host.
467
+ */
468
+ _scrollContainer() {
469
+ let el = this;
470
+ while (el) {
471
+ const { overflow, overflowY } = getComputedStyle(el);
472
+ const scrollable =
473
+ /(auto|scroll|overlay)/.test(overflowY) || /(auto|scroll|overlay)/.test(overflow);
474
+ if (scrollable && el.scrollHeight > el.clientHeight) {
475
+ return el;
476
+ }
477
+ el = el.parentElement;
478
+ }
479
+ return this;
480
+ }
481
+
482
+ _captureScrollPosition() {
483
+ const el = this._scrollContainer();
484
+ this._scrollEl = el;
485
+ this._scrollTop = el.scrollTop;
486
+ }
487
+
488
+ _restoreScrollPosition() {
489
+ if (this._scrollTop === undefined || !this._scrollEl) {
490
+ return;
491
+ }
492
+ const { _scrollEl: el, _scrollTop: top } = this;
493
+ const restore = () => {
494
+ el.scrollTop = top;
495
+ };
496
+ restore();
497
+ requestAnimationFrame(() => {
498
+ restore();
499
+ setTimeout(restore, 100);
500
+ // Run after pb-view._scroll() (400ms) so URL-hash scrolling does not win.
501
+ setTimeout(() => {
502
+ restore();
503
+ this._scrollTop = undefined;
504
+ this._scrollEl = undefined;
505
+ }, 450);
506
+ });
507
+ }
508
+
463
509
  _refresh(ev) {
464
- super._refresh(ev);
465
- if (ev && ev.detail && ev.detail.preserveScroll) {
466
- this._scrollTop = this.scrollTop;
510
+ if (ev?.detail?.preserveScroll) {
511
+ this._captureScrollPosition();
467
512
  }
513
+ super._refresh(ev);
468
514
  }
469
515
 
470
516
  _doLoad(params) {
@@ -480,10 +526,7 @@ class PbViewAnnotate extends PbView {
480
526
  this._annotationStyles();
481
527
  this.updateAnnotations(true, false);
482
528
  this._markIncompleteAnnotations();
483
- if (this._scrollTop) {
484
- this.scrollTop = this._scrollTop;
485
- this._scrollTop = undefined;
486
- }
529
+ this._restoreScrollPosition();
487
530
  this.emitTo('pb-annotations-loaded');
488
531
  // Marker positions depend on the ODD stylesheet and final text layout.
489
532
  this._scheduleMarkerRefresh();