@teipublisher/pb-components 2.26.1-next.3 → 3.0.0-next-4.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 (262) hide show
  1. package/.github/workflows/docker-cypress.yml +54 -0
  2. package/.github/workflows/main.yml +6 -4
  3. package/.github/workflows/node.js.yml +56 -21
  4. package/.github/workflows/release.js.yml +19 -17
  5. package/.releaserc.json +1 -1
  6. package/CHANGELOG.md +346 -11
  7. package/Dockerfile +78 -70
  8. package/README.md +112 -4
  9. package/css/components.css +5 -5
  10. package/css/gridjs/mermaid.min.css +1 -1
  11. package/css/leaflet/Control.Geocoder.css +1 -126
  12. package/css/leaflet/images/layers.png +0 -0
  13. package/css/tify/tify.css +6 -5
  14. package/css/tom-select/tom-select.bootstrap4.min.css +1 -1
  15. package/css/tom-select/tom-select.bootstrap5.min.css +1 -1
  16. package/css/tom-select/tom-select.default.min.css +1 -1
  17. package/css/tom-select/tom-select.default.min.css.map +1 -0
  18. package/css/tom-select/tom-select.min.css +1 -1
  19. package/cypress.config.js +84 -0
  20. package/dist/api.html +1 -1
  21. package/dist/css/design-system.css +607 -0
  22. package/dist/demo/bundle-test.html +4 -3
  23. package/dist/demo/components.css +46 -1
  24. package/dist/demo/design-system.html +710 -0
  25. package/dist/demo/dts-client.html +2 -2
  26. package/dist/demo/pb-autocomplete.html +23 -11
  27. package/dist/demo/pb-autocomplete2.html +66 -55
  28. package/dist/demo/pb-autocomplete3.html +17 -8
  29. package/dist/demo/pb-blacklab-highlight.html +28 -11
  30. package/dist/demo/pb-blacklab-results.html +3 -2
  31. package/dist/demo/pb-browse-docs.html +24 -24
  32. package/dist/demo/pb-browse-docs2.html +3 -3
  33. package/dist/demo/pb-clipboard.html +32 -28
  34. package/dist/demo/pb-code-editor.html +6 -6
  35. package/dist/demo/pb-code-highlight.html +63 -63
  36. package/dist/demo/pb-codepen.html +1 -1
  37. package/dist/demo/pb-collapse.html +1 -1
  38. package/dist/demo/pb-collapse2.html +2 -2
  39. package/dist/demo/pb-combo-box.html +135 -130
  40. package/dist/demo/pb-custom-form.html +64 -55
  41. package/dist/demo/pb-dialog.html +12 -6
  42. package/dist/demo/pb-document.html +1 -1
  43. package/dist/demo/pb-download.html +68 -59
  44. package/dist/demo/pb-drawer.html +67 -46
  45. package/dist/demo/pb-drawer2.html +65 -58
  46. package/dist/demo/pb-edit-app.html +2 -2
  47. package/dist/demo/pb-edit-xml.html +1 -1
  48. package/dist/demo/pb-facsimile-2.html +26 -11
  49. package/dist/demo/pb-facsimile-3.html +25 -10
  50. package/dist/demo/pb-facsimile-dedup-test-2.html +48 -0
  51. package/dist/demo/pb-facsimile-dedup-test.html +48 -0
  52. package/dist/demo/pb-facsimile.html +4 -4
  53. package/dist/demo/pb-formula.html +1 -1
  54. package/dist/demo/pb-grid.html +22 -8
  55. package/dist/demo/pb-highlight.html +2 -2
  56. package/dist/demo/pb-i18n-simple.html +1 -0
  57. package/dist/demo/pb-i18n.html +15 -5
  58. package/dist/demo/pb-image-strip-standalone.html +2 -2
  59. package/dist/demo/pb-image-strip-view.html +2 -2
  60. package/dist/demo/pb-leaflet-map.html +3 -3
  61. package/dist/demo/pb-leaflet-map2.html +2 -2
  62. package/dist/demo/pb-leaflet-map3.html +3 -3
  63. package/dist/demo/pb-link.html +1 -1
  64. package/dist/demo/pb-load.html +2 -6
  65. package/dist/demo/pb-login.html +1 -3
  66. package/dist/demo/pb-manage-odds.html +9 -4
  67. package/dist/demo/pb-markdown.html +1 -1
  68. package/dist/demo/pb-media-query.html +2 -2
  69. package/dist/demo/pb-mei.html +2 -2
  70. package/dist/demo/pb-mei2.html +2 -2
  71. package/dist/demo/pb-message.html +2 -3
  72. package/dist/demo/pb-odd-editor.html +54 -52
  73. package/dist/demo/pb-page-header.html +27 -0
  74. package/dist/demo/pb-popover.html +1 -1
  75. package/dist/demo/pb-print-preview.html +2 -2
  76. package/dist/demo/pb-progress.html +4 -4
  77. package/dist/demo/pb-repeat.html +32 -36
  78. package/dist/demo/pb-search.html +16 -5
  79. package/dist/demo/pb-search2.html +4 -4
  80. package/dist/demo/pb-search3.html +3 -3
  81. package/dist/demo/pb-search4.html +3 -3
  82. package/dist/demo/pb-select-feature.html +4 -4
  83. package/dist/demo/pb-select-feature2.html +4 -4
  84. package/dist/demo/pb-select-feature3.html +2 -2
  85. package/dist/demo/pb-select-i18n.html +58 -53
  86. package/dist/demo/pb-select-odd.html +1 -1
  87. package/dist/demo/pb-select.html +190 -75
  88. package/dist/demo/pb-select2.html +91 -37
  89. package/dist/demo/pb-select3.html +109 -41
  90. package/dist/demo/pb-svg.html +1 -1
  91. package/dist/demo/pb-table-grid.html +26 -15
  92. package/dist/demo/pb-tabs.html +15 -7
  93. package/dist/demo/pb-tify.html +7 -7
  94. package/dist/demo/pb-timeline.html +1 -1
  95. package/dist/demo/pb-timeline2.html +1 -1
  96. package/dist/demo/pb-toggle-feature.html +26 -23
  97. package/dist/demo/pb-toggle-feature2.html +4 -4
  98. package/dist/demo/pb-toggle-feature3.html +2 -2
  99. package/dist/demo/pb-toggle-feature4.html +56 -54
  100. package/dist/demo/pb-version.html +2 -2
  101. package/dist/demo/pb-view.html +78 -40
  102. package/dist/demo/pb-view2.html +69 -46
  103. package/dist/demo/pb-view3.html +53 -48
  104. package/dist/demo/pb-view4.html +70 -49
  105. package/dist/demo/pb-zoom.html +2 -2
  106. package/dist/{es-global-bridge-d8ce175d.js → es-global-bridge-D8ZcUcx_.js} +0 -4
  107. package/dist/focus-mixin-VCsFap6b.js +768 -0
  108. package/dist/images/icons.svg +217 -0
  109. package/dist/jinn-codemirror-DETLdm08.js +1 -0
  110. package/dist/lib/openseadragon.min.js +80 -0
  111. package/dist/lib/openseadragon.min.js.map +1 -0
  112. package/dist/pb-code-editor.js +25 -20
  113. package/dist/pb-component-docs.js +414 -3225
  114. package/dist/pb-components-bundle.js +3046 -4402
  115. package/dist/pb-dialog-tklYGWfc.js +121 -0
  116. package/dist/pb-edit-app.js +208 -107
  117. package/dist/pb-elements.json +716 -249
  118. package/dist/pb-facsimile.js +46 -0
  119. package/dist/pb-i18n-C0NDma4h.js +1 -0
  120. package/dist/pb-leaflet-map.js +23 -23
  121. package/dist/pb-mei.js +152 -134
  122. package/dist/pb-mixin-DHoWQheB.js +1 -0
  123. package/dist/pb-odd-editor.js +1671 -1231
  124. package/dist/pb-tify.js +1 -27
  125. package/dist/unsafe-html-D5VGo9Oq.js +1 -0
  126. package/dist/urls-BEONu_g4.js +1 -0
  127. package/eslint.config.mjs +92 -0
  128. package/gh-pages.js +5 -3
  129. package/i18n/common/en.json +6 -0
  130. package/i18n/common/pl.json +2 -2
  131. package/images/icons.svg +217 -0
  132. package/index.html +0 -5
  133. package/lib/leaflet-src.js.map +1 -0
  134. package/lib/leaflet.markercluster-src.js.map +1 -0
  135. package/lib/openseadragon.min.js +6 -6
  136. package/package.json +56 -81
  137. package/pb-elements.json +716 -249
  138. package/rollup.config.mjs +312 -0
  139. package/src/assets/components.css +5 -5
  140. package/src/assets/design-system.css +607 -0
  141. package/src/authority/airtable.js +20 -21
  142. package/src/authority/anton.js +129 -129
  143. package/src/authority/custom.js +70 -27
  144. package/src/authority/geonames.js +38 -32
  145. package/src/authority/gnd.js +50 -42
  146. package/src/authority/kbga.js +136 -134
  147. package/src/authority/metagrid.js +44 -46
  148. package/src/authority/reconciliation.js +66 -68
  149. package/src/authority/registry.js +4 -4
  150. package/src/docs/demo-utils.js +91 -0
  151. package/src/docs/pb-component-docs.js +287 -147
  152. package/src/docs/pb-component-view.js +380 -273
  153. package/src/docs/pb-components-list.js +115 -51
  154. package/src/docs/pb-demo-snippet.js +199 -174
  155. package/src/dts-client.js +306 -303
  156. package/src/dts-select-endpoint.js +125 -85
  157. package/src/parse-date-service.js +184 -135
  158. package/src/pb-ajax.js +175 -173
  159. package/src/pb-authority-lookup.js +198 -158
  160. package/src/pb-autocomplete.js +731 -313
  161. package/src/pb-blacklab-highlight.js +266 -260
  162. package/src/pb-blacklab-results.js +230 -225
  163. package/src/pb-browse-docs.js +601 -484
  164. package/src/pb-browse.js +68 -65
  165. package/src/pb-clipboard.js +97 -76
  166. package/src/pb-code-editor.js +111 -103
  167. package/src/pb-code-highlight.js +234 -204
  168. package/src/pb-codepen.js +81 -73
  169. package/src/pb-collapse.js +265 -152
  170. package/src/pb-combo-box.js +191 -191
  171. package/src/pb-components-bundle.js +1 -7
  172. package/src/pb-components.js +2 -6
  173. package/src/pb-custom-form.js +230 -141
  174. package/src/pb-dialog.js +99 -63
  175. package/src/pb-document.js +118 -91
  176. package/src/pb-download.js +214 -198
  177. package/src/pb-drawer.js +146 -149
  178. package/src/pb-edit-app.js +471 -240
  179. package/src/pb-edit-xml.js +101 -98
  180. package/src/pb-events.js +126 -107
  181. package/src/pb-facs-link.js +130 -101
  182. package/src/pb-facsimile.js +494 -410
  183. package/src/pb-fetch.js +389 -0
  184. package/src/pb-formula.js +152 -154
  185. package/src/pb-geolocation.js +130 -132
  186. package/src/pb-grid-action.js +59 -56
  187. package/src/pb-grid.js +388 -228
  188. package/src/pb-highlight.js +142 -142
  189. package/src/pb-hotkeys.js +40 -42
  190. package/src/pb-i18n.js +115 -127
  191. package/src/pb-icon-button.js +108 -0
  192. package/src/pb-icon.js +283 -0
  193. package/src/pb-image-strip.js +85 -79
  194. package/src/pb-lang.js +142 -57
  195. package/src/pb-leaflet-map.js +551 -483
  196. package/src/pb-link.js +132 -126
  197. package/src/pb-load.js +495 -428
  198. package/src/pb-login.js +303 -248
  199. package/src/pb-manage-odds.js +384 -338
  200. package/src/pb-map-icon.js +90 -90
  201. package/src/pb-map-layer.js +86 -86
  202. package/src/pb-markdown.js +107 -110
  203. package/src/pb-media-query.js +75 -73
  204. package/src/pb-mei.js +523 -303
  205. package/src/pb-message.js +144 -98
  206. package/src/pb-mixin.js +268 -265
  207. package/src/pb-navigation.js +83 -96
  208. package/src/pb-observable.js +39 -39
  209. package/src/pb-odd-editor.js +1209 -948
  210. package/src/pb-odd-elementspec-editor.js +375 -310
  211. package/src/pb-odd-model-editor.js +1189 -941
  212. package/src/pb-odd-parameter-editor.js +269 -170
  213. package/src/pb-odd-rendition-editor.js +184 -131
  214. package/src/pb-page.js +451 -422
  215. package/src/pb-paginate.js +260 -178
  216. package/src/pb-panel.js +217 -183
  217. package/src/pb-popover-themes.js +16 -9
  218. package/src/pb-popover.js +297 -288
  219. package/src/pb-print-preview.js +128 -128
  220. package/src/pb-progress.js +52 -52
  221. package/src/pb-repeat.js +141 -108
  222. package/src/pb-restricted.js +85 -78
  223. package/src/pb-search.js +258 -230
  224. package/src/pb-select-feature.js +210 -126
  225. package/src/pb-select-odd.js +184 -118
  226. package/src/pb-select-template.js +113 -78
  227. package/src/pb-select.js +330 -229
  228. package/src/pb-split-list.js +181 -176
  229. package/src/pb-svg.js +81 -80
  230. package/src/pb-table-column.js +55 -55
  231. package/src/pb-table-grid.js +334 -205
  232. package/src/pb-tabs.js +238 -61
  233. package/src/pb-tify.js +3331 -126
  234. package/src/pb-timeline.js +394 -255
  235. package/src/pb-toggle-feature.js +196 -188
  236. package/src/pb-upload.js +201 -176
  237. package/src/pb-version.js +22 -34
  238. package/src/pb-view-annotate.js +138 -102
  239. package/src/pb-view.js +1722 -1272
  240. package/src/pb-zoom.js +144 -46
  241. package/src/search-result-service.js +256 -223
  242. package/src/seed-element.js +14 -22
  243. package/src/settings.js +4 -4
  244. package/src/theming.js +98 -91
  245. package/src/urls.js +403 -289
  246. package/src/utils.js +53 -51
  247. package/vite.config.js +86 -0
  248. package/css/pb-styles.css +0 -51
  249. package/dist/iron-form-3b8dcaa7.js +0 -210
  250. package/dist/jinn-codemirror-da0e2d1f.js +0 -1
  251. package/dist/paper-checkbox-515a5284.js +0 -1597
  252. package/dist/paper-icon-button-b1d31571.js +0 -398
  253. package/dist/paper-listbox-a3b7175c.js +0 -1265
  254. package/dist/pb-i18n-0611135a.js +0 -1
  255. package/dist/pb-mixin-b1caa22e.js +0 -158
  256. package/dist/polymer-hack.js +0 -1
  257. package/dist/vaadin-element-mixin-fe4a4883.js +0 -527
  258. package/lib/Control.Geocoder.min.js +0 -2
  259. package/lib/Control.Geocoder.min.js.map +0 -1
  260. package/src/assets/pb-styles.css +0 -51
  261. package/src/pb-light-dom.js +0 -41
  262. package/src/polymer-hack.js +0 -6
