@vaadin/icon 24.4.0-dev.b3e1d14600 → 24.4.0-rc2

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.
@@ -1,20 +1,18 @@
1
1
  /**
2
2
  * @license
3
- * Copyright (c) 2021 - 2023 Vaadin Ltd.
3
+ * Copyright (c) 2021 - 2024 Vaadin Ltd.
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
+ import './vaadin-iconset.js';
6
7
  import { html, PolymerElement } from '@polymer/polymer/polymer-element.js';
7
8
  import { ControllerMixin } from '@vaadin/component-base/src/controller-mixin.js';
8
9
  import { defineCustomElement } from '@vaadin/component-base/src/define.js';
9
10
  import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
10
- import { SlotStylesMixin } from '@vaadin/component-base/src/slot-styles-mixin.js';
11
- import { TooltipController } from '@vaadin/component-base/src/tooltip-controller.js';
12
- import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
13
- import { IconFontSizeMixin } from './vaadin-icon-font-size-mixin.js';
14
- import { ensureSvgLiteral, renderSvg, unsafeSvgLiteral } from './vaadin-icon-svg.js';
15
- import { Iconset } from './vaadin-iconset.js';
11
+ import { registerStyles, ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
12
+ import { IconMixin } from './vaadin-icon-mixin.js';
13
+ import { iconStyles } from './vaadin-icon-styles.js';
16
14
 
17
- const srcCache = new Map();
15
+ registerStyles('vaadin-icon', iconStyles, { moduleId: 'vaadin-icon-styles' });
18
16
 
19
17
  /**
20
18
  * `<vaadin-icon>` is a Web Component for displaying SVG icons.
@@ -57,55 +55,14 @@ const srcCache = new Map();
57
55
  *
58
56
  * @customElement
59
57
  * @extends HTMLElement
58
+ * @mixes IconMixin
60
59
  * @mixes ControllerMixin
61
60
  * @mixes ThemableMixin
62
61
  * @mixes ElementMixin
63
- * @mixes SlotStylesMixin
64
- * @mixes IconFontSizeMixin
65
62
  */
