@sb1/indeks-web 0.4.0 → 0.6.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @sb1/indeks-web
2
2
 
3
+ ## 0.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 2348f42: Legg til ix-icon komponent
8
+
9
+ ## 0.5.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 9a41a0e: Legg til tooltip
14
+
3
15
  ## 0.4.0
4
16
 
5
17
  ## 0.3.1
@@ -1,8 +1,13 @@
1
1
  import { IxField } from './lib/components/field/IxField.js';
2
+ import { IxIcon, IconName } from './lib/components/icon/IxIcon.js';
2
3
  export { IxField };
4
+ export { IxIcon };
5
+ export { ICON_NAMES } from './lib/components/icon/IxIcon.js';
6
+ export type { IconName, IconValue } from './lib/components/icon/IxIcon.js';
3
7
  declare global {
4
8
  interface HTMLElementTagNameMap {
5
9
  'ix-field': IxField;
10
+ 'ix-icon': IxIcon;
6
11
  }
7
12
  namespace JSX {
8
13
  interface IntrinsicElements {
@@ -11,6 +16,15 @@ declare global {
11
16
  class?: string;
12
17
  children?: unknown;
13
18
  };
19
+ 'ix-icon': {
20
+ name?: IconName;
21
+ materialdesignname?: string;
22
+ 'data-size'?: 'sm' | 'md' | 'lg' | 'xl';
23
+ 'aria-label'?: string;
24
+ 'aria-labelledby'?: string;
25
+ class?: string;
26
+ style?: string | Record<string, string>;
27
+ };
14
28
  }
15
29
  }
16
30
  }
package/dist/npm/index.js CHANGED
@@ -1,4 +1,4 @@
1
- //#region \0@oxc-project+runtime@0.127.0/helpers/typeof.js
1
+ //#region \0@oxc-project+runtime@0.129.0/helpers/typeof.js
2
2
  function _typeof(o) {
3
3
  "@babel/helpers - typeof";
4
4
  return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o) {
@@ -8,7 +8,7 @@ function _typeof(o) {
8
8
  }, _typeof(o);
9
9
  }
10
10
  //#endregion