package/src/pb-i18n.js CHANGED
@@ -1,167 +1,155 @@
1
- import { LitElement, html } from 'lit-element';
2
- import { directive, NodePart, AttributePart } from "lit-html";
3
-
4
- // Cache created lit-html parts, so we can update translations
5
- const partCache = new Map();
1
+ import { LitElement, html } from 'lit';
6
2
 
7
3
  // The currently used i18next translation function
8
- let _translate;
4
+ let _translateFn;
5
+ // Bump whenever the translation function or resources change
6
+ let _i18nVersion = 0;
7
+ // Shared, tiny in-memory cache per-version (key+options)
8
+ const _i18nCache = new Map();
9
9
 
10
- /**
10
+ /**
11
11
  * Called by pb-page before the first pb-i18n-update
12
12
  * to make sure the translation function is set.
13
13
  */
14
14
  export function initTranslation(translate) {
15
- _translate = translate;
16
- }
17
-
18
- function isConnected(part) {
19
- if (part instanceof NodePart) {
20
- return part.startNode.isConnected;
21
-
22
- }
23
- if (part instanceof AttributePart) {
24
- return part.committer.element.isConnected;
25
-
26
- }
27
- return part.element.isConnected;
15
+ _translateFn = translate;
16
+ _i18nVersion++;
17
+ _i18nCache.clear();
28
18
  }
