@teipublisher/pb-components 1.34.1 → 1.35.0

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.
@@ -754,6 +754,18 @@
754
754
  "type": "array",
755
755
  "default": "[]"
756
756
  },
757
+ {
758
+ "name": "preload",
759
+ "description": "If set, the entire list of possible suggestions will be preloaded upon initialization of the\ncomponent.",
760
+ "type": "boolean",
761
+ "default": "false"
762
+ },
763
+ {
764
+ "name": "substring",
765
+ "description": "By default suggestions are filtered by prefix, i.e. only suggestions starting with the prefix\ntyped by the user are shown. Set this property to true to search for the user-provided string\nanywhere within the suggestion text.",
766
+ "type": "boolean",
767
+ "default": "false"
768
+ },
757
769
  {
758
770
  "name": "subscribe",
759
771
  "description": "The name of the channel to subscribe to. Only events on a channel corresponding\nto this property are listened to.",
@@ -828,6 +840,20 @@
828
840
  {
829
841
  "name": "lastSelected"
830
842
  },
843
+ {
844
+ "name": "preload",
845
+ "attribute": "preload",
846
+ "description": "If set, the entire list of possible suggestions will be preloaded upon initialization of the\ncomponent.",
847
+ "type": "boolean",
848
+ "default": "false"
849
+ },
850
+ {
851
+ "name": "substring",
852
+ "attribute": "substring",
853
+ "description": "By default suggestions are filtered by prefix, i.e. only suggestions starting with the prefix\ntyped by the user are shown. Set this property to true to search for the user-provided string\nanywhere within the suggestion text.",
854
+ "type": "boolean",
855
+ "default": "false"
856
+ },
831
857
  {
832
858
  "name": "subscribe",
833
859
  "attribute": "subscribe",
@@ -10224,9 +10250,8 @@
10224
10250
  },
10225
10251
  {
10226
10252
  "name": "static",
10227
- "description": "If set, rewrite URLs to load pages as static HTML files,\nso no TEI Publisher instance is required. Use this in combination with\n[tei-publisher-static](https://github.com/eeditiones/tei-publisher-static).",
10228
- "type": "boolean",
10229
- "default": "false"
10253
+ "description": "If set, rewrite URLs to load pages as static HTML files,\nso no TEI Publisher instance is required. Use this in combination with\n[tei-publisher-static](https://github.com/eeditiones/tei-publisher-static).\nThe value should point to the HTTP root path under which the static version\nwill be hosted. This is used to resolve CSS stylesheets.",
10254
+ "type": "string"
10230
10255
  },
10231
10256
  {
10232
10257
  "name": "subscribe",
@@ -10426,9 +10451,8 @@
10426
10451
  {
10427
10452
  "name": "static",
10428
10453
  "attribute": "static",
10429
- "description": "If set, rewrite URLs to load pages as static HTML files,\nso no TEI Publisher instance is required. Use this in combination with\n[tei-publisher-static](https://github.com/eeditiones/tei-publisher-static).",
10430
- "type": "boolean",
10431
- "default": "false"
10454
+ "description": "If set, rewrite URLs to load pages as static HTML files,\nso no TEI Publisher instance is required. Use this in combination with\n[tei-publisher-static](https://github.com/eeditiones/tei-publisher-static).\nThe value should point to the HTTP root path under which the static version\nwill be hosted. This is used to resolve CSS stylesheets.",
10455
+ "type": "string"
10432
10456
  },
10433
10457
  {
10434
10458
  "name": "subscribe",
@@ -10550,6 +10574,10 @@
10550
10574
  {
10551
10575
  "name": "--pb-footnote-font-family",
10552
10576
  "description": "Font family for the footnote marker"
10577
+ },
10578
+ {
10579
+ "name": "--pb-view-scroll-margin-top",
10580
+ "description": "Applied to any element with an id"
10553
10581
  }
10554
10582
  ],
10555
10583
  "cssParts": [
@@ -10688,9 +10716,8 @@
10688
10716
  },
10689
10717
  {
10690
10718
  "name": "static",
10691
- "description": "If set, rewrite URLs to load pages as static HTML files,\nso no TEI Publisher instance is required. Use this in combination with\n[tei-publisher-static](https://github.com/eeditiones/tei-publisher-static).",
10692
- "type": "boolean",
10693
- "default": "false"
10719
+ "description": "If set, rewrite URLs to load pages as static HTML files,\nso no TEI Publisher instance is required. Use this in combination with\n[tei-publisher-static](https://github.com/eeditiones/tei-publisher-static).\nThe value should point to the HTTP root path under which the static version\nwill be hosted. This is used to resolve CSS stylesheets.",
10720
+ "type": "string"
10694
10721
  },
10695
10722
  {
10696
10723
  "name": "subscribe",
@@ -10873,9 +10900,8 @@
10873
10900
  {
10874
10901
  "name": "static",
10875
10902
  "attribute": "static",
10876
- "description": "If set, rewrite URLs to load pages as static HTML files,\nso no TEI Publisher instance is required. Use this in combination with\n[tei-publisher-static](https://github.com/eeditiones/tei-publisher-static).",
10877
- "type": "boolean",
10878
- "default": "false"
10903
+ "description": "If set, rewrite URLs to load pages as static HTML files,\nso no TEI Publisher instance is required. Use this in combination with\n[tei-publisher-static](https://github.com/eeditiones/tei-publisher-static).\nThe value should point to the HTTP root path under which the static version\nwill be hosted. This is used to resolve CSS stylesheets.",
10904
+ "type": "string"
10879
10905
  },
10880
10906
  {
10881
10907
  "name": "subscribe",
@@ -10985,6 +11011,10 @@
10985
11011
  {
10986
11012
  "name": "--pb-footnote-font-family",
10987
11013
  "description": "Font family for the footnote marker"
11014
+ },
11015
+ {
11016
+ "name": "--pb-view-scroll-margin-top",
11017
+ "description": "Applied to any element with an id"
10988
11018
  }
10989
11019
  ],
10990
11020
  "cssParts": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teipublisher/pb-components",
3
- "version": "1.34.1",
3
+ "version": "1.35.0",
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",
package/pb-elements.json CHANGED
@@ -754,6 +754,18 @@
754
754
  "type": "array",
755
755
  "default": "[]"
756
756
  },
757
+ {
758
+ "name": "preload",
759
+ "description": "If set, the entire list of possible suggestions will be preloaded upon initialization of the\ncomponent.",
760
+ "type": "boolean",
761
+ "default": "false"
762
+ },
763
+ {
764
+ "name": "substring",
765
+ "description": "By default suggestions are filtered by prefix, i.e. only suggestions starting with the prefix\ntyped by the user are shown. Set this property to true to search for the user-provided string\nanywhere within the suggestion text.",
766
+ "type": "boolean",
767
+ "default": "false"
768
+ },
757
769
  {
758
770
  "name": "subscribe",
759
771
  "description": "The name of the channel to subscribe to. Only events on a channel corresponding\nto this property are listened to.",
@@ -828,6 +840,20 @@
828
840
  {
829
841
  "name": "lastSelected"
830
842
  },
843
+ {
844
+ "name": "preload",
845
+ "attribute": "preload",
846
+ "description": "If set, the entire list of possible suggestions will be preloaded upon initialization of the\ncomponent.",
847
+ "type": "boolean",
848
+ "default": "false"
849
+ },
850
+ {
851
+ "name": "substring",
852
+ "attribute": "substring",
853
+ "description": "By default suggestions are filtered by prefix, i.e. only suggestions starting with the prefix\ntyped by the user are shown. Set this property to true to search for the user-provided string\nanywhere within the suggestion text.",
854
+ "type": "boolean",
855
+ "default": "false"
856
+ },
831
857
  {
832
858
  "name": "subscribe",
833
859
  "attribute": "subscribe",
@@ -10224,9 +10250,8 @@
10224
10250
  },
10225
10251
  {
10226
10252
  "name": "static",
10227
- "description": "If set, rewrite URLs to load pages as static HTML files,\nso no TEI Publisher instance is required. Use this in combination with\n[tei-publisher-static](https://github.com/eeditiones/tei-publisher-static).",
10228
- "type": "boolean",
10229
- "default": "false"
10253
+ "description": "If set, rewrite URLs to load pages as static HTML files,\nso no TEI Publisher instance is required. Use this in combination with\n[tei-publisher-static](https://github.com/eeditiones/tei-publisher-static).\nThe value should point to the HTTP root path under which the static version\nwill be hosted. This is used to resolve CSS stylesheets.",
10254
+ "type": "string"
10230
10255
  },
10231
10256
  {
10232
10257
  "name": "subscribe",
@@ -10426,9 +10451,8 @@
10426
10451
  {
10427
10452
  "name": "static",
10428
10453
  "attribute": "static",
10429
- "description": "If set, rewrite URLs to load pages as static HTML files,\nso no TEI Publisher instance is required. Use this in combination with\n[tei-publisher-static](https://github.com/eeditiones/tei-publisher-static).",
10430
- "type": "boolean",
10431
- "default": "false"
10454
+ "description": "If set, rewrite URLs to load pages as static HTML files,\nso no TEI Publisher instance is required. Use this in combination with\n[tei-publisher-static](https://github.com/eeditiones/tei-publisher-static).\nThe value should point to the HTTP root path under which the static version\nwill be hosted. This is used to resolve CSS stylesheets.",
10455
+ "type": "string"
10432
10456
  },
10433
10457
  {
10434
10458
  "name": "subscribe",
@@ -10550,6 +10574,10 @@
10550
10574
  {
10551
10575
  "name": "--pb-footnote-font-family",
10552
10576
  "description": "Font family for the footnote marker"
10577
+ },
10578
+ {
10579
+ "name": "--pb-view-scroll-margin-top",
10580
+ "description": "Applied to any element with an id"
10553
10581
  }
10554
10582
  ],
10555
10583
  "cssParts": [
@@ -10688,9 +10716,8 @@
10688
10716
  },
10689
10717
  {
10690
10718
  "name": "static",
10691
- "description": "If set, rewrite URLs to load pages as static HTML files,\nso no TEI Publisher instance is required. Use this in combination with\n[tei-publisher-static](https://github.com/eeditiones/tei-publisher-static).",
10692
- "type": "boolean",
10693
- "default": "false"
10719
+ "description": "If set, rewrite URLs to load pages as static HTML files,\nso no TEI Publisher instance is required. Use this in combination with\n[tei-publisher-static](https://github.com/eeditiones/tei-publisher-static).\nThe value should point to the HTTP root path under which the static version\nwill be hosted. This is used to resolve CSS stylesheets.",
10720
+ "type": "string"
10694
10721
  },
10695
10722
  {
10696
10723
  "name": "subscribe",
@@ -10873,9 +10900,8 @@
10873
10900
  {
10874
10901
  "name": "static",
10875
10902
  "attribute": "static",
10876
- "description": "If set, rewrite URLs to load pages as static HTML files,\nso no TEI Publisher instance is required. Use this in combination with\n[tei-publisher-static](https://github.com/eeditiones/tei-publisher-static).",
10877
- "type": "boolean",
10878
- "default": "false"
10903
+ "description": "If set, rewrite URLs to load pages as static HTML files,\nso no TEI Publisher instance is required. Use this in combination with\n[tei-publisher-static](https://github.com/eeditiones/tei-publisher-static).\nThe value should point to the HTTP root path under which the static version\nwill be hosted. This is used to resolve CSS stylesheets.",
10904
+ "type": "string"
10879
10905
  },
10880
10906
  {
10881
10907
  "name": "subscribe",
@@ -10985,6 +11011,10 @@
10985
11011
  {
10986
11012
  "name": "--pb-footnote-font-family",
10987
11013
  "description": "Font family for the footnote marker"
11014
+ },
11015
+ {
11016
+ "name": "--pb-view-scroll-margin-top",
11017
+ "description": "Applied to any element with an id"
10988
11018
  }
10989
11019
  ],
10990
11020
  "cssParts": [
@@ -30,12 +30,12 @@ export class ParseDateService {
30
30
  const resultWeekMatch = this.input.match(this._weekMatchRegex());
31
31
  const resultYearAndMonthMatch = this.input.match(this._yearAndMonthRegex());
32
32
  if (resultIsoMatch) {
33
- const split = resultIsoMatch[0].split(/-|\/|\s/);
33
+ const split = resultIsoMatch[1].split(/-|\/|\s/);
34
34
  this.year = split[0];
35
35
  this.month = this._setWithLeadingZero(split[1]);
36
36
  this.day = this._setWithLeadingZero(split[2]);
37
37
  } else if (resultYearAndMonthMatch) {
38
- const split = resultYearAndMonthMatch[0].split("-");
38
+ const split = resultYearAndMonthMatch[1].split("-");
39
39
  this.year = split[0];
40
40
  this.month = this._setWithLeadingZero(split[1]);
41
41
  this.day = "01";
@@ -73,7 +73,7 @@ export class ParseDateService {
73
73
  * | 2012/1/31 | 2012 1 31 | 2012 01 31 |
74
74
  */
75
75
  _isoMatchRegex() {
76
- return /(?<=\s|^)\d{4}(-|\s|\/)([0][1-9]|[1-9]|10|11|12)(-|\s|\/)([0][1-9]|[1-2][0-9]|3[01]|[1-9])(?=\s|$|\.)/;
76
+ return /(?:\s|^)(\d{4}(-|\s|\/)([0][1-9]|[1-9]|10|11|12)(-|\s|\/)([0][1-9]|[1-2][0-9]|3[01]|[1-9]))(?=\s|$|\.)/;
77
77
  /* | | year | 01-09 | 1-9 | 10-12 | |01-09 |10-29 |30,31| 1-9 |
78
78
  * | | dash or slash | dash or slash |
79
79
  * |preceding with space or start of string end with space endofstr or dot <-|*/
@@ -111,7 +111,7 @@ export class ParseDateService {
111
111
  * | 2020-01 | 2020-12 | 2012-1 |
112
112
  */
113
113
  _yearAndMonthRegex() {
114
- return /(?<=\s|^)\d{4}-([0][1-9]|[1-9]|10|11|12)(?=\s|$)/;
114
+ return /(?:\s|^)(\d{4}-([0][1-9]|[1-9]|10|11|12))(?=\s|$)/;
115
115
  }
116
116
 
117
117
  _findYear() {
@@ -126,14 +126,14 @@ export class ParseDateService {
126
126
  _findMonth() {
127
127
  const months = this._monthDictionaryValues();
128
128
  months.forEach(month => {
129
- let re = new RegExp(`(?<=\\s|^)(${month})(?=\\s|$|\\.)`, "i")
129
+ const re = new RegExp(`(?:\\s|^)(${month})(?=\\s|$|\\.)`, "i")
130
130
  const result = this.input.match(re);
131
131
  if (result) { // yes => get dict and value + return
132
- this.month = this._monthDictionary()[result[0].toLowerCase()];
132
+ this.month = this._monthDictionary()[result[1].toLowerCase()];
133
133
  this._removeMatchFromInput(result);
134
134
  return this.month;
135
135
  }
136
- })
136
+ });
137
137
  return undefined;
138
138
  }
139
139
 
@@ -141,14 +141,14 @@ export class ParseDateService {
141
141
  * find single numbers from 1-31
142
142
  */
143
143
  _findDay() {
144
- let regex = /(?<=\s|^)([0][1-9]|[1-2][0-9]|3[01]|[1-9])(?=\s|$|\.|st|nd|rd|th)/;
144
+ const regex = /(?:\s|^)([0][1-9]|[1-2][0-9]|3[01]|[1-9])(?=\s|$|\.|st|nd|rd|th)/;
145
145
  /* | | 01-09 | 10-29 |30,31|1-9 | ends with whitespace, endofstr or dot.
146
146
  * | starts with whitepace or startoftr | won't be included in match (lookbehind operator)
147
147
  * | look behind operator (not included)
148
148
  * | https://stackoverflow.com/a/6713378/6272061 */
149
149
  const result = this.input.match(regex)
150
150
  if (result) {
151
- this.day = this._setWithLeadingZero(result[0]);
151
+ this.day = this._setWithLeadingZero(result[1]);
152
152
  }
153
153
  }
154
154
 
@@ -6,6 +6,31 @@ import '@polymer/iron-ajax';
6
6
  import '@polymer/iron-icon';
7
7
  import '@cwmr/paper-autocomplete/paper-autocomplete-suggestions.js';
8
8
 
9
+ function _query(datasource, query) {
10
+ const queryResult = [];
11
+ datasource.forEach((item) => {
12
+ let objText, objValue;
13
+
14
+ if (typeof item === 'object') {
15
+ objText = item.text;
16
+ objValue = item.value;
17
+ } else {
18
+ objText = item.toString();
19
+ objValue = objText;
20
+ }
21
+
22
+ if (objText.toLowerCase().indexOf(query) > -1) {
23
+ // NOTE: the structure of the result object matches with the current template. For custom templates, you
24
+ // might need to return more data
25
+ const resultItem = {};
26
+ resultItem.text = objText;
27
+ resultItem.value = objValue;
28
+ queryResult.push(resultItem);
29
+ }
30
+ });
31
+ return queryResult;
32
+ }
33
+
9
34
  /**
10
35
  * Provides an input with attached autocomplete. The autocomplete suggestions can be read
11
36
  * either from a static list or a remote endpoint to which the current user input is sent.
@@ -52,6 +77,13 @@ export class PbAutocomplete extends pbMixin(LitElement) {
52
77
  source: {
53
78
  type: String
54
79
  },
80
+ /**
81
+ * If set, the entire list of possible suggestions will be preloaded upon initialization of the
82
+ * component.
83
+ */
84
+ preload: {
85
+ type: Boolean
86
+ },
55
87
  /**
56
88
  * A static list of suggestions. Use instead of `source`. May either be a flat array of strings,
57
89
  * or an array containing objects of the form `{"text": "", "value": ""}, in which case "value" denotes
@@ -60,6 +92,14 @@ export class PbAutocomplete extends pbMixin(LitElement) {
60
92
  suggestions: {
61
93
  type: Array
62
94
  },
95
+ /**
96
+ * By default suggestions are filtered by prefix, i.e. only suggestions starting with the prefix
97
+ * typed by the user are shown. Set this property to true to search for the user-provided string
98
+ * anywhere within the suggestion text.
99
+ */
100
+ substring: {
101
+ type: Boolean
102
+ },
63
103
  /**
64
104
  * An icon to display next to the input.
65
105
  */
@@ -74,6 +114,8 @@ export class PbAutocomplete extends pbMixin(LitElement) {
74
114
  this.placeholder = 'search.placeholder';
75
115
  this.suggestions = [];
76
116
  this.lastSelected = null;
117
+ this.preload = false;
118
+ this.substring = false;
77
119
  this._hiddenInput = null;
78
120
  this._initialized = false;
79
121
  }
@@ -94,7 +136,14 @@ export class PbAutocomplete extends pbMixin(LitElement) {
94
136
  const autocomplete = this.shadowRoot.getElementById('autocomplete');
95
137
  autocomplete.addEventListener('autocomplete-change', this._autocomplete.bind(this));
96
138
 
97
- if (this.value) {
139
+ if (this.preload && this.source) {
140
+ if (this.substring) {
141
+ autocomplete.queryFn = _query;
142
+ }
143
+ PbAutocomplete.waitOnce('pb-page-ready', () => {
144
+ this._sendRequest();
145
+ });
146
+ } else if (this.value) {
98
147
  if (this.source) {
99
148
  PbAutocomplete.waitOnce('pb-page-ready', () => {
100
149
  //console.log('send autocomplete request for remote source %s on value %s', this.source, this.value);
@@ -140,7 +189,7 @@ export class PbAutocomplete extends pbMixin(LitElement) {
140
189
  always-float-label>
141
190
  ${this.icon ? html`<iron-icon icon="${this.icon}" slot="prefix"></iron-icon>` : null}
142
191
  </paper-input>
143
- <paper-autocomplete-suggestions id="autocomplete" for="search" .source="${this.suggestions}" ?remote-source="${this.source}"
192
+ <paper-autocomplete-suggestions id="autocomplete" for="search" .source="${this.suggestions}" ?remote-source="${!this.preload && this.source}"
144
193
  @autocomplete-selected="${this._autocompleteSelected}"></paper-autocomplete-suggestions>
145
194
 
146
195
  <iron-ajax
@@ -179,7 +228,6 @@ export class PbAutocomplete extends pbMixin(LitElement) {
179
228
  this._sendRequest(search.value);
180
229
  }
181
230
 
182
-
183
231
  _sendRequest(query) {
184
232
  const loader = this.shadowRoot.getElementById('autocompleteLoader');
185
233
  loader.url = this.toAbsoluteURL(this.source);
@@ -195,35 +243,33 @@ export class PbAutocomplete extends pbMixin(LitElement) {
195
243
 
196
244
  _updateSuggestions() {
197
245
  const loader = this.shadowRoot.getElementById('autocompleteLoader');
198
-
199
246
  if (this._initialized) {
200
247
  const autocomplete = this.shadowRoot.getElementById('autocomplete');
201
248
  if (loader.lastResponse) {
202
249
  this.suggestions = loader.lastResponse;
203
250
  autocomplete.suggestions(this.suggestions);
204
251
  }
205
- } else {
206
- if (loader.lastResponse) {
207
- let suggestions = loader.lastResponse;
208
- //console.log('suggestions received', suggestions);
252
+ } else if (loader.lastResponse) {
253
+ const suggestions = loader.lastResponse;
209
254
 
210
- const input = this.shadowRoot.getElementById('search');
211
- const value = suggestions.find((suggestion) => {
212
- if (suggestion.text) {
213
- return suggestion.value === this.value;
214
- }
215
- return suggestion === this.value;
216
- });
217
- if (value) {
218
- input.value = value.text || value;
219
- if (this._hiddenInput) {
220
- this._hiddenInput.value = value.value || value;
221
- }
222
- } else {
223
- if (this._hiddenInput) {
224
- this._hiddenInput.value = this.value;
225
- }
255
+ const input = this.shadowRoot.getElementById('search');
256
+ const value = suggestions.find((suggestion) => {
257
+ if (suggestion.text) {
258
+ return suggestion.value === this.value;
226
259
  }
260
+ return suggestion === this.value;
261
+ });
262
+ if (value) {
263
+ input.value = value.text || value;
264
+ if (this._hiddenInput) {
265
+ this._hiddenInput.value = value.value || value;
266
+ }
267
+ } else if (this._hiddenInput) {
268
+ this._hiddenInput.value = this.value;
269
+ }
270
+
271
+ if (this.preload) {
272
+ this.suggestions = suggestions;
227
273
  }
228
274
  }
229
275
  this._initialized = true;
@@ -242,22 +288,48 @@ export class PbAutocomplete extends pbMixin(LitElement) {
242
288
  _autocompleteSelected(ev) {
243
289
  this.lastSelected = ev.detail.text;
244
290
  const input = this.shadowRoot.getElementById('search');
245
- console.log('autocomplete selected %s', ev.detail.text);
246
291
  input.value = ev.detail.text;
247
292
  this.value = ev.detail.value;
248
293
  if (this._hiddenInput) {
249
294
  this._hiddenInput.value = this.value;
250
295
  }
296
+
297
+ this.emitTo('pb-autocomplete-selected', {
298
+ text: ev.detail.text,
299
+ value: ev.detail.value
300
+ });
251
301
  }
252
302
 
253
303
  _setInput(ev) {
254
304
  const input = this.shadowRoot.getElementById('search');
255
- console.log('Autocomplete set manually to %s', input.value);
256
-
257
305
  this.value = input.value;
306
+
258
307
  if (this._hiddenInput) {
259
308
  this._hiddenInput.value = this.value;
260
309
  }
310
+
311
+ if (ev.keyCode === 13) {
312
+ const entry = this.suggestions.find((suggestion) => {
313
+ if (suggestion.text) {
314
+ return suggestion.value === this.value;
315
+ }
316
+ return suggestion === this.value;
317
+ });
318
+ if (!entry) {
319
+ return;
320
+ }
321
+ if (entry.value) {
322
+ this.emitTo('pb-autocomplete-selected', {
323
+ text: entry.text,
324
+ value: entry.value
325
+ });
326
+ } else {
327
+ this.emitTo('pb-autocomplete-selected', {
328
+ text: entry,
329
+ value: entry
330
+ });
331
+ }
332
+ }
261
333
  }
262
334
 
263
335
 
@@ -299,6 +299,9 @@ export class PbTimeline extends pbMixin(LitElement) {
299
299
  },
300
300
  resettable: {
301
301
  type: Boolean
302
+ },
303
+ _language: {
304
+ type: String
302
305
  }
303
306
  };
304
307
  }
@@ -315,6 +318,7 @@ export class PbTimeline extends pbMixin(LitElement) {
315
318
  this.url = '';
316
319
  this.auto = false;
317
320
  this.resettable = false;
321
+ this._language = 'en';
318
322
  this._resetSelectionProperty();
319
323
  }
320
324
 
@@ -327,6 +331,9 @@ export class PbTimeline extends pbMixin(LitElement) {
327
331
  loader.url = url;
328
332
  loader.generateRequest();
329
333
  });
334
+ this.subscribeTo('pb-i18n-update', (ev) => {
335
+ this._language = ev.detail.language;
336
+ });
330
337
  }
331
338
 
332
339
  firstUpdated() {
@@ -620,7 +627,7 @@ export class PbTimeline extends pbMixin(LitElement) {
620
627
  }
621
628
 
622
629
  _numberWithCommas(input) {
623
- return input.toString().replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, "'");
630
+ return new Intl.NumberFormat(this._language, {style: 'decimal'}).format(input);
624
631
  }
625
632
 
626
633
  _areOverlapping(A, B) { // check if 2 intervals are overlapping
package/src/pb-view.js CHANGED
@@ -45,6 +45,7 @@ import '@polymer/paper-dialog-scrollable';
45
45
  * @cssprop --pb-footnote-padding - Padding around a footnote marker
46
46
  * @cssprop --pb-footnote-font-size - Font size for the footnote marker
47
47
  * @cssprop --pb-footnote-font-family - Font family for the footnote marker
48
+ * @cssprop --pb-view-scroll-margin-top - Applied to any element with an id
48
49
  * @csspart content - The root div around the displayed content
49
50
  * @csspart footnotes - div containing the footnotes
50
51
 
@@ -165,9 +166,11 @@ export class PbView extends pbMixin(LitElement) {
165
166
  * If set, rewrite URLs to load pages as static HTML files,
166
167
  * so no TEI Publisher instance is required. Use this in combination with
167
168
  * [tei-publisher-static](https://github.com/eeditiones/tei-publisher-static).
169
+ * The value should point to the HTTP root path under which the static version
170
+ * will be hosted. This is used to resolve CSS stylesheets.
168
171
  */
169
172
  static: {
170
- type: Boolean
173
+ type: String
171
174
  },
172
175
  /**
173
176
  * The server returns footnotes separately. Set this property
@@ -336,7 +339,7 @@ export class PbView extends pbMixin(LitElement) {
336
339
  this._selector = new Map();
337
340
  this._chunks = [];
338
341
  this._scrollTarget = null;
339
- this.static = false;
342
+ this.static = null;
340
343
  }
341
344
 
342
345
  attributeChangedCallback(name, oldVal, newVal) {
@@ -522,7 +525,7 @@ export class PbView extends pbMixin(LitElement) {
522
525
  this._scrollTarget = ev.detail.hash;
523
526
  const target = this.shadowRoot.getElementById(this._scrollTarget);
524
527
  if (target) {
525
- setTimeout(() => target.scrollIntoView());
528
+ setTimeout(() => target.scrollIntoView({block: 'nearest'}));
526
529
  }
527
530
  return;
528
531
  }
@@ -538,6 +541,10 @@ export class PbView extends pbMixin(LitElement) {
538
541
  this.columnSeparator = ev.detail.columnSeparator;
539
542
  }
540
543
  this.view = ev.detail.view || this.view;
544
+ if (ev.detail.xpath) {
545
+ this.xpath = ev.detail.xpath;
546
+ this.nodeId = null;
547
+ }
541
548
  // clear nodeId if set to null
542
549
  if (ev.detail.position === null) {
543
550
  this.nodeId = null;
@@ -593,7 +600,7 @@ export class PbView extends pbMixin(LitElement) {
593
600
 
594
601
  const loadContent = this.shadowRoot.getElementById('loadContent');
595
602
 
596
- if (this.static) {
603
+ if (this.static !== null) {
597
604
  this._staticUrl(params).then((url) => {
598
605
  loadContent.url = url;
599
606
  loadContent.generateRequest();
@@ -623,7 +630,7 @@ export class PbView extends pbMixin(LitElement) {
623
630
  function createKey(paramNames) {
624
631
  const urlComponents = [];
625
632
  paramNames.sort().forEach(key => {
626
- if (params[key]) {
633
+ if (params.hasOwnProperty(key)) {
627
634
  urlComponents.push(`${key}=${params[key]}`);
628
635
  }
629
636
  });
@@ -632,9 +639,9 @@ export class PbView extends pbMixin(LitElement) {
632
639
 
633
640
  const index = await fetch(`index.json`)
634
641
  .then((response) => response.json());
635
- const paramNames = ['odd', 'view', 'xpath'];
642
+ const paramNames = ['odd', 'view', 'xpath', 'map'];
636
643
  this.querySelectorAll('pb-param').forEach((param) => paramNames.push(`user.${param.getAttribute('name')}`));
637
- let url = createKey([...paramNames, 'root']);
644
+ let url = params.id ? createKey([...paramNames, 'id']) : createKey([...paramNames, 'root']);
638
645
  let file = index[url];
639
646
  if (!file) {
640
647
  url = createKey(paramNames);
@@ -705,9 +712,11 @@ export class PbView extends pbMixin(LitElement) {
705
712
  const target = this.shadowRoot.getElementById(this._scrollTarget) ||
706
713
  this.shadowRoot.querySelector(`[node-id="${this._scrollTarget}"]`);
707
714
  if (target) {
708
- setTimeout(() => {
709
- target.scrollIntoView();
710
- }, 100);
715
+ window.requestAnimationFrame(() =>
716
+ setTimeout(() => {
717
+ target.scrollIntoView({block: 'nearest'});
718
+ }, 400)
719
+ );
711
720
  }
712
721
  this._scrollTarget = null;
713
722
  });
@@ -883,8 +892,8 @@ export class PbView extends pbMixin(LitElement) {
883
892
  let link = document.createElement('link');
884
893
  link.setAttribute('rel', 'stylesheet');
885
894
  link.setAttribute('type', 'text/css');
886
- if (this.static) {
887
- link.setAttribute('href', `/css/${this.getOdd()}.css`);
895
+ if (this.static !== null) {
896
+ link.setAttribute('href', `${this.static}/css/${this.getOdd()}.css`);
888
897
  } else {
889
898
  link.setAttribute('href', `${this.getEndpoint()}/transform/${this.getOdd()}.css`);
890
899
  }
@@ -894,7 +903,7 @@ export class PbView extends pbMixin(LitElement) {
894
903
  link = document.createElement('link');
895
904
  link.setAttribute('rel', 'stylesheet');
896
905
  link.setAttribute('type', 'text/css');
897
- link.setAttribute('href', `${this.getEndpoint()}/${this.loadCss}`);
906
+ link.setAttribute('href', this.toAbsoluteURL(this.loadCss));
898
907
  links.push(link);
899
908
  }
900
909
 
@@ -1204,6 +1213,10 @@ export class PbView extends pbMixin(LitElement) {
1204
1213
  display: none;
1205
1214
  }
1206
1215
 
1216
+ [id] {
1217
+ scroll-margin-top: var(--pb-view-scroll-margin-top);
1218
+ }
1219
+
1207
1220
  #view {
1208
1221
  position: relative;
1209
1222
  }