66
- class Icon extends ThemableMixin(ElementMixin(ControllerMixin(SlotStylesMixin(IconFontSizeMixin(PolymerElement))))) {
63
+ class Icon extends IconMixin(ControllerMixin(ElementMixin(ThemableMixin(PolymerElement)))) {
67
64
  static get template() {
68
65
  return html`
69
- <style>
70
- :host {
71
- display: inline-flex;
72
- justify-content: center;
73
- align-items: center;
74
- box-sizing: border-box;
75
- vertical-align: middle;
76
- width: 24px;
77
- height: 24px;
78
- fill: currentColor;
79
- container-type: size;
80
- }
81
-
82
- :host::after,
83
- :host::before {
84
- line-height: 1;
85
- font-size: 100cqh;
86
- -webkit-font-smoothing: antialiased;
87
- text-rendering: optimizeLegibility;
88
- -moz-osx-font-smoothing: grayscale;
89
- }
90
-
91
- :host([hidden]) {
92
- display: none !important;
93
- }
94
-
95
- svg {
96
- display: block;
97
- width: 100%;
98
- height: 100%;
99
- }
100
-
101
- :host(:is([icon-class], [font-icon-content])) svg {
102
- display: none;
103
- }
104
-
105
- :host([font-icon-content])::before {
106
- content: attr(font-icon-content);
107
- }
108
- </style>
109
66
  <svg
110
67
  version="1.1"
111
68
  xmlns="http://www.w3.org/2000/svg"
@@ -132,350 +89,6 @@ class Icon extends ThemableMixin(ElementMixin(ControllerMixin(SlotStylesMixin(Ic
132
89
  static get is() {
133
90
  return 'vaadin-icon';
134
91
  }
135
-
136
- static get properties() {
137
- return {
138
- /**
139
- * The name of the icon to use. The name should be of the form:
140
- * `iconset_name:icon_name`. When using `vaadin-icons` it is possible
141
- * to omit the first part and only use `icon_name` as a value.
142
- *
143
- * Setting the `icon` property updates the `svg` and `size` based on the
144
- * values provided by the corresponding `vaadin-iconset` element.
145
- *
146
- * See also [`name`](#/elements/vaadin-iconset#property-name) property of `vaadin-iconset`.
147
- *
148
- * @attr {string} icon
149
- * @type {string}
150
- */
151
- icon: {
152
- type: String,
153
- reflectToAttribute: true,
154
- observer: '__iconChanged',
155
- },
156
-
157
- /**
158
- * The SVG icon wrapped in a Lit template literal.
159
- */
160
- svg: {
161
- type: Object,
162
- },
163
-
164
- /**
165
- * The SVG source to be loaded as the icon. It can be:
166
- * - an URL to a file containing the icon
167
- * - an URL in the format "/path/to/file.svg#objectID", where the "objectID" refers to an ID attribute contained
168
- * inside the SVG referenced by the path. Note that the file needs to follow the same-origin policy.
169
- * - a string in the format "data:image/svg+xml,<svg>...</svg>". You may need to use the "encodeURIComponent"
170
- * function for the SVG content passed
171
- *
172
- * @type {string}
173
- */
174
- src: {
175
- type: String,
176
- },
177
-
178
- /**
179
- * The symbol identifier that references an ID of an element contained in the SVG element assigned to the
180
- * `src` property
181
- *
182
- * @type {string}
183
- */
184
- symbol: {
185
- type: String,
186
- },
187
-
188
- /**
189
- * Class names defining an icon font and/or a specific glyph inside an icon font.
190
- *
191
- * Example: "fa-solid fa-user"
192
- *
193
- * @attr {string} icon-class
194
- * @type {string}
195
- */
196
- iconClass: {
197
- type: String,
198
- reflectToAttribute: true,
199
- },
200
-
201
- /**
202
- * A hexadecimal code point that specifies a glyph from an icon font.
203
- *
204
- * Example: "e001"
205
- *
206
- * @type {string}
207
- */
208
- char: {
209
- type: String,
210
- },
211
-
212
- /**
213
- * A ligature name that specifies an icon from an icon font with support for ligatures.
214
- *
215
- * Example: "home".
216
- *
217
- * @type {string}
218
- */
219
- ligature: {
220
- type: String,
221
- },
222
-
223
- /**
224
- * The font family to use for the font icon.
225
- *
226
- * @type {string}
227
- */
228
- fontFamily: {
229
- type: String,
230
- observer: '__fontFamilyChanged',
231
- },
232
-
233
- /**
234
- * The size of an icon, used to set the `viewBox` attribute.
235
- */
236
- size: {
237
- type: Number,
238
- value: 24,
239
- },
240
-
241
- /** @private */
242
- __defaultPAR: {
243
- type: String,
244
- value: 'xMidYMid meet',
245
- },
246
-
247
- /** @private */
248
- __preserveAspectRatio: String,
249
-
250
- /** @private */
251
- __useRef: Object,
252
-
253
- /** @private */
254
- __svgElement: String,
255
-
256
- /** @private */
257
- __viewBox: String,
258
-
259
- /** @private */
260
- __fill: String,
261
-
262
- /** @private */
263
- __stroke: String,
264
-
265
- /** @private */
266
- __strokeWidth: String,
267
-
268
- /** @private */
269
- __strokeLinecap: String,
270
-
271
- /** @private */
272
- __strokeLinejoin: String,
273
- };
274
- }
275
-
276
- static get observers() {
277
- return ['__svgChanged(svg, __svgElement)', '__fontChanged(iconClass, char, ligature)', '__srcChanged(src, symbol)'];
278
- }
279
-
280
- static get observedAttributes() {
281
- return [...super.observedAttributes, 'class'];
282
- }
283
-
284
- constructor() {
285
- super();
286
-
287
- this.__fetch = fetch.bind(window);
288
- }
289
-
290
- /** @protected */
291
- get slotStyles() {
292
- const tag = this.localName;
293
- return [
294
- `
295
- ${tag}[icon-class] {
296
- display: inline-flex;
297
- vertical-align: middle;
298
- font-size: inherit;
299
- }
300
- `,
301
- ];
302
- }
303
-
304
- /** @private */
305
- get __iconClasses() {
306
- return this.iconClass ? this.iconClass.split(' ') : [];
307
- }
308
-
309
- /** @protected */
310
- ready() {
311
- super.ready();
312
- this.__svgElement = this.shadowRoot.querySelector('#svg-group');
313
-
314
- this._tooltipController = new TooltipController(this);
315
- this.addController(this._tooltipController);
316
- }
317
-
318
- /** @protected */
319
- connectedCallback() {
320
- super.connectedCallback();
321
-
322
- Iconset.attachedIcons.add(this);
323
- }
324
-
325
- /** @protected */
326
- disconnectedCallback() {
327
- super.disconnectedCallback();
328
-
329
- Iconset.attachedIcons.delete(this);
330
- }
331
-
332
- /** @protected */
333
- _applyIcon() {
334
- const { preserveAspectRatio, svg, size, viewBox } = Iconset.getIconSvg(this.icon);
335
-
336
- if (viewBox) {
337
- this.__viewBox = viewBox;
338
- }
339
-
340
- if (preserveAspectRatio) {
341
- this.__preserveAspectRatio = preserveAspectRatio;
342
- }
343
-
344
- if (size && size !== this.size) {
345
- this.size = size;
346
- }
347
-
348
- this.svg = svg;
349
- }
350
-
351
- /** @private */
352
- __iconChanged(icon) {
353
- if (icon) {
354
- this._applyIcon();
355
- } else {
356
- this.svg = ensureSvgLiteral(null);
357
- }
358
- }
359
-
360
- /** @private */
361
- async __srcChanged(src, symbol) {
362
- if (!src) {
363
- this.svg = null;
364
- return;
365
- }
366
-
367
- // Need to add the "icon" attribute to avoid issues as described in
368
- // https://github.com/vaadin/web-components/issues/6301
369
- this.icon = '';
370
-
371
- if (!src.startsWith('data:') && (symbol || src.includes('#'))) {
372
- const [path, iconId] = src.split('#');
373
- this.__useRef = `${path}#${symbol || iconId}`;
374
- } else {
375
- try {
376
- if (!srcCache.has(src)) {
377
- srcCache.set(
378
- src,
379
- this.__fetch(src, { mode: 'cors' }).then((data) => {
380
- if (!data.ok) {
381
- throw new Error('Error loading icon');
382
- }
383
- return data.text();
384
- }),
385
- );
386
- }
387
- const svgData = await srcCache.get(src);
388
-
389
- if (!Icon.__domParser) {
390
- Icon.__domParser = new DOMParser();
391
- }
392
- const parsedResponse = Icon.__domParser.parseFromString(svgData, 'text/html');
393
-
394
- const svgElement = parsedResponse.querySelector('svg');
395
- if (!svgElement) {
396
- throw new Error(`SVG element not found on path: ${src}`);
397
- }
398
-
399
- this.svg = unsafeSvgLiteral(svgElement.innerHTML);
400
-
401
- if (symbol) {
402
- this.__useRef = `#${symbol}`;
403
- }
404
-
405
- this.__viewBox = svgElement.getAttribute('viewBox');
406
- this.__fill = svgElement.getAttribute('fill');
407
- this.__stroke = svgElement.getAttribute('stroke');
408
- this.__strokeWidth = svgElement.getAttribute('stroke-width');
409
- this.__strokeLinecap = svgElement.getAttribute('stroke-linecap');
410
- this.__strokeLinejoin = svgElement.getAttribute('stroke-linejoin');
411
- } catch (e) {
412
- console.error(e);
413
- this.svg = null;
414
- }
415
- }
416
- }
417
-
418
- /** @private */
419
- __svgChanged(svg, svgElement) {
420
- if (!svgElement) {
421
- return;
422
- }
423
-
424
- renderSvg(svg, svgElement);
425
- }
426
-
427
- /** @private */
428
- __computePAR(defaultPAR, preserveAspectRatio) {
429
- return preserveAspectRatio || defaultPAR;
430
- }
431
-
432
- /** @private */
433
- __computeVisibility(__useRef) {
434
- return __useRef ? 'visible' : 'hidden';
435
- }
436
-
437
- /** @private */
438
- __computeViewBox(size, viewBox) {
439
- return viewBox || `0 0 ${size} ${size}`;
440
- }
441
-
442
- /** @private */
443
- __fontChanged(iconClass, char, ligature) {
444
- this.classList.remove(...(this.__addedIconClasses || []));
445
- if (iconClass) {
446
- this.__addedIconClasses = [...this.__iconClasses];
447
- this.classList.add(...this.__addedIconClasses);
448
- }
449
-
450
- if (char) {
451
- this.setAttribute('font-icon-content', char.length > 1 ? String.fromCodePoint(parseInt(char, 16)) : char);
452
- } else if (ligature) {
453
- this.setAttribute('font-icon-content', ligature);
454
- } else {
455
- this.removeAttribute('font-icon-content');
456
- }
457
-
458
- if ((iconClass || char || ligature) && !this.icon) {
459
- // The "icon" attribute needs to be set on the host also when using font icons
460
- // to avoid issues such as https://github.com/vaadin/web-components/issues/6301
461
- this.icon = '';
462
- }
463
- }
464
-
465
- /** @protected */
466
- attributeChangedCallback(name, oldValue, newValue) {
467
- super.attributeChangedCallback(name, oldValue, newValue);
468
-
469
- // Make sure class list always contains all the font class names
470
- if (name === 'class' && this.__iconClasses.some((className) => !this.classList.contains(className))) {
471
- this.classList.add(...this.__iconClasses);
472
- }
473
- }
474
-
475
- /** @private */
476
- __fontFamilyChanged(fontFamily) {
477
- this.style.fontFamily = `'${fontFamily}'`;
478
- }
479
92
  }