29
19
 
30
- function removeDisconnectedParts() {
31
- Object.keys(partCache).forEach((part) => {
32
- if (!isConnected(part)) {
33
- partCache.delete(part);
34
- }
35
- });
36
- }
37
-
38
- function whenIdle(cb) {
39
- "requestIdleCallback" in window ? window.requestIdleCallback(cb) : setTimeout(cb)
40
- }
41
-
42
- function updatePart(part, cb) {
43
-
44
- // Grab the new value
45
- const newValue = cb();
46
-
47
- // Only set the value if it has changed
48
- if (part.value === newValue) {
49
- return;
50
- }
51
-
52
- // Set the new value
53
- part.setValue(newValue);
54
- part.commit();
55
- }
56
-
57
- function updateParts(options) {
58
- _translate = options.t;
59
- partCache.forEach((cb, part) => {
60
- if (isConnected(part)) {
61
- updatePart(part, cb);
62
- }
63
- });
64
- }
20
+ // No-op helpers kept for backward compatibility of API surface
65
21
 
66
22
  /**
67
23
  * Translate the given string using i18n.
68
- *
24
+ *
69
25
  * @param {String} key The key to translate
70
26
  * @param {Object} [value] Optional object containing interpolation values
71
- * @returns
27
+ * @returns {string}
72
28
  */
