mtrl 0.2.2 → 0.2.3
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/.typedocignore +11 -0
- package/DOCS.md +153 -0
- package/index.ts +18 -3
- package/package.json +7 -2
- package/src/components/badge/_styles.scss +174 -0
- package/src/components/badge/api.ts +292 -0
- package/src/components/badge/badge.ts +52 -0
- package/src/components/badge/config.ts +68 -0
- package/src/components/badge/constants.ts +30 -0
- package/src/components/badge/features.ts +185 -0
- package/src/components/badge/index.ts +4 -0
- package/src/components/badge/types.ts +105 -0
- package/src/components/button/types.ts +174 -29
- package/src/components/carousel/_styles.scss +645 -0
- package/src/components/carousel/api.ts +147 -0
- package/src/components/carousel/carousel.ts +178 -0
- package/src/components/carousel/config.ts +91 -0
- package/src/components/carousel/constants.ts +95 -0
- package/src/components/carousel/features/drag.ts +388 -0
- package/src/components/carousel/features/index.ts +8 -0
- package/src/components/carousel/features/slides.ts +682 -0
- package/src/components/carousel/index.ts +38 -0
- package/src/components/carousel/types.ts +327 -0
- package/src/components/dialog/_styles.scss +213 -0
- package/src/components/dialog/api.ts +283 -0
- package/src/components/dialog/config.ts +113 -0
- package/src/components/dialog/constants.ts +32 -0
- package/src/components/dialog/dialog.ts +56 -0
- package/src/components/dialog/features.ts +713 -0
- package/src/components/dialog/index.ts +15 -0
- package/src/components/dialog/types.ts +221 -0
- package/src/components/progress/_styles.scss +13 -1
- package/src/components/progress/api.ts +2 -2
- package/src/components/progress/progress.ts +2 -2
- package/src/components/progress/types.ts +3 -0
- package/src/components/radios/_styles.scss +232 -0
- package/src/components/radios/api.ts +100 -0
- package/src/components/radios/config.ts +60 -0
- package/src/components/radios/constants.ts +28 -0
- package/src/components/radios/index.ts +4 -0
- package/src/components/radios/radio.ts +269 -0
- package/src/components/radios/radios.ts +42 -0
- package/src/components/radios/types.ts +232 -0
- package/src/components/sheet/_styles.scss +236 -0
- package/src/components/sheet/api.ts +96 -0
- package/src/components/sheet/config.ts +66 -0
- package/src/components/sheet/constants.ts +20 -0
- package/src/components/sheet/features/content.ts +51 -0
- package/src/components/sheet/features/gestures.ts +177 -0
- package/src/components/sheet/features/index.ts +6 -0
- package/src/components/sheet/features/position.ts +42 -0
- package/src/components/sheet/features/state.ts +116 -0
- package/src/components/sheet/features/title.ts +86 -0
- package/src/components/sheet/index.ts +4 -0
- package/src/components/sheet/sheet.ts +57 -0
- package/src/components/sheet/types.ts +266 -0
- package/src/components/slider/_styles.scss +518 -0
- package/src/components/slider/api.ts +336 -0
- package/src/components/slider/config.ts +145 -0
- package/src/components/slider/constants.ts +28 -0
- package/src/components/slider/features/appearance.ts +140 -0
- package/src/components/slider/features/disabled.ts +43 -0
- package/src/components/slider/features/events.ts +164 -0
- package/src/components/slider/features/index.ts +5 -0
- package/src/components/slider/features/interactions.ts +256 -0
- package/src/components/slider/features/keyboard.ts +114 -0
- package/src/components/slider/features/slider.ts +336 -0
- package/src/components/slider/features/structure.ts +264 -0
- package/src/components/slider/features/ui.ts +518 -0
- package/src/components/slider/index.ts +9 -0
- package/src/components/slider/slider.ts +58 -0
- package/src/components/slider/types.ts +166 -0
- package/src/components/tabs/_styles.scss +224 -0
- package/src/components/tabs/api.ts +443 -0
- package/src/components/tabs/config.ts +80 -0
- package/src/components/tabs/constants.ts +12 -0
- package/src/components/tabs/index.ts +4 -0
- package/src/components/tabs/tabs.ts +52 -0
- package/src/components/tabs/types.ts +247 -0
- package/src/components/textfield/_styles.scss +97 -4
- package/src/components/tooltip/_styles.scss +241 -0
- package/src/components/tooltip/api.ts +411 -0
- package/src/components/tooltip/config.ts +78 -0
- package/src/components/tooltip/constants.ts +27 -0
- package/src/components/tooltip/index.ts +4 -0
- package/src/components/tooltip/tooltip.ts +60 -0
- package/src/components/tooltip/types.ts +178 -0
- package/src/index.ts +9 -1
- package/src/styles/abstract/_variables.scss +24 -12
- package/tsconfig.json +22 -0
- package/typedoc.json +28 -0
- package/typedoc.simple.json +14 -0
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
// src/components/tooltip/api.ts
|
|
2
|
+
import { TooltipComponent } from './types';
|
|
3
|
+
import { TOOLTIP_POSITIONS, DEFAULT_OFFSET, DEFAULT_ARROW_SIZE } from './constants';
|
|
4
|
+
|
|
5
|
+
interface ApiOptions {
|
|
6
|
+
lifecycle: {
|
|
7
|
+
destroy: () => void;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface ComponentWithElements {
|
|
12
|
+
element: HTMLElement;
|
|
13
|
+
getClass: (name: string) => string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Enhances a tooltip component with API methods
|
|
18
|
+
* @param {ApiOptions} options - API configuration options
|
|
19
|
+
* @returns {Function} Higher-order function that adds API methods to component
|
|
20
|
+
* @internal This is an internal utility for the Tooltip component
|
|
21
|
+
*/
|
|
22
|
+
export const withAPI = ({ lifecycle }: ApiOptions) =>
|
|
23
|
+
(component: ComponentWithElements): TooltipComponent => {
|
|
24
|
+
// Set up internal state
|
|
25
|
+
let target: HTMLElement | null = null;
|
|
26
|
+
let position = TOOLTIP_POSITIONS.BOTTOM;
|
|
27
|
+
let isVisible = false;
|
|
28
|
+
let showTimer: number | null = null;
|
|
29
|
+
let hideTimer: number | null = null;
|
|
30
|
+
let showDelay = 300;
|
|
31
|
+
let hideDelay = 100;
|
|
32
|
+
let showOnFocus = true;
|
|
33
|
+
let showOnHover = true;
|
|
34
|
+
|
|
35
|
+
// Create arrow element
|
|
36
|
+
const arrowElement = document.createElement('div');
|
|
37
|
+
arrowElement.className = `${component.getClass('tooltip')}__arrow`;
|
|
38
|
+
component.element.appendChild(arrowElement);
|
|
39
|
+
|
|
40
|
+
// Add to body (but hidden initially)
|
|
41
|
+
document.body.appendChild(component.element);
|
|
42
|
+
component.element.setAttribute('aria-hidden', 'true');
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Calculate position based on target element and desired position
|
|
46
|
+
*/
|
|
47
|
+
const calculatePosition = (): { top: number; left: number; arrowPosition?: string } => {
|
|
48
|
+
if (!target) return { top: 0, left: 0 };
|
|
49
|
+
|
|
50
|
+
const tooltipRect = component.element.getBoundingClientRect();
|
|
51
|
+
const targetRect = target.getBoundingClientRect();
|
|
52
|
+
const scrollY = window.scrollY || window.pageYOffset;
|
|
53
|
+
const scrollX = window.scrollX || window.pageXOffset;
|
|
54
|
+
|
|
55
|
+
// Default offset
|
|
56
|
+
const offset = DEFAULT_OFFSET;
|
|
57
|
+
const arrowSize = DEFAULT_ARROW_SIZE;
|
|
58
|
+
|
|
59
|
+
let top = 0;
|
|
60
|
+
let left = 0;
|
|
61
|
+
let arrowPosition: string | undefined;
|
|
62
|
+
|
|
63
|
+
// Calculate position based on position value
|
|
64
|
+
switch (position) {
|
|
65
|
+
case TOOLTIP_POSITIONS.TOP:
|
|
66
|
+
top = targetRect.top + scrollY - tooltipRect.height - offset;
|
|
67
|
+
left = targetRect.left + scrollX + (targetRect.width / 2) - (tooltipRect.width / 2);
|
|
68
|
+
arrowPosition = 'bottom';
|
|
69
|
+
break;
|
|
70
|
+
|
|
71
|
+
case TOOLTIP_POSITIONS.TOP_START:
|
|
72
|
+
top = targetRect.top + scrollY - tooltipRect.height - offset;
|
|
73
|
+
left = targetRect.left + scrollX;
|
|
74
|
+
arrowPosition = 'bottom-start';
|
|
75
|
+
break;
|
|
76
|
+
|
|
77
|
+
case TOOLTIP_POSITIONS.TOP_END:
|
|
78
|
+
top = targetRect.top + scrollY - tooltipRect.height - offset;
|
|
79
|
+
left = targetRect.left + scrollX + targetRect.width - tooltipRect.width;
|
|
80
|
+
arrowPosition = 'bottom-end';
|
|
81
|
+
break;
|
|
82
|
+
|
|
83
|
+
case TOOLTIP_POSITIONS.RIGHT:
|
|
84
|
+
top = targetRect.top + scrollY + (targetRect.height / 2) - (tooltipRect.height / 2);
|
|
85
|
+
left = targetRect.left + scrollX + targetRect.width + offset;
|
|
86
|
+
arrowPosition = 'left';
|
|
87
|
+
break;
|
|
88
|
+
|
|
89
|
+
case TOOLTIP_POSITIONS.RIGHT_START:
|
|
90
|
+
top = targetRect.top + scrollY;
|
|
91
|
+
left = targetRect.left + scrollX + targetRect.width + offset;
|
|
92
|
+
arrowPosition = 'left-start';
|
|
93
|
+
break;
|
|
94
|
+
|
|
95
|
+
case TOOLTIP_POSITIONS.RIGHT_END:
|
|
96
|
+
top = targetRect.top + scrollY + targetRect.height - tooltipRect.height;
|
|
97
|
+
left = targetRect.left + scrollX + targetRect.width + offset;
|
|
98
|
+
arrowPosition = 'left-end';
|
|
99
|
+
break;
|
|
100
|
+
|
|
101
|
+
case TOOLTIP_POSITIONS.BOTTOM:
|
|
102
|
+
top = targetRect.top + scrollY + targetRect.height + offset;
|
|
103
|
+
left = targetRect.left + scrollX + (targetRect.width / 2) - (tooltipRect.width / 2);
|
|
104
|
+
arrowPosition = 'top';
|
|
105
|
+
break;
|
|
106
|
+
|
|
107
|
+
case TOOLTIP_POSITIONS.BOTTOM_START:
|
|
108
|
+
top = targetRect.top + scrollY + targetRect.height + offset;
|
|
109
|
+
left = targetRect.left + scrollX;
|
|
110
|
+
arrowPosition = 'top-start';
|
|
111
|
+
break;
|
|
112
|
+
|
|
113
|
+
case TOOLTIP_POSITIONS.BOTTOM_END:
|
|
114
|
+
top = targetRect.top + scrollY + targetRect.height + offset;
|
|
115
|
+
left = targetRect.left + scrollX + targetRect.width - tooltipRect.width;
|
|
116
|
+
arrowPosition = 'top-end';
|
|
117
|
+
break;
|
|
118
|
+
|
|
119
|
+
case TOOLTIP_POSITIONS.LEFT:
|
|
120
|
+
top = targetRect.top + scrollY + (targetRect.height / 2) - (tooltipRect.height / 2);
|
|
121
|
+
left = targetRect.left + scrollX - tooltipRect.width - offset;
|
|
122
|
+
arrowPosition = 'right';
|
|
123
|
+
break;
|
|
124
|
+
|
|
125
|
+
case TOOLTIP_POSITIONS.LEFT_START:
|
|
126
|
+
top = targetRect.top + scrollY;
|
|
127
|
+
left = targetRect.left + scrollX - tooltipRect.width - offset;
|
|
128
|
+
arrowPosition = 'right-start';
|
|
129
|
+
break;
|
|
130
|
+
|
|
131
|
+
case TOOLTIP_POSITIONS.LEFT_END:
|
|
132
|
+
top = targetRect.top + scrollY + targetRect.height - tooltipRect.height;
|
|
133
|
+
left = targetRect.left + scrollX - tooltipRect.width - offset;
|
|
134
|
+
arrowPosition = 'right-end';
|
|
135
|
+
break;
|
|
136
|
+
|
|
137
|
+
default:
|
|
138
|
+
top = targetRect.top + scrollY + targetRect.height + offset;
|
|
139
|
+
left = targetRect.left + scrollX + (targetRect.width / 2) - (tooltipRect.width / 2);
|
|
140
|
+
arrowPosition = 'top';
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Constrain to window boundaries
|
|
144
|
+
const windowWidth = window.innerWidth;
|
|
145
|
+
const windowHeight = window.innerHeight;
|
|
146
|
+
|
|
147
|
+
// Adjust horizontal position
|
|
148
|
+
if (left < 0) {
|
|
149
|
+
left = 0;
|
|
150
|
+
} else if (left + tooltipRect.width > windowWidth) {
|
|
151
|
+
left = windowWidth - tooltipRect.width;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return { top, left, arrowPosition };
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Handle events on target element
|
|
159
|
+
*/
|
|
160
|
+
const addTargetEvents = (targetEl: HTMLElement) => {
|
|
161
|
+
if (showOnHover) {
|
|
162
|
+
targetEl.addEventListener('mouseenter', handleTargetMouseEnter);
|
|
163
|
+
targetEl.addEventListener('mouseleave', handleTargetMouseLeave);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (showOnFocus) {
|
|
167
|
+
targetEl.addEventListener('focus', handleTargetFocus);
|
|
168
|
+
targetEl.addEventListener('blur', handleTargetBlur);
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const removeTargetEvents = (targetEl: HTMLElement) => {
|
|
173
|
+
if (targetEl) {
|
|
174
|
+
targetEl.removeEventListener('mouseenter', handleTargetMouseEnter);
|
|
175
|
+
targetEl.removeEventListener('mouseleave', handleTargetMouseLeave);
|
|
176
|
+
targetEl.removeEventListener('focus', handleTargetFocus);
|
|
177
|
+
targetEl.removeEventListener('blur', handleTargetBlur);
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
// Event handlers
|
|
182
|
+
const handleTargetMouseEnter = () => api.show();
|
|
183
|
+
const handleTargetMouseLeave = () => api.hide();
|
|
184
|
+
const handleTargetFocus = () => api.show();
|
|
185
|
+
const handleTargetBlur = () => api.hide();
|
|
186
|
+
|
|
187
|
+
// Create the API object
|
|
188
|
+
const api: TooltipComponent = {
|
|
189
|
+
element: component.element,
|
|
190
|
+
target,
|
|
191
|
+
lifecycle,
|
|
192
|
+
|
|
193
|
+
getClass: component.getClass,
|
|
194
|
+
|
|
195
|
+
setText(text) {
|
|
196
|
+
// Create a text node
|
|
197
|
+
const contentNode = document.createTextNode(text);
|
|
198
|
+
|
|
199
|
+
// Clear existing content
|
|
200
|
+
while (component.element.firstChild !== arrowElement) {
|
|
201
|
+
component.element.removeChild(component.element.firstChild);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Add new content before the arrow
|
|
205
|
+
component.element.insertBefore(contentNode, arrowElement);
|
|
206
|
+
|
|
207
|
+
// Update position if visible
|
|
208
|
+
if (isVisible) {
|
|
209
|
+
this.updatePosition();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return this;
|
|
213
|
+
},
|
|
214
|
+
|
|
215
|
+
getText() {
|
|
216
|
+
// Return text content excluding the arrow element
|
|
217
|
+
const clone = component.element.cloneNode(true) as HTMLElement;
|
|
218
|
+
const arrowClone = clone.querySelector(`.${component.getClass('tooltip')}__arrow`);
|
|
219
|
+
if (arrowClone) {
|
|
220
|
+
arrowClone.remove();
|
|
221
|
+
}
|
|
222
|
+
return clone.textContent || '';
|
|
223
|
+
},
|
|
224
|
+
|
|
225
|
+
setPosition(newPosition) {
|
|
226
|
+
// Update position value
|
|
227
|
+
position = newPosition;
|
|
228
|
+
|
|
229
|
+
// Update position class
|
|
230
|
+
const baseClass = component.getClass('tooltip');
|
|
231
|
+
|
|
232
|
+
// Remove existing position classes
|
|
233
|
+
const positionClasses = Object.values(TOOLTIP_POSITIONS).map(p => `${baseClass}--${p}`);
|
|
234
|
+
component.element.classList.remove(...positionClasses);
|
|
235
|
+
|
|
236
|
+
// Add new position class
|
|
237
|
+
component.element.classList.add(`${baseClass}--${newPosition}`);
|
|
238
|
+
|
|
239
|
+
// Update position if visible
|
|
240
|
+
if (isVisible) {
|
|
241
|
+
this.updatePosition();
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return this;
|
|
245
|
+
},
|
|
246
|
+
|
|
247
|
+
getPosition() {
|
|
248
|
+
return position;
|
|
249
|
+
},
|
|
250
|
+
|
|
251
|
+
setTarget(newTarget) {
|
|
252
|
+
// Remove events from old target
|
|
253
|
+
if (target) {
|
|
254
|
+
removeTargetEvents(target);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Set new target
|
|
258
|
+
target = newTarget;
|
|
259
|
+
this.target = newTarget;
|
|
260
|
+
|
|
261
|
+
// Set target's aria attributes
|
|
262
|
+
target.setAttribute('aria-describedby', component.element.id);
|
|
263
|
+
|
|
264
|
+
// Add events to new target
|
|
265
|
+
addTargetEvents(target);
|
|
266
|
+
|
|
267
|
+
// Update position if visible
|
|
268
|
+
if (isVisible) {
|
|
269
|
+
this.updatePosition();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return this;
|
|
273
|
+
},
|
|
274
|
+
|
|
275
|
+
show(immediate = false) {
|
|
276
|
+
// Clear any existing timers
|
|
277
|
+
if (showTimer !== null) {
|
|
278
|
+
window.clearTimeout(showTimer);
|
|
279
|
+
showTimer = null;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (hideTimer !== null) {
|
|
283
|
+
window.clearTimeout(hideTimer);
|
|
284
|
+
hideTimer = null;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const showTooltip = () => {
|
|
288
|
+
if (!target) return this;
|
|
289
|
+
|
|
290
|
+
// Show the tooltip
|
|
291
|
+
component.element.setAttribute('aria-hidden', 'false');
|
|
292
|
+
component.element.classList.add(`${component.getClass('tooltip')}--visible`);
|
|
293
|
+
isVisible = true;
|
|
294
|
+
|
|
295
|
+
// Update position
|
|
296
|
+
this.updatePosition();
|
|
297
|
+
|
|
298
|
+
// Add resize listener
|
|
299
|
+
window.addEventListener('resize', handleWindowResize);
|
|
300
|
+
window.addEventListener('scroll', handleWindowScroll);
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
if (immediate) {
|
|
304
|
+
showTooltip();
|
|
305
|
+
} else {
|
|
306
|
+
showTimer = window.setTimeout(showTooltip, showDelay);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return this;
|
|
310
|
+
},
|
|
311
|
+
|
|
312
|
+
hide(immediate = false) {
|
|
313
|
+
// Clear any existing timers
|
|
314
|
+
if (hideTimer !== null) {
|
|
315
|
+
window.clearTimeout(hideTimer);
|
|
316
|
+
hideTimer = null;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (showTimer !== null) {
|
|
320
|
+
window.clearTimeout(showTimer);
|
|
321
|
+
showTimer = null;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const hideTooltip = () => {
|
|
325
|
+
// Hide the tooltip
|
|
326
|
+
component.element.setAttribute('aria-hidden', 'true');
|
|
327
|
+
component.element.classList.remove(`${component.getClass('tooltip')}--visible`);
|
|
328
|
+
isVisible = false;
|
|
329
|
+
|
|
330
|
+
// Remove resize and scroll listeners
|
|
331
|
+
window.removeEventListener('resize', handleWindowResize);
|
|
332
|
+
window.removeEventListener('scroll', handleWindowScroll);
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
if (immediate) {
|
|
336
|
+
hideTooltip();
|
|
337
|
+
} else {
|
|
338
|
+
hideTimer = window.setTimeout(hideTooltip, hideDelay);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return this;
|
|
342
|
+
},
|
|
343
|
+
|
|
344
|
+
isVisible() {
|
|
345
|
+
return isVisible;
|
|
346
|
+
},
|
|
347
|
+
|
|
348
|
+
updatePosition() {
|
|
349
|
+
if (!target) return this;
|
|
350
|
+
|
|
351
|
+
// Calculate position
|
|
352
|
+
const { top, left, arrowPosition } = calculatePosition();
|
|
353
|
+
|
|
354
|
+
// Update tooltip position
|
|
355
|
+
component.element.style.top = `${Math.round(top)}px`;
|
|
356
|
+
component.element.style.left = `${Math.round(left)}px`;
|
|
357
|
+
|
|
358
|
+
// Update arrow position
|
|
359
|
+
if (arrowPosition) {
|
|
360
|
+
// Remove existing arrow position classes
|
|
361
|
+
arrowElement.className = `${component.getClass('tooltip')}__arrow`;
|
|
362
|
+
// Add new position class
|
|
363
|
+
arrowElement.classList.add(`${component.getClass('tooltip')}__arrow--${arrowPosition}`);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return this;
|
|
367
|
+
},
|
|
368
|
+
|
|
369
|
+
destroy() {
|
|
370
|
+
// Clear timers
|
|
371
|
+
if (showTimer !== null) {
|
|
372
|
+
window.clearTimeout(showTimer);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (hideTimer !== null) {
|
|
376
|
+
window.clearTimeout(hideTimer);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Remove target events
|
|
380
|
+
if (target) {
|
|
381
|
+
removeTargetEvents(target);
|
|
382
|
+
target.removeAttribute('aria-describedby');
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Remove window events
|
|
386
|
+
window.removeEventListener('resize', handleWindowResize);
|
|
387
|
+
window.removeEventListener('scroll', handleWindowScroll);
|
|
388
|
+
|
|
389
|
+
// Remove from DOM
|
|
390
|
+
if (component.element.parentNode) {
|
|
391
|
+
component.element.parentNode.removeChild(component.element);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Call lifecycle destroy
|
|
395
|
+
lifecycle.destroy();
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
// Window event handlers
|
|
400
|
+
const handleWindowResize = () => {
|
|
401
|
+
api.updatePosition();
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
const handleWindowScroll = () => {
|
|
405
|
+
api.updatePosition();
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
return api;
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
export default withAPI;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// src/components/tooltip/config.ts
|
|
2
|
+
import {
|
|
3
|
+
createComponentConfig,
|
|
4
|
+
createElementConfig
|
|
5
|
+
} from '../../core/config/component-config';
|
|
6
|
+
import { TooltipConfig } from './types';
|
|
7
|
+
import {
|
|
8
|
+
TOOLTIP_POSITIONS,
|
|
9
|
+
TOOLTIP_VARIANTS,
|
|
10
|
+
DEFAULT_SHOW_DELAY,
|
|
11
|
+
DEFAULT_HIDE_DELAY
|
|
12
|
+
} from './constants';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Default configuration for the Tooltip component
|
|
16
|
+
*/
|
|
17
|
+
export const defaultConfig: TooltipConfig = {
|
|
18
|
+
position: TOOLTIP_POSITIONS.BOTTOM,
|
|
19
|
+
variant: TOOLTIP_VARIANTS.DEFAULT,
|
|
20
|
+
visible: false,
|
|
21
|
+
showDelay: DEFAULT_SHOW_DELAY,
|
|
22
|
+
hideDelay: DEFAULT_HIDE_DELAY,
|
|
23
|
+
showOnFocus: true,
|
|
24
|
+
showOnHover: true,
|
|
25
|
+
rich: false
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Creates the base configuration for Tooltip component
|
|
30
|
+
* @param {TooltipConfig} config - User provided configuration
|
|
31
|
+
* @returns {TooltipConfig} Complete configuration with defaults applied
|
|
32
|
+
*/
|
|
33
|
+
export const createBaseConfig = (config: TooltipConfig = {}): TooltipConfig =>
|
|
34
|
+
createComponentConfig(defaultConfig, config, 'tooltip') as TooltipConfig;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Generates element configuration for the Tooltip component
|
|
38
|
+
* @param {TooltipConfig} config - Tooltip configuration
|
|
39
|
+
* @returns {Object} Element configuration object for withElement
|
|
40
|
+
*/
|
|
41
|
+
export const getElementConfig = (config: TooltipConfig) => {
|
|
42
|
+
// Create the attributes object
|
|
43
|
+
const attrs: Record<string, any> = {
|
|
44
|
+
role: 'tooltip',
|
|
45
|
+
'aria-hidden': 'true'
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Add z-index if provided
|
|
49
|
+
if (config.zIndex !== undefined) {
|
|
50
|
+
attrs['style'] = `z-index: ${config.zIndex};`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const extraClasses: string[] = [
|
|
54
|
+
`--${config.position}`,
|
|
55
|
+
`--${config.variant}`
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
return createElementConfig(config, {
|
|
59
|
+
tag: 'div',
|
|
60
|
+
attrs,
|
|
61
|
+
className: config.class,
|
|
62
|
+
extraClasses,
|
|
63
|
+
content: config.text || ''
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Creates API configuration for the Tooltip component
|
|
69
|
+
* @param {Object} comp - Component with lifecycle feature
|
|
70
|
+
* @returns {Object} API configuration object
|
|
71
|
+
*/
|
|
72
|
+
export const getApiConfig = (comp) => ({
|
|
73
|
+
lifecycle: {
|
|
74
|
+
destroy: () => comp.lifecycle.destroy()
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
export default defaultConfig;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// src/components/tooltip/constants.ts
|
|
2
|
+
|
|
3
|
+
export const TOOLTIP_POSITIONS = {
|
|
4
|
+
TOP: 'top',
|
|
5
|
+
RIGHT: 'right',
|
|
6
|
+
BOTTOM: 'bottom',
|
|
7
|
+
LEFT: 'left',
|
|
8
|
+
TOP_START: 'top-start',
|
|
9
|
+
TOP_END: 'top-end',
|
|
10
|
+
RIGHT_START: 'right-start',
|
|
11
|
+
RIGHT_END: 'right-end',
|
|
12
|
+
BOTTOM_START: 'bottom-start',
|
|
13
|
+
BOTTOM_END: 'bottom-end',
|
|
14
|
+
LEFT_START: 'left-start',
|
|
15
|
+
LEFT_END: 'left-end'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const TOOLTIP_VARIANTS = {
|
|
19
|
+
DEFAULT: 'default',
|
|
20
|
+
RICH: 'rich',
|
|
21
|
+
PLAIN: 'plain'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const DEFAULT_SHOW_DELAY = 300
|
|
25
|
+
export const DEFAULT_HIDE_DELAY = 100
|
|
26
|
+
export const DEFAULT_OFFSET = 8
|
|
27
|
+
export const DEFAULT_ARROW_SIZE = 8
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// src/components/tooltip/tooltip.ts
|
|
2
|
+
import { PREFIX } from '../../core/config';
|
|
3
|
+
import { pipe } from '../../core/compose';
|
|
4
|
+
import { createBase, withElement } from '../../core/compose/component';
|
|
5
|
+
import { withLifecycle } from '../../core/compose/features';
|
|
6
|
+
import { withAPI } from './api';
|
|
7
|
+
import { TooltipConfig } from './types';
|
|
8
|
+
import { createBaseConfig, getElementConfig, getApiConfig } from './config';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Creates a unique ID for the tooltip
|
|
12
|
+
* @returns {string} Unique ID
|
|
13
|
+
*/
|
|
14
|
+
const createTooltipId = (): string => {
|
|
15
|
+
return `${PREFIX}-tooltip-${Math.random().toString(36).substring(2, 9)}`;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Creates a new Tooltip component
|
|
20
|
+
* @param {TooltipConfig} config - Tooltip configuration object
|
|
21
|
+
* @returns {TooltipComponent} Tooltip component instance
|
|
22
|
+
*/
|
|
23
|
+
const createTooltip = (config: TooltipConfig = {}) => {
|
|
24
|
+
const baseConfig = createBaseConfig(config);
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
// Create the tooltip element
|
|
28
|
+
const tooltip = pipe(
|
|
29
|
+
createBase,
|
|
30
|
+
withElement(getElementConfig(baseConfig)),
|
|
31
|
+
withLifecycle(),
|
|
32
|
+
comp => withAPI(getApiConfig(comp))(comp)
|
|
33
|
+
)(baseConfig);
|
|
34
|
+
|
|
35
|
+
// Generate a unique ID
|
|
36
|
+
tooltip.element.id = createTooltipId();
|
|
37
|
+
|
|
38
|
+
// Set target if provided
|
|
39
|
+
if (baseConfig.target) {
|
|
40
|
+
tooltip.setTarget(baseConfig.target);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Set text if provided
|
|
44
|
+
if (baseConfig.text) {
|
|
45
|
+
tooltip.setText(baseConfig.text);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Show tooltip if initially visible
|
|
49
|
+
if (baseConfig.visible) {
|
|
50
|
+
tooltip.show(true);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return tooltip;
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.error('Tooltip creation error:', error);
|
|
56
|
+
throw new Error(`Failed to create tooltip: ${(error as Error).message}`);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export default createTooltip;
|