480
93
 
481
94
  defineCustomElement(Icon);
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2017 - 2024 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
+ */
6
+ import type { Constructor } from '@open-wc/dedupe-mixin';
7
+
8
+ /**
9
+ * A mixin providing common iconset functionality.
10
+ */
11
+ export declare function IconsetMixin<T extends Constructor<HTMLElement>>(base: T): Constructor<IconsetMixinClass> & T;
12
+
13
+ export declare class IconsetMixinClass {
14
+ /**
15
+ * The name of the iconset. Every iconset is required to have its own unique name.
16
+ * All the SVG icons in the iconset must have IDs conforming to its name.
17
+ *
18
+ * See also [`name`](#/elements/vaadin-icon#property-name) property of `vaadin-icon`.
19
+ */
20
+ name: string;
21
+
22
+ /**
23
+ * The size of an individual icon. Note that icons must be square.
24
+ *
25
+ * When using `vaadin-icon`, the size of the iconset will take precedence
26
+ * over the size defined by the user to ensure correct appearance.
27
+ */
28
+ size: number;
29
+ }
@@ -0,0 +1,169 @@
1
+ /**
2
+ * @license
3
+ * Copyright (c) 2021 - 2024 Vaadin Ltd.
4
+ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
+ */
6
+ import { cloneSvgNode } from './vaadin-icon-svg.js';
7
+
8
+ const iconsetRegistry = {};
9
+
10
+ const attachedIcons = new Set();
11
+
12
+ function getIconId(id, name) {
13
+ return (id || '').replace(`${name}:`, '');
14
+ }
15
+
16
+ function getIconsetName(icon) {
17
+ if (!icon) {
18
+ return;
19
+ }
20
+ const parts = icon.split(':');
21
+
22
+ // Use "vaadin" as a fallback
23
+ return parts[0] || 'vaadin';
24
+ }
25
+
26
+ function initIconsMap(iconset, name) {
27
+ iconset._icons = [...iconset.querySelectorAll('[id]')].reduce((map, svg) => {
28
+ const key = getIconId(svg.id, name);
29
+ map[key] = svg;
30
+ return map;
31
+ }, {});
32
+ }
33
+
34
+ /**
35
+ * @polymerMixin
36
+ */
37
+ export const IconsetMixin = (superClass) =>
38
+ class extends superClass {
39
+ static get properties() {
40
+ return {
41
+ /**
42
+ * The name of the iconset. Every iconset is required to have its own unique name.
43
+ * All the SVG icons in the iconset must have IDs conforming to its name.
44
+ *
45
+ * See also [`name`](#/elements/vaadin-icon#property-name) property of `vaadin-icon`.
46
+ */
47
+ name: {
48
+ type: String,
49
+ observer: '__nameChanged',
50
+ sync: true,
51
+ },
52
+ /**
53
+ * The size of an individual icon. Note that icons must be square.
54
+ *
55
+ * When using `vaadin-icon`, the size of the iconset will take precedence
56
+ * over the size defined by the user to ensure correct appearance.
57
+ */
58
+ size: {
59
+ type: Number,
60
+ value: 24,
61
+ sync: true,
62
+ },
63
+ };
64
+ }
65
+
66
+ /**
67
+ * Set of the `vaadin-icon` instances in the DOM.
68
+ *
69
+ * @return {Set<Icon>}
70
+ */
71
+ static get attachedIcons() {
72
+ return attachedIcons;
73
+ }
74
+
75
+ /**
76
+ * Returns an instance of the iconset by its name.
77
+ *
78
+ * @param {string} name
79
+ * @return {Iconset}
80
+ */
81
+ static getIconset(name) {
82
+ return iconsetRegistry[name];
83
+ }
84
+
85
+ /**
86
+ * Returns SVGTemplateResult for the `icon` ID matching `name` of the
87
+ * iconset, or `nothing` literal if there is no matching icon found.
88
+ *
89
+ * @param {string} icon
90
+ * @param {?string} name
91
+ */
92
+ static getIconSvg(icon, name) {
93
+ const iconsetName = name || getIconsetName(icon);
94
+ const iconset = this.getIconset(iconsetName);
95
+ if (!icon || !iconset) {
96
+ // Missing icon, return `nothing` literal.
97
+ return {
98
+ svg: cloneSvgNode(null),
99
+ };
100
+ }
101
+ const iconId = getIconId(icon, iconsetName);
102
+ const iconSvg = iconset._icons[iconId];
103
+ return {
104
+ preserveAspectRatio: iconSvg ? iconSvg.getAttribute('preserveAspectRatio') : null,
105
+ svg: cloneSvgNode(iconSvg),
106
+ size: iconset.size,
107
+ viewBox: iconSvg ? iconSvg.getAttribute('viewBox') : null,
108
+ };
109
+ }
110
+
111
+ /**
112
+ * Register an iconset without adding to the DOM.
113
+ *
114
+ * @param {string} name
115
+ * @param {number} size
116
+ * @param {?HTMLTemplateElement} template
117
+ */
118
+ static register(name, size, template) {
119
+ if (!iconsetRegistry[name]) {
120
+ const iconset = document.createElement('vaadin-iconset');
121
+ iconset.appendChild(template.content.cloneNode(true));
122
+ iconsetRegistry[name] = iconset;
123
+ initIconsMap(iconset, name);
124
+ iconset.size = size;
125
+ iconset.name = name;
126
+
127
+ // Call this function manually instead of using observer
128
+ // to make it work without appending element to the DOM.
129
+ iconset.__nameChanged(name);
130
+ }
131
+ }
132
+
133
+ /** @protected */
134
+ connectedCallback() {
135
+ super.connectedCallback();
136
+ this.style.display = 'none';
137
+
138
+ // Store reference and init icons.
139
+ const { name } = this;
140
+ iconsetRegistry[name] = this;
141
+ initIconsMap(this, name);
142
+ this.__updateIcons(name);
143
+ }
144
+
145
+ /**
146
+ * Update all the icons instances in the DOM.
147
+ *
148
+ * @param {string} name
149
+ * @private
150
+ */
151
+ __updateIcons(name) {
152
+ attachedIcons.forEach((element) => {
153
+ if (name === getIconsetName(element.icon)) {
154
+ element._applyIcon();
155
+ }
156
+ });
157
+ }
158
+
159
+ /** @private */
160
+ __nameChanged(name, oldName) {
161
+ if (oldName) {
162
+ iconsetRegistry[name] = iconsetRegistry[oldName];
163
+ delete iconsetRegistry[oldName];
164
+ }
165
+ if (name) {
166
+ this.__updateIcons(name);
167
+ }
168
+ }
169
+ };
@@ -1,16 +1,17 @@
1
1
  /**
2
2
  * @license
3
- * Copyright (c) 2021 - 2023 Vaadin Ltd.
3
+ * Copyright (c) 2021 - 2024 Vaadin Ltd.
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
6
  import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
7
7
  import type { Icon } from './vaadin-icon.js';
8
8
  import type { IconSvgLiteral } from './vaadin-icon-svg.js';
9
+ import { IconsetMixin } from './vaadin-iconset-mixin.js';
9
10
 
10
11
  /**
11
12
  * `<vaadin-iconset>` is a Web Component for creating SVG icon collections.
12
13
  */
13
- declare class Iconset extends ElementMixin(HTMLElement) {
14
+ declare class Iconset extends ElementMixin(IconsetMixin(HTMLElement)) {
14
15
  /**
15
16
  * Set of the `vaadin-icon` instances in the DOM.
16
17
  */
@@ -39,22 +40,6 @@ declare class Iconset extends ElementMixin(HTMLElement) {
39
40
  size?: number;
40
41
  viewBox?: string | null;
41
42
  };
42
-
43
- /**
44
- * The name of the iconset. Every iconset is required to have its own unique name.
45
- * All the SVG icons in the iconset must have IDs conforming to its name.
46
- *
47
- * See also [`name`](#/elements/vaadin-icon#property-name) property of `vaadin-icon`.
48
- */
49
- name: string;
50
-
51
- /**
52
- * The size of an individual icon. Note that icons must be square.
53
- *
54
- * When using `vaadin-icon`, the size of the iconset will take precedence
55
- * over the size defined by the user to ensure correct appearance.
56
- */
57
- size: number;
58
43
  }
59
44
 
60
45
  declare global {