73
29
  export function get(key, value) {
74
- if (_translate) {
75
- return _translate(key, value);
76
- }
77
- return key;
30
+ if (_translateFn) return _translateFn(key, value);
31
+ return key;
78
32
  }
79
33
 
80
- export const langChanged = directive((cb) => (part) => {
81
- partCache.set(part, cb);
82
- updatePart(part, cb);
83
- });
84
-
85
34
  /**
86
35
  * lit-html directive to translate a given key into the target language
87
36
  * using i18next.
88
- *
89
- * @param {String} key The key to translate
90
- * @param {Object} [value] Optional object containing interpolation values
37
+ *
38
+ * @param {String} key
39
+ * @param {Object} [value]
91
40
  */
92
- export const translate = (key, value) => langChanged(() => get(key, value));
93
-
94
- document.addEventListener('pb-i18n-update', (ev) => {
95
- updateParts(ev.detail);
96
- });
97
-
98
- // start a background task to garbage collect parts
99
- setInterval(() => whenIdle(() => removeDisconnectedParts()), 1000 * 60);
41
+ export const translate = (key, value) => get(key, value);
42
+
43
+ // Keep _translateFn in sync on updates (guard for non-browser envs)
44
+ if (typeof document !== 'undefined') {
45
+ document.addEventListener('pb-i18n-update', ev => {
46
+ const next = ev.detail?.t;
47
+ if (next && next !== _translateFn) {
48
+ _translateFn = next;
49
+ }
50
+ // Invalidate cache regardless (resources may have changed)
51
+ _i18nVersion++;
52
+ _i18nCache.clear();
53
+ });
54
+ }
100
55
 
