@dile/ui 3.5.6 → 3.6.2

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,4 +1,5 @@
1
1
  export { DileInput } from './src/DileInput.js';
2
+ export { DileInputIcon } from './src/DileInputIcon.js';
2
3
  export { DileInputInteger } from './src/DileInputInteger.js';
3
4
  export { DileInputMessage } from './src/DileInputMessage.js';
4
5
  export { DileInputMoney } from './src/DileInputMoney.js';
@@ -0,0 +1,5 @@
1
+ import { DileInputIcon } from './src/DileInputIcon.js';
2
+
3
+ if (!customElements.get('dile-input-icon')) {
4
+ window.customElements.define('dile-input-icon', DileInputIcon);
5
+ }
@@ -0,0 +1,166 @@
1
+ import { describe, it, expect, afterEach, vi } from 'vitest';
2
+ import './input-icon.js';
3
+ import '@dile/iconlib/lucide-icons/dot.js';
4
+
5
+ describe('dile-input-icon', () => {
6
+ afterEach(() => {
7
+ document.body.innerHTML = '';
8
+ });
9
+
10
+ async function renderInputIcon(html) {
11
+ document.body.innerHTML = html;
12
+ const el = document.body.querySelector('dile-input-icon');
13
+ await el.updateComplete;
14
+ return el;
15
+ }
16
+
17
+ it('renders a text input with the given value', async () => {
18
+ const el = await renderInputIcon('<dile-input-icon name="test" value="hello"></dile-input-icon>');
19
+ const input = el.shadowRoot.querySelector('input');
20
+ expect(input).toBeTruthy();
21
+ expect(input.value).toBe('hello');
22
+ expect(input.type).toBe('text');
23
+ });
24
+
25
+ it('renders the label when set', async () => {
26
+ const el = await renderInputIcon('<dile-input-icon label="Username"></dile-input-icon>');
27
+ const label = el.shadowRoot.querySelector('label');
28
+ expect(label).toBeTruthy();
29
+ expect(label.textContent.trim()).toBe('Username');
30
+ });
31
+
32
+ it('does not render icon button when icon is not set', async () => {
33
+ const el = await renderInputIcon('<dile-input-icon></dile-input-icon>');
34
+ const button = el.shadowRoot.querySelector('.input-icon-btn');
35
+ expect(button).toBeFalsy();
36
+ });
37
+
38
+ it('renders icon button when icon is set', async () => {
39
+ const el = await renderInputIcon('<dile-input-icon icon="lucide.dot"></dile-input-icon>');
40
+ await new Promise(resolve => setTimeout(resolve, 50)); // Small delay for icon registration
41
+ const button = el.shadowRoot.querySelector('.input-icon-btn');
42
+ expect(button).toBeTruthy();
43
+ });
44
+
45
+ it('disables the icon button when input is disabled', async () => {
46
+ const el = await renderInputIcon('<dile-input-icon icon="lucide.dot" disabled></dile-input-icon>');
47
+ const button = el.shadowRoot.querySelector('.input-icon-btn');
48
+ expect(button).toBeTruthy();
49
+ expect(button.disabled).toBe(true);
50
+ });
51
+
52
+ it('dispatches dile-input-icon-clicked event when icon button is clicked', async () => {
53
+ const el = await renderInputIcon('<dile-input-icon icon="lucide.dot" value="test-value"></dile-input-icon>');
54
+ const button = el.shadowRoot.querySelector('.input-icon-btn');
55
+
56
+ let eventData = null;
57
+ el.addEventListener('dile-input-icon-clicked', (e) => {
58
+ eventData = e.detail;
59
+ });
60
+
61
+ button.click();
62
+ await el.updateComplete;
63
+
64
+ expect(eventData).toBeTruthy();
65
+ expect(eventData.icon).toBe('lucide.dot');
66
+ expect(eventData.value).toBe('test-value');
67
+ });
68
+
69
+ it('calls onIconClick callback when icon button is clicked', async () => {
70
+ const callback = vi.fn();
71
+ const el = await renderInputIcon('<dile-input-icon icon="lucide.dot" value="test"></dile-input-icon>');
72
+
73
+ el.onIconClick = callback;
74
+ const button = el.shadowRoot.querySelector('.input-icon-btn');
75
+ button.click();
76
+ await el.updateComplete;
77
+
78
+ expect(callback).toHaveBeenCalledWith(el);
79
+ expect(callback).toHaveBeenCalledTimes(1);
80
+ });
81
+
82
+ it('callback receives component instance with access to value property', async () => {
83
+ const el = await renderInputIcon('<dile-input-icon icon="lucide.dot" value="secret"></dile-input-icon>');
84
+
85
+ let receivedComponent = null;
86
+ el.onIconClick = (component) => {
87
+ receivedComponent = component;
88
+ };
89
+
90
+ const button = el.shadowRoot.querySelector('.input-icon-btn');
91
+ button.click();
92
+ await el.updateComplete;
93
+
94
+ expect(receivedComponent).toBe(el);
95
+ expect(receivedComponent.value).toBe('secret');
96
+ });
97
+
98
+ it('renders a message when set', async () => {
99
+ const el = await renderInputIcon('<dile-input-icon></dile-input-icon>');
100
+ el.message = 'Required field';
101
+ await el.updateComplete;
102
+
103
+ const message = el.shadowRoot.querySelector('.message span');
104
+ expect(message).toBeTruthy();
105
+ expect(message.textContent.trim()).toBe('Required field');
106
+ });
107
+
108
+ it('updates the value property on user input', async () => {
109
+ const el = await renderInputIcon('<dile-input-icon name="test"></dile-input-icon>');
110
+ const input = el.shadowRoot.querySelector('input');
111
+ input.value = 'typed';
112
+ input.dispatchEvent(new Event('input', { bubbles: true }));
113
+ await el.updateComplete;
114
+
115
+ expect(el.value).toBe('typed');
116
+ });
117
+
118
+ it('does not render tooltip wrapper when tooltip is not set', async () => {
119
+ const el = await renderInputIcon('<dile-input-icon icon="lucide.dot"></dile-input-icon>');
120
+ const tooltip = el.shadowRoot.querySelector('dile-tooltip');
121
+ expect(tooltip).toBeFalsy();
122
+ });
123
+
124
+ it('renders dile-tooltip when tooltip property is set', async () => {
125
+ const el = await renderInputIcon('<dile-input-icon icon="lucide.dot" tooltip="Help text"></dile-input-icon>');
126
+ await el.updateComplete;
127
+ const tooltip = el.shadowRoot.querySelector('dile-tooltip');
128
+ expect(tooltip).toBeTruthy();
129
+ expect(tooltip.getAttribute('tooltip')).toBe('Help text');
130
+ });
131
+
132
+ it('maps tooltipPosition to tooltip position attribute', async () => {
133
+ const el = await renderInputIcon('<dile-input-icon icon="lucide.dot" tooltip="Help" tooltipPosition="bottom"></dile-input-icon>');
134
+ await el.updateComplete;
135
+ const tooltip = el.shadowRoot.querySelector('dile-tooltip');
136
+ expect(tooltip.getAttribute('position')).toBe('bottom');
137
+ });
138
+
139
+ it('maps tooltipFadeIn to tooltip fadeIn attribute', async () => {
140
+ const el = await renderInputIcon('<dile-input-icon icon="lucide.dot" tooltip="Help" tooltipFadeIn></dile-input-icon>');
141
+ await el.updateComplete;
142
+ const tooltip = el.shadowRoot.querySelector('dile-tooltip');
143
+ expect(tooltip.getAttribute('fadeIn')).toBe('');
144
+ });
145
+
146
+ it('maps tooltipArrow to tooltip arrow attribute', async () => {
147
+ const el = await renderInputIcon('<dile-input-icon icon="lucide.dot" tooltip="Help" tooltipArrow></dile-input-icon>');
148
+ await el.updateComplete;
149
+ const tooltip = el.shadowRoot.querySelector('dile-tooltip');
150
+ expect(tooltip.getAttribute('arrow')).toBe('');
151
+ });
152
+
153
+ it('button is still clickable when wrapped in tooltip', async () => {
154
+ const callback = vi.fn();
155
+ const el = await renderInputIcon('<dile-input-icon icon="lucide.dot" tooltip="Help"></dile-input-icon>');
156
+ el.onIconClick = callback;
157
+ await el.updateComplete;
158
+
159
+ const button = el.shadowRoot.querySelector('.input-icon-btn');
160
+ expect(button).toBeTruthy();
161
+ button.click();
162
+ await el.updateComplete;
163
+
164
+ expect(callback).toHaveBeenCalledTimes(1);
165
+ });
166
+ });
@@ -0,0 +1,181 @@
1
+ import { LitElement, html, css } from 'lit';
2
+ import { DileInput } from '../../input/index.js';
3
+ import '@dile/iconlib/dile-iconlib.js';
4
+ import '@dile/ui/components/tooltip/tooltip.js';
5
+
6
+ export class DileInputIcon extends DileInput {
7
+
8
+ static get properties() {
9
+ return {
10
+ ...super.properties,
11
+ /** Icon to display in the button, format: "family.name" (e.g., "lucide.eye") */
12
+ icon: { type: String },
13
+ /** Callback function that receives the component instance when icon button is clicked */
14
+ onIconClick: { type: Function },
15
+ /** Tooltip text to show on icon hover */
16
+ tooltip: { type: String },
17
+ /** Tooltip position: "top", "bottom", "left", "right" */
18
+ tooltipPosition: { type: String },
19
+ /** Enable fade-in animation for tooltip */
20
+ tooltipFadeIn: { type: Boolean },
21
+ /** Show arrow pointing to the icon on tooltip */
22
+ tooltipArrow: { type: Boolean },
23
+ };
24
+ }
25
+
26
+ static get styles() {
27
+ return [
28
+ ...super.styles,
29
+ css`
30
+ .input-icon-btn {
31
+ background: none;
32
+ border: none;
33
+ cursor: pointer;
34
+ padding: var(--dile-input-icon-padding, var(--dile-input-padding, 5px));
35
+ margin: var(--dile-input-icon-margin, 0 5px);
36
+ color: var(--dile-input-icon-color, var(--dile-on-background-color, #303030));
37
+ display: flex;
38
+ align-items: center;
39
+ justify-content: center;
40
+ width: var(--dile-input-icon-width, 32px);
41
+ height: var(--dile-input-icon-height, 32px);
42
+ border-radius: var(--dile-input-icon-border-radius, var(--dile-input-border-radius, 5px));
43
+ transition: var(--dile-input-icon-transition, background-color 0.2s);
44
+ }
45
+ .input-icon-btn:hover {
46
+ background-color: var(--dile-input-icon-hover-bg, rgba(0, 0, 0, 0.05));
47
+ }
48
+ .input-icon-btn:active {
49
+ background-color: var(--dile-input-icon-active-bg, rgba(0, 0, 0, 0.1));
50
+ }
51
+ .input-icon-btn:disabled {
52
+ cursor: not-allowed;
53
+ opacity: 0.5;
54
+ }
55
+ .input-icon-btn dile-iconlib {
56
+ --dile-icon-color: var(--dile-input-icon-color, var(--dile-on-background-color, #303030));
57
+ --dile-icon-size: var(--dile-input-icon-size, 20px);
58
+ }
59
+ `
60
+ ];
61
+ }
62
+
63
+ constructor() {
64
+ super();
65
+ this.icon = '';
66
+ this.onIconClick = null;
67
+ this.tooltip = '';
68
+ this.tooltipPosition = 'top';
69
+ this.tooltipFadeIn = false;
70
+ this.tooltipArrow = false;
71
+ }
72
+
73
+ render() {
74
+ const inputType = this.type || 'text';
75
+
76
+ return html`
77
+ <div>
78
+ ${this.label
79
+ ? html`<label for="textField">${this.label}</label>`
80
+ : ''
81
+ }
82
+ <section class="for-input">
83
+ <input
84
+ type="${inputType}"
85
+ id="textField"
86
+ name="${this.name}"
87
+ placeholder="${this.placeholder}"
88
+ ?disabled="${this.disabled}"
89
+ ?readonly="${this.readonly}"
90
+ @keypress="${this._lookForEnter}"
91
+ @input="${this._input}"
92
+ @blur="${this.doBlur}"
93
+ @focus="${this.doFocus}"
94
+ .value="${this.value}"
95
+ class="${this.errored ? 'errored' : ''}"
96
+ autocomplete="${this.disableAutocomplete ? 'off' : 'on'}"
97
+ ?selectonfocus="${this.selectOnFocus}"
98
+ >
99
+ ${this.icon
100
+ ? this.renderIconButton()
101
+ : ''
102
+ }
103
+ </section>
104
+ ${this.message
105
+ ? html`<div class="message ${this.errored ? 'errored-msg' : ''}"><span>${this.message}</span></div>`
106
+ : ''
107
+ }
108
+ </div>
109
+ `;
110
+ }
111
+
112
+ /**
113
+ * Renders an icon using dile-iconlib
114
+ * Supports format "family.name" (e.g., "lucide.eye", "material.home")
115
+ * dile-iconlib handles warnings if icon is not registered
116
+ * @param {string} iconName - Icon in "family.name" format
117
+ * @returns {TemplateResult}
118
+ */
119
+ renderIconlibIcon(iconName) {
120
+ if (!iconName || typeof iconName !== 'string' || !iconName.includes('.')) {
121
+ console.warn(`Invalid icon format: "${iconName}". Use "family.name" (e.g., "lucide.eye")`);
122
+ return html``;
123
+ }
124
+
125
+ const [family, name] = iconName.split('.');
126
+ if (!family || !name) {
127
+ console.warn(`Invalid icon format: "${iconName}". Use "family.name" (e.g., "lucide.eye")`);
128
+ return html``;
129
+ }
130
+
131
+ // Always render dile-iconlib, it will handle non-registered icons
132
+ return html`<dile-iconlib icon="${iconName}"></dile-iconlib>`;
133
+ }
134
+
135
+ /**
136
+ * Renders the icon button, optionally wrapped with a tooltip
137
+ * @returns {TemplateResult}
138
+ */
139
+ renderIconButton() {
140
+ const button = html`<button
141
+ class="input-icon-btn"
142
+ type="button"
143
+ @click="${this._handleIconClick}"
144
+ ?disabled="${this.disabled}"
145
+ aria-label="Icon action button"
146
+ >
147
+ ${this.renderIconlibIcon(this.icon)}
148
+ </button>`;
149
+
150
+ // If tooltip is defined, wrap button with dile-tooltip
151
+ if (this.tooltip) {
152
+ return html`<dile-tooltip
153
+ tooltip="${this.tooltip}"
154
+ position="${this.tooltipPosition}"
155
+ ?fadeIn="${this.tooltipFadeIn}"
156
+ ?arrow="${this.tooltipArrow}"
157
+ >
158
+ ${button}
159
+ </dile-tooltip>`;
160
+ }
161
+
162
+ return button;
163
+ }
164
+
165
+ _handleIconClick() {
166
+ // Dispatch custom event
167
+ this.dispatchEvent(new CustomEvent('dile-input-icon-clicked', {
168
+ bubbles: true,
169
+ composed: true,
170
+ detail: {
171
+ icon: this.icon,
172
+ value: this.value,
173
+ }
174
+ }));
175
+
176
+ // Execute callback if provided
177
+ if (typeof this.onIconClick === 'function') {
178
+ this.onIconClick(this);
179
+ }
180
+ }
181
+ }
@@ -76,7 +76,7 @@ export class DileTooltip extends LitElement {
76
76
  transform: translateX(-50%);
77
77
  border-width: 5px;
78
78
  border-style: solid;
79
- border-color: transparent transparent var(--dile-tooltip-background-color, #e74c3c) transparent;
79
+ border-color: transparent transparent var(--dile-tooltip-background-color, var(--dile-primary-color, #e74c3c)) transparent;
80
80
  }
81
81
 
82
82
  .animation{
@@ -117,14 +117,22 @@ export const DileForm = (superclass) => class extends superclass {
117
117
  const value = this.firstValue[fieldName][index];
118
118
  const node = this.getNodeElement(name);
119
119
  if (node) {
120
- node.value = value;
120
+ if (typeof node.set === "function") {
121
+ node.set(value);
122
+ } else {
123
+ node.value = value;
124
+ }
121
125
  }
122
126
  }
123
127
  } else if (this.firstValue.hasOwnProperty(name)) {
124
128
  let value = this.firstValue[name];
125
129
  let node = this.getNodeElement(name);
126
130
  if (node) {
127
- node.value = value;
131
+ if (typeof node.set === "function") {
132
+ node.set(value);
133
+ } else {
134
+ node.value = value;
135
+ }
128
136
  }
129
137
  }
130
138
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dile/ui",
3
- "version": "3.5.6",
3
+ "version": "3.6.2",
4
4
  "description": "UI Core components from dile-components.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -28,5 +28,5 @@
28
28
  "publishConfig": {
29
29
  "access": "public"
30
30
  },
31
- "gitHead": "1e41d1159507b49c1beb4f023f910085ed06c2c6"
31
+ "gitHead": "c5978f587285d7fd91670ec57f99925c46fb6bd7"
32
32
  }