11
- //#region \0@oxc-project+runtime@0.127.0/helpers/toPrimitive.js
11
+ //#region \0@oxc-project+runtime@0.129.0/helpers/toPrimitive.js
12
12
  function toPrimitive(t, r) {
13
13
  if ("object" != _typeof(t) || !t) return t;
14
14
  var e = t[Symbol.toPrimitive];
@@ -20,13 +20,13 @@ function toPrimitive(t, r) {
20
20
  return ("string" === r ? String : Number)(t);
21
21
  }
22
22
  //#endregion
23
- //#region \0@oxc-project+runtime@0.127.0/helpers/toPropertyKey.js
23
+ //#region \0@oxc-project+runtime@0.129.0/helpers/toPropertyKey.js
24
24
  function toPropertyKey(t) {
25
25
  var i = toPrimitive(t, "string");
26
26
  return "symbol" == _typeof(i) ? i : i + "";
27
27
  }
28
28
  //#endregion
29
- //#region \0@oxc-project+runtime@0.127.0/helpers/defineProperty.js
29
+ //#region \0@oxc-project+runtime@0.129.0/helpers/defineProperty.js
30
30
  function _defineProperty(e, r, t) {
31
31
  return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
32
32
  value: t,
@@ -38,6 +38,7 @@ function _defineProperty(e, r, t) {
38
38
  //#endregion
39
39
  //#region lib/components/field/IxField.ts
40
40
  var fieldCounter = 0;
41
+ var ICON_URL = "https://cdn.sparebank1.no/icons/info.svg";
41
42
  var IxField = class extends HTMLElement {
42
43
  constructor(..._args) {
43
44
  super(..._args);
@@ -46,8 +47,16 @@ var IxField = class extends HTMLElement {
46
47
  _defineProperty(this, "_charCountListener", null);
47
48
  _defineProperty(this, "_charCountControl", null);
48
49
  }
50
+ static get observedAttributes() {
51
+ return [
52
+ "tooltip",
53
+ "tooltip-label",
54
+ "tooltip-placement"
55
+ ];
56
+ }
49
57
  connectedCallback() {
50
58
  this._wire();
59
+ this._syncTooltip();
51
60
  }
52
61
  disconnectedCallback() {
53
62
  this._observer?.disconnect();
@@ -56,6 +65,11 @@ var IxField = class extends HTMLElement {
56
65
  this._stateObserver = null;
57
66
  this._teardownCharCount();
58
67
  }
68
+ attributeChangedCallback(name, _oldValue, _newValue) {
69
+ if (name === "tooltip" || name === "tooltip-label" || name === "tooltip-placement") {
70
+ if (this.isConnected) this._syncTooltip();
71
+ }
72
+ }
59
73
  _wire() {
60
74
  const control = this.querySelector("input, select, textarea");
61
75
  const label = this.querySelector("label");
@@ -90,10 +104,14 @@ var IxField = class extends HTMLElement {
90
104
  "minlength"
91
105
  ]
92
106
  });
93
- if (control instanceof HTMLInputElement && control.type === "number") console.warn("[ix-field] Unngå type=\"number\" — bruk inputMode=\"numeric\" i stedet.");
94
107
  if (!control.id) control.id = `ix-field-${++fieldCounter}`;
108
+ if (label && !label.closest(".ix-field__label-row")) {
109
+ const labelRow = document.createElement("div");
110
+ labelRow.className = "ix-field__label-row";
111
+ label.parentNode.insertBefore(labelRow, label);
112
+ labelRow.appendChild(label);
113
+ }
95
114
  if (label && !label.htmlFor) label.htmlFor = control.id;
96
- if (!label && !control.getAttribute("aria-label") && !control.getAttribute("aria-labelledby")) console.warn("[ix-field] Kontrollen mangler tilgjengelig navn. Legg til <label>, aria-label eller aria-labelledby. Synlig label er anbefalt.");
97
115
  const describedBy = [];
98
116
  if (description) {
99
117
  if (!description.id) description.id = `${control.id}-description`;
@@ -167,9 +185,222 @@ var IxField = class extends HTMLElement {
167
185
  const readOnly = "readOnly" in control ? control.readOnly : false;
168
186
  this.toggleAttribute("data-readonly", readOnly);
169
187
  }
188
+ _syncTooltip() {
189
+ const content = this.getAttribute("tooltip");
190
+ if (content) this._setupTooltipBtn(content);
191
+ else this._teardownTooltipBtn();
192
+ }
193
+ _setupTooltipBtn(content) {
194
+ const tooltipLabel = this.getAttribute("tooltip-label");
195
+ const existingBtn = this.querySelector(".ix-field__tooltip-btn");
196
+ if (existingBtn) {
197
+ existingBtn.setAttribute("data-tooltip", content);
198
+ existingBtn.setAttribute("data-tooltip-placement", this.getAttribute("tooltip-placement") ?? "top");
199
+ if (tooltipLabel) existingBtn.setAttribute("aria-label", tooltipLabel);
200
+ else existingBtn.removeAttribute("aria-label");
201
+ return;
202
+ }
203
+ const labelRow = this.querySelector(".ix-field__label-row");
204
+ if (!labelRow) return;
205
+ const btn = document.createElement("button");
206
+ btn.type = "button";
207
+ btn.className = "ix-field__tooltip-btn";
208
+ btn.setAttribute("data-tooltip", content);
209
+ btn.setAttribute("data-tooltip-placement", this.getAttribute("tooltip-placement") ?? "top");
210
+ if (tooltipLabel) btn.setAttribute("aria-label", tooltipLabel);
211
+ const icon = document.createElement("span");
212
+ icon.className = "ix-icon";
213
+ icon.setAttribute("aria-hidden", "true");
214
+ icon.style.maskImage = `url(${ICON_URL})`;
215
+ btn.appendChild(icon);
216
+ labelRow.appendChild(btn);
217
+ }
218
+ _teardownTooltipBtn() {
219
+ this.querySelector(".ix-field__tooltip-btn")?.remove();
220
+ }
221
+ };
222
+ //#endregion
223
+ //#region lib/components/icon/IxIcon.ts
224
+ var ICON_CDN_BASE = "https://cdn.sparebank1.no/icons";
225
+ var ICON_NAMES = {
226
+ hjem: "home",
227
+ meny: "menu",
228
+ sparing: "savings",
229
+ lukk: "close",
230
+ "pil-hoyre": "chevron_right",
231
+ "legg-til": "add",
232
+ hake: "check",
233
+ "apne-ekstern": "open_in_new",
234
+ bankkonto: "account_balance",
235
+ rediger: "edit",
236
+ betalingskort: "credit_card",
237
+ slett: "delete",
238
+ "last-ned": "download",
239
+ person: "person",
240
+ "pil-ned": "keyboard_arrow_down",
241
+ "e-post": "mail",
242
+ betaling: "payments",
243
+ info: "info",
244
+ sok: "search",
245
+ innstillinger: "settings",
246
+ bil: "directions_car",
247
+ "pil-venstre": "chevron_left",
248
+ feil: "error"
249
+ };
250
+ var IxIcon = class extends HTMLElement {
251
+ static get observedAttributes() {
252
+ return [
253
+ "name",
254
+ "materialdesignname",
255
+ "aria-label",
256
+ "aria-labelledby"
257
+ ];
258
+ }
259
+ connectedCallback() {
260
+ this._update();
261
+ }
262
+ attributeChangedCallback(_name, oldValue, newValue) {
263
+ if (oldValue === newValue) return;
264
+ this._update();
265
+ }
266
+ _update() {
267
+ const nameAttr = this.getAttribute("name");
268
+ const materialDesignName = this.getAttribute("materialdesignname");
269
+ const ariaLabel = this.getAttribute("aria-label");
270
+ const ariaLabelledBy = this.getAttribute("aria-labelledby");
271
+ const iconFileName = nameAttr ? ICON_NAMES[nameAttr] ?? nameAttr : materialDesignName;
272
+ if (iconFileName) this.style.setProperty("--ii-icon-url", `url(${ICON_CDN_BASE}/${iconFileName}.svg)`);
273
+ else this.style.removeProperty("--ii-icon-url");
274
+ if (ariaLabel || ariaLabelledBy) {
275
+ this.setAttribute("role", "img");
276
+ this.removeAttribute("aria-hidden");
277
+ } else {
278
+ this.removeAttribute("role");
279
+ this.setAttribute("aria-hidden", "true");
280
+ }
281
+ }
170
282
  };
171
283
  //#endregion
284
+ //#region lib/tooltip/tooltip.ts
285
+ var TOOLTIP_ID = "ix-tooltip-singleton";
286
+ var ATTR = "data-tooltip";
287
+ var ATTR_PLACEMENT = "data-tooltip-placement";
288
+ var DELAY_HOVER = 300;
289
+ var tip;
290
+ var source;
291
+ var hoverTimer = 0;
292
+ function getOrCreateTip() {
293
+ if (!tip) {
294
+ tip = document.createElement("div");
295
+ tip.id = TOOLTIP_ID;
296
+ tip.className = "ix-tooltip";
297
+ tip.setAttribute("role", "tooltip");
298
+ tip.hidden = true;
299
+ }
300
+ if (!tip.isConnected) document.body.appendChild(tip);
301
+ return tip;
302
+ }
303
+ function place(trigger, panel, placement) {
304
+ const r = trigger.getBoundingClientRect();
305
+ const vw = window.innerWidth;
306
+ const vh = window.innerHeight;
307
+ panel.hidden = false;
308
+ panel.style.visibility = "hidden";
309
+ const pw = panel.offsetWidth;
310
+ const ph = panel.offsetHeight;
311
+ const arrowSize = 6;
312
+ const gap = arrowSize + 4;
313
+ const axis = placement.startsWith("top") ? "top" : placement.startsWith("bottom") ? "bottom" : placement;
314
+ let resolvedAxis = axis;
315
+ if (axis === "top" && r.top - ph - gap < 0) resolvedAxis = "bottom";
316
+ else if (axis === "bottom" && r.bottom + ph + gap > vh) resolvedAxis = "top";
317
+ else if (axis === "left" && r.left - pw - gap < 0) resolvedAxis = "right";
318
+ else if (axis === "right" && r.right + pw + gap > vw) resolvedAxis = "left";
319
+ const align = placement.endsWith("-start") ? "start" : placement.endsWith("-end") ? "end" : "center";
320
+ const resolved = align === "center" ? resolvedAxis : `${resolvedAxis}-${align}`;
321
+ panel.setAttribute("data-placement", resolved);
322
+ const triggerCenterX = r.left + r.width / 2;
323
+ const triggerCenterY = r.top + r.height / 2;
324
+ let top;
325
+ let left;
326
+ if (resolvedAxis === "top" || resolvedAxis === "bottom") {
327
+ top = resolvedAxis === "top" ? r.top - ph - gap : r.bottom + gap;
328
+ left = align === "start" ? r.left : align === "end" ? r.right - pw : triggerCenterX - pw / 2;
329
+ } else {
330
+ left = resolvedAxis === "left" ? r.left - pw - gap : r.right + gap;
331
+ top = triggerCenterY - ph / 2;
332
+ }
333
+ const clampedLeft = Math.max(8, Math.min(left, vw - pw - 8));
334
+ const clampedTop = Math.max(8, Math.min(top, vh - ph - 8));
335
+ if (resolvedAxis === "top" || resolvedAxis === "bottom") {
336
+ const padding = parseFloat(getComputedStyle(panel).paddingInlineStart) || 12;
337
+ const half = arrowSize / 2;
338
+ const rawX = triggerCenterX - clampedLeft;
339
+ const arrowX = align === "start" ? Math.max(rawX, padding + half) : align === "end" ? Math.min(rawX, pw - padding - half) : rawX;
340
+ panel.style.setProperty("--_arrow-x", `${arrowX}px`);
341
+ panel.style.removeProperty("--_arrow-y");
342
+ } else {
343
+ panel.style.setProperty("--_arrow-y", `${triggerCenterY - clampedTop}px`);
344
+ panel.style.removeProperty("--_arrow-x");
345
+ }
346
+ panel.style.top = `${clampedTop}px`;
347
+ panel.style.left = `${clampedLeft}px`;
348
+ panel.style.visibility = "";
349
+ }
350
+ function show(trigger) {
351
+ const content = trigger.getAttribute(ATTR);
352
+ if (!content) return;
353
+ const placement = trigger.getAttribute(ATTR_PLACEMENT) ?? "top";
354
+ const panel = getOrCreateTip();
355
+ if (source && source !== trigger) hide();
356
+ source = trigger;
357
+ panel.textContent = content;
358
+ trigger.setAttribute("aria-describedby", TOOLTIP_ID);
359
+ place(trigger, panel, placement);
360
+ panel.hidden = false;
361
+ }
362
+ function hide() {
363
+ clearTimeout(hoverTimer);
364
+ if (tip) tip.hidden = true;
365
+ if (source) {
366
+ source.removeAttribute("aria-describedby");
367
+ source = void 0;
368
+ }
369
+ }
370
+ function handleMouseover(e) {
371
+ const target = e.target.closest?.(`[${ATTR}]`);
372
+ if (!target) return;
373
+ if (target === source) return;
374
+ clearTimeout(hoverTimer);
375
+ hoverTimer = window.setTimeout(() => show(target), DELAY_HOVER);
376
+ }
377
+ function handleMouseout(e) {
378
+ const related = e.relatedTarget;
379
+ if (tip && related && tip.contains(related)) return;
380
+ clearTimeout(hoverTimer);
381
+ if (source && !source.contains(related)) hide();
382
+ }
383
+ function handleFocusin(e) {
384
+ const target = e.target.closest?.(`[${ATTR}]`);
385
+ if (!target) return;
386
+ clearTimeout(hoverTimer);
387
+ show(target);
388
+ }
389
+ function handleFocusout(e) {
390
+ const related = e.relatedTarget;
391
+ if (source && !source.contains(related)) hide();
392
+ }
393
+ function handleKeydown(e) {
394
+ if (e.key === "Escape" && source) hide();
395
+ }
396
+ document.addEventListener("mouseover", handleMouseover);
397
+ document.addEventListener("mouseout", handleMouseout);
398
+ document.addEventListener("focusin", handleFocusin);
399
+ document.addEventListener("focusout", handleFocusout);
400
+ document.addEventListener("keydown", handleKeydown);
401
+ //#endregion
172
402
  //#region index.ts
173
403
  customElements.define("ix-field", IxField);
404
+ customElements.define("ix-icon", IxIcon);
174
405
  //#endregion
175
- export { IxField };
406
+ export { ICON_NAMES, IxField, IxIcon };
@@ -81,12 +81,17 @@ export declare class IxField extends HTMLElement {
81
81
  private _stateObserver;
82
82
  private _charCountListener;
83
83
  private _charCountControl;
84
+ static get observedAttributes(): string[];
84
85
  connectedCallback(): void;
85
86
  disconnectedCallback(): void;
87
+ attributeChangedCallback(name: string, _oldValue: string | null, _newValue: string | null): void;
86
88
  private _wire;
87
89
  private _setupCharCount;
88
90
  private _teardownCharCount;
89
91
  private _updateCharCount;
90
92
  private _syncInvalid;
91
93
  private _syncControlState;
94
+ private _syncTooltip;
95
+ private _setupTooltipBtn;
96
+ private _teardownTooltipBtn;
92
97
  }
@@ -0,0 +1,33 @@
1
+ export declare const ICON_NAMES: {
2
+ readonly hjem: "home";
3
+ readonly meny: "menu";
4
+ readonly sparing: "savings";
5
+ readonly lukk: "close";
6
+ readonly 'pil-hoyre': "chevron_right";
7
+ readonly 'legg-til': "add";
8
+ readonly hake: "check";
9
+ readonly 'apne-ekstern': "open_in_new";
10
+ readonly bankkonto: "account_balance";
11
+ readonly rediger: "edit";
12
+ readonly betalingskort: "credit_card";
13
+ readonly slett: "delete";
14
+ readonly 'last-ned': "download";
15
+ readonly person: "person";
16
+ readonly 'pil-ned': "keyboard_arrow_down";
17
+ readonly 'e-post': "mail";
18
+ readonly betaling: "payments";
19
+ readonly info: "info";
20
+ readonly sok: "search";
21
+ readonly innstillinger: "settings";
22
+ readonly bil: "directions_car";
23
+ readonly 'pil-venstre': "chevron_left";
24
+ readonly feil: "error";
25
+ };
26
+ export type IconValue = (typeof ICON_NAMES)[keyof typeof ICON_NAMES];
27
+ export type IconName = keyof typeof ICON_NAMES;
28
+ export declare class IxIcon extends HTMLElement {
29
+ static get observedAttributes(): string[];
30
+ connectedCallback(): void;
31
+ attributeChangedCallback(_name: string, oldValue: string | null, newValue: string | null): void;
32
+ private _update;
33
+ }
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sb1/indeks-web",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "type": "module",
5
5
  "description": "Web components for Indeks design system",
6
6
  "homepage": "https://design.sparebank1.no",
@@ -40,10 +40,10 @@
40
40
  "author": "",
41
41
  "license": "MIT",
42
42
  "devDependencies": {
43
- "@types/node": "^25.6.0",
43
+ "@types/node": "^25.6.2",
44
44
  "jsdom": "^29.1.1",
45
45
  "typescript": "~6.0.3",
46
- "vite": "^8.0.10",
46
+ "vite": "^8.0.12",
47
47
  "vite-plugin-dts": "^5.0.0",
48
48
  "vitest": "^4.1.5"
49
49
  },