101
56
  /**
102
57
  * Insert translated text somewhere on an HTML page. If no translation is found,
103
58
  * display the contained content.
104
59
  */
105
60
  export class PbI18n extends LitElement {
106
- static get properties() {
107
- return {
108
- ...super.properties,
109
- /**
110
- * The i18n key to use for looking up the translation.
111
- */
112
- key: {
113
- type: String
114
- },
115
- /**
116
- * Optional interpolation parameters to be passed to the
117
- * translation function
118
- */
119
- options: {
120
- type: Object
121
- },
122
- _translated: {
123
- type: String
124
- }
125
- };
61
+ static get properties() {
62
+ return {
63
+ ...super.properties,
64
+ /**
65
+ * The i18n key to use for looking up the translation.
66
+ */
67
+ key: { type: String },
68
+ /**
69
+ * Optional interpolation parameters to be passed to the
70
+ * translation function
71
+ */
72
+ options: { type: Object },
73
+ _translated: { type: String },
74
+ };
75
+ }
76
+
77
+ constructor() {
78
+ super();
79
+ this.key = 'missing-key';
80
+ this.options = null;
81
+ this._translated = null;
82
+ this._fallback = '';
83
+ // stable handler so we can remove it on disconnect
84
+ this._onI18nUpdate = () => this._recompute();
85
+ this._recomputeTimer = null;
86
+ }
87
+
88
+ connectedCallback() {
89
+ super.connectedCallback();
90
+
91
+ // Capture initial fallback then clear host content so Lit fully controls light DOM
92
+ if (!this._capturedFallback) {
93
+ this._fallback = this.innerHTML;
94
+ this._capturedFallback = true;
126
95
  }
96
+ this.innerHTML = '';
127
97
 
128
- constructor() {
129
- super();
130
- this.key = 'missing-key';
131
- this._options = null;
132
- this._translated = null;
98
+ if (typeof document !== 'undefined') {
99
+ document.addEventListener('pb-i18n-update', this._onI18nUpdate);
133
100
  }
134
101
 
135
- connectedCallback() {
136
- super.connectedCallback();
137
-
138
- this._fallback = this.innerHTML;
139
-
140
- document.addEventListener('pb-i18n-update', this._translate.bind(this));
102
+ this._scheduleRecompute();
103
+ }
141
104
 
142
- this._translate();
105
+ disconnectedCallback() {
106
+ if (typeof document !== 'undefined') {
107
+ document.removeEventListener('pb-i18n-update', this._onI18nUpdate);
143
108
  }
144
-
145
- set options(value) {
146
- this._options = value;
147
- this._translate();
109
+ if (this._recomputeTimer) {
110
+ clearTimeout(this._recomputeTimer);
111
+ this._recomputeTimer = null;
148
112
  }
113
+ super.disconnectedCallback();
114
+ }
149
115
 
150
- _translate() {
151
- const transl = get(this.key, this._options);
152
- if (transl && transl !== this.key) {
153
- this._translated = transl;
154
- } else {
155
- this._translated = null;
156
- }
116
+ updated(changed) {
117
+ if (changed.has('key') || changed.has('options')) {
118
+ this._scheduleRecompute();
157
119
  }
158
-
159
- render() {
160
- return this._translated ? this._translated : this._fallback;
120
+ }
121
+
122
+ _scheduleRecompute() {
123
+ if (this._recomputeTimer) return;
124
+ // Small debounce to coalesce multiple updates and event bursts
125
+ this._recomputeTimer = setTimeout(() => {
126
+ this._recomputeTimer = null;
127
+ this._recompute();
128
+ }, 20);
129
+ }
130
+
131
+ _recompute() {
132
+ const opts = this.options || {};
133
+ const cacheKey = `${_i18nVersion}::${this.key}::${JSON.stringify(opts)}`;
134
+ if (_i18nCache.has(cacheKey)) {
135
+ this._translated = _i18nCache.get(cacheKey);
136
+ return;
161
137
  }
162
138
 
163
- createRenderRoot() {
164
- return this;
165
- }
139
+ const result = get(this.key, opts);
140
+ const value = result && result !== this.key ? result : null;
141
+ _i18nCache.set(cacheKey, value);
142
+ this._translated = value;
143
+ }
144
+
145
+ render() {
146
+ // Return text (not HTML) so translations aren’t treated as markup
147
+ return this._translated ?? this._fallback;
148
+ }
149
+
150
+ // light DOM rendering (as before)
151
+ createRenderRoot() {
152
+ return this;
153
+ }
166
154
  }
167
- customElements.define('pb-i18n', PbI18n);
155
+ customElements.define('pb-i18n', PbI18n);
@@ -0,0 +1,108 @@
1
+ import { LitElement, html, css } from 'lit';
2
+ import './pb-icon.js';
3
+
4
+ const normalizeLabel = (value = '') => value.replace(/[-_]/g, ' ').trim();
5
+
6
+ export class PbIconButton extends LitElement {
7
+ static properties = {
8
+ icon: { type: String },
9
+ disabled: { type: Boolean, reflect: true },
10
+ label: { type: String },
11
+ toggles: { type: Boolean },
12
+ active: { type: Boolean, reflect: true },
13
+ };
14
+
15
+ constructor() {
16
+ super();
17
+ this.icon = '';
18
+ this.disabled = false;
19
+ this.label = '';
20
+ this.toggles = false;
21
+ this.active = false;
22
+ }
23
+
24
+ render() {
25
+ const hostTitle = this.title || '';
26
+ const iconLabel = this.icon
27
+ ? normalizeLabel(this.icon.includes(':') ? this.icon.split(':').pop() : this.icon)
28
+ : '';
29
+ const ariaLabel = this.label || hostTitle || iconLabel;
30
+
31
+ return html`
32
+ <button
33
+ class="pb-button pb-button--icon"
34
+ type="button"
35
+ title=${hostTitle}
36
+ aria-label=${ariaLabel}
37
+ ?disabled=${this.disabled}
38
+ aria-pressed=${this.toggles ? String(this.active) : undefined}
39
+ @click=${this._handleClick}
40
+ >
41
+ ${this.icon
42
+ ? html`<pb-icon icon="${this.icon}" decorative></pb-icon>`
43
+ : html`<slot></slot>`}
44
+ </button>
45
+ `;
46
+ }
47
+
48
+ _handleClick(event) {
49
+ if (this.disabled) {
50
+ event.preventDefault();
51
+ event.stopImmediatePropagation();
52
+ return;
53
+ }
54
+
55
+ if (this.toggles) {
56
+ this.active = !this.active;
57
+ this.dispatchEvent(
58
+ new CustomEvent('active-changed', {
59
+ detail: { value: this.active },
60
+ bubbles: true,
61
+ composed: true,
62
+ }),
63
+ );
64
+ }
65
+ }
66
+
67
+ static styles = css`
68
+ :host {
69
+ display: inline-flex;
70
+ box-sizing: border-box;
71
+ --pb-icon-button-size: 40px;
72
+ }
73
+
74
+ button {
75
+ width: var(--pb-icon-button-size);
76
+ height: var(--pb-icon-button-size);
77
+ border-radius: 999px;
78
+ border: none;
79
+ background: transparent;
80
+ color: inherit;
81
+ padding: 0;
82
+ cursor: pointer;
83
+ display: inline-flex;
84
+ align-items: center;
85
+ justify-content: center;
86
+ transition: background 0.2s ease-in-out;
87
+ }
88
+
89
+ button:hover:not(:disabled),
90
+ button:focus-visible {
91
+ background: var(--pb-icon-button-hover-background, rgba(0, 0, 0, 0.05));
92
+ outline: none;
93
+ }
94
+
95
+ button:disabled {
96
+ opacity: 0.4;
97
+ cursor: not-allowed;
98
+ }
99
+
100
+ :host([active]) button {
101
+ background: var(--pb-icon-button-active-background, rgba(0, 0, 0, 0.08));
102
+ }
103
+ `;
104
+ }
105
+
106
+ if (!customElements.get('pb-icon-button')) {
107
+ customElements.define('pb-icon-button', PbIconButton);
108
+ }
package/src/pb-icon.js ADDED
@@ -0,0 +1,283 @@
1
+ import { LitElement, html, css } from 'lit';
2
+
3
+ if (!customElements.__pbDefineGuard) {
4
+ const originalDefine = customElements.define.bind(customElements);
5
+ customElements.define = (name, constructor, options) => {
6
+ if (customElements.get(name)) {
7
+ return;
8
+ }
9
+ originalDefine(name, constructor, options);
10
+ };
11
+ customElements.__pbDefineGuard = true;
12
+ }
13
+
14
+ const polymerIconMap = {
15
+ 'icons:file-upload': 'upload',
16
+ 'icons:file-download': 'download',
17
+ 'icons:cloud-download': 'download',
18
+ 'icons:clear': 'close',
19
+ 'icons:check': 'check',
20
+ 'icons:error-outline': 'warning',
21
+ 'icons:folder-open': 'folder-open',
22
+ 'icons:arrow-upward': 'arrow-upward',
23
+ 'icons:arrow-forward': 'chevron-right',
24
+ 'icons:arrow-back': 'chevron-left',
25
+ 'icons:chevron-right': 'chevron-right',
26
+ 'icons:chevron-left': 'chevron-left',
27
+ 'icons:open-in-new': 'open-in-new',
28
+ 'icons:flag': 'flag',
29
+ 'icons:code': 'code',
30
+ 'icons:hourglass-empty': 'clock',
31
+ 'icons:delete': 'delete',
32
+ 'icons:create': 'edit',
33
+ 'icons:expand-more': 'expand-more',
34
+ 'icons:expand-less': 'expand-less',
35
+ 'icons:view-list': 'view-list',
36
+ 'icons:zoom-in': 'zoom-in',
37
+ 'icons:zoom-out': 'zoom-out',
38
+ 'icons:content-copy': 'copy',
39
+ 'content-copy': 'copy',
40
+ 'icons:content-paste': 'content-paste',
41
+ 'content-paste': 'content-paste',
42
+ 'icons:arrow-downward': 'arrow-downward',
43
+ 'icons:refresh': 'refresh',
44
+ 'icons:save': 'save',
45
+ };
46
+
47
+ /**
48
+ * A versatile icon component that replaces iron-icon and iron-icons.
49
+ * Supports both SVG sprite references and inline SVG content.
50
+ *
51
+ * @example
52
+ * <!-- Using icon name (requires icon sprite) -->
53
+ * <pb-icon icon="expand-more"></pb-icon>
54
+ *
55
+ * <!-- Using external sprite (default dist/images/icons.svg) -->
56
+ * <pb-icon icon="expand-more" sprite="dist/images/icons.svg"></pb-icon>
57
+ *
58
+ * <!-- Using inline SVG -->
59
+ * <pb-icon>
60
+ * <svg viewBox="0 0 24 24">
61
+ * <path d="M7 10l5 5 5-5z"/>
62
+ * </svg>
63
+ * </pb-icon>
64
+ *
65
+ * @slot - Inline SVG content when not using icon name
66
+ * @cssprop [--pb-icon-color] - Icon color (defaults to currentColor)
67
+ * @cssprop [--pb-icon-size] - Icon size (defaults to 1em)
68
+ */
69
+ export class PbIcon extends LitElement {
70
+ static get properties() {
71
+ return {
72
+ /**
73
+ * Icon name to display from the icon sprite.
74
+ * If not provided, expects SVG content in the default slot.
75
+ */
76
+ icon: { type: String },
77
+
78
+ /**
79
+ * Path or URL to the SVG sprite file. Optional.
80
+ * If not specified, defaults to 'dist/images/icons.svg'.
81
+ * If set to a string starting with '#', uses inline sprite.
82
+ */
83
+ sprite: { type: String },
84
+ /**
85
+ * Icon size. Can be a preset (xs, sm, md, lg, xl) or a custom size.
86
+ */
87
+ size: { type: String },
88
+
89
+ /**
90
+ * ARIA label for accessibility. Auto-generated from icon name if not provided.
91
+ */
92
+ label: { type: String },
93
+
94
+ /**
95
+ * Whether the icon is decorative (hidden from screen readers).
96
+ */
97
+ decorative: { type: Boolean },
98
+ };
99
+ }
100
+
101
+ constructor() {
102
+ super();
103
+ this.icon = '';
104
+ this.size = '';
105
+ this.label = '';
106
+ this.decorative = false;
107
+ // Default sprite path: try to determine the correct base URL
108
+ try {
109
+ const isDev =
110
+ (typeof import.meta !== 'undefined' && import.meta.env && import.meta.env.DEV) ||
111
+ (typeof location !== 'undefined' && /localhost|127\.0\.0\.1/.test(location.hostname));
112
+
113
+ if (isDev) {
114
+ // In dev mode, check if we're running in TEI Publisher context
115
+ const currentHost = window.location.hostname;
116
+ const currentPort = window.location.port;
117
+
118
+ // If we're on the TEI Publisher port (8080), use the app's resources path
119
+ if (currentPort === '8080' || currentHost.includes('tei-publisher')) {
120
+ this.sprite = '/exist/apps/tei-publisher/resources/images/icons.svg';
121
+ } else {
122
+ // Otherwise use the Vite dev server path
123
+ this.sprite = '/images/icons.svg';
124
+ }
125
+ } else {
126
+ this.sprite = '/dist/images/icons.svg';
127
+ }
128
+ } catch (_) {
129
+ this.sprite = '/dist/images/icons.svg';
130
+ }
131
+ }
132
+
133
+ render() {
134
+ const sizeClass = this._getSizeClass();
135
+ const ariaLabel = this._getAriaLabel();
136
+
137
+ return html`
138
+ <span
139
+ class="pb-icon ${sizeClass}"
140
+ role="img"
141
+ aria-label="${ariaLabel}"
142
+ aria-hidden="${this.decorative ? 'true' : 'false'}"
143
+ >
144
+ ${this.icon ? this._renderSpriteIcon() : this._renderSlotIcon()}
145
+ </span>
146
+ `;
147
+ }
148
+
149
+ _renderSpriteIcon() {
150
+ const iconName = this._normalizeIconName(this.icon);
151
+ if (!iconName) {
152
+ return html``;
153
+ }
154
+ const href =
155
+ this.sprite && !this.sprite.startsWith('#')
156
+ ? `${this.sprite}#icon-${iconName}`
157
+ : `#icon-${iconName}`;
158
+ return html`
159
+ <svg
160
+ width="1em"
161
+ height="1em"
162
+ viewBox="0 0 24 24"
163
+ aria-hidden="${this.decorative ? 'true' : 'false'}"
164
+ >
165
+ <use href="${href}"></use>
166
+ </svg>
167
+ `;
168
+ }
169
+
170
+ _renderSlotIcon() {
171
+ return html`<slot></slot>`;
172
+ }
173
+
174
+ _normalizeIconName(name) {
175
+ if (!name) return '';
176
+ const lower = name.toLowerCase();
177
+ if (polymerIconMap[lower]) {
178
+ return polymerIconMap[lower];
179
+ }
180
+ if (lower.includes(':')) {
181
+ return lower.split(':').pop();
182
+ }
183
+ return lower;
184
+ }
185
+
186
+ _getSizeClass() {
187
+ if (!this.size) return '';
188
+
189
+ const presetSizes = ['xs', 'sm', 'md', 'lg', 'xl'];
190
+ if (presetSizes.includes(this.size)) {
191
+ return `pb-icon--${this.size}`;
192
+ }
193
+
194
+ // Custom size - set CSS custom property
195
+ this.style.setProperty('--pb-icon-size', this.size);
196
+ return '';
197
+ }
198
+
199
+ _getAriaLabel() {
200
+ if (this.decorative) return '';
201
+ if (this.label) return this.label;
202
+ if (this.icon) {
203
+ const normalized = this._normalizeIconName(this.icon);
204
+ return normalized.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
205
+ }
206
+ return 'Icon';
207
+ }
208
+
209
+ static get styles() {
210
+ return css`
211
+ :host {
212
+ display: inline-flex;
213
+ align-items: center;
214
+ justify-content: center;
215
+ }
216
+
217
+ .pb-icon {
218
+ display: inline-flex;
219
+ align-items: center;
220
+ justify-content: center;
221
+ width: var(--pb-icon-size, 1em);
222
+ height: var(--pb-icon-size, 1em);
223
+ color: var(--pb-icon-color, currentColor);
224
+ fill: currentColor;
225
+ flex-shrink: 0;
226
+ user-select: none;
227
+ line-height: 1;
228
+ }
229
+
230
+ .pb-icon svg {
231
+ width: 100%;
232
+ height: 100%;
233
+ fill: inherit;
234
+ }
235
+
236
+ /* Size variants */
237
+ .pb-icon--xs {
238
+ font-size: 12px;
239
+ width: 12px;
240
+ height: 12px;
241
+ }
242
+
243
+ .pb-icon--sm {
244
+ font-size: 16px;
245
+ width: 16px;
246
+ height: 16px;
247
+ }
248
+
249
+ .pb-icon--md {
250
+ font-size: 20px;
251
+ width: 20px;
252
+ height: 20px;
253
+ }
254
+
255
+ .pb-icon--lg {
256
+ font-size: 24px;
257
+ width: 24px;
258
+ height: 24px;
259
+ }
260
+
261
+ .pb-icon--xl {
262
+ font-size: 32px;
263
+ width: 32px;
264
+ height: 32px;
265
+ }
266
+
267
+ /* Ensure slotted SVGs are properly sized */
268
+ ::slotted(svg) {
269
+ width: 100%;
270
+ height: 100%;
271
+ fill: currentColor;
272
+ }
273
+ `;
274
+ }
275
+ }
276
+
277
+ if (!customElements.get('pb-icon')) {
278
+ customElements.define('pb-icon', PbIcon);
279
+ }
280
+ if (!customElements.get('iron-icon')) {
281
+ class PbIronIcon extends PbIcon {}
282
+ customElements.define('iron-icon', PbIronIcon);
283
+ }