@dodlhuat/basix 1.2.8 → 1.2.9

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/js/timepicker.ts DELETED
@@ -1,202 +0,0 @@
1
- interface TimeSpan {
2
- start: string;
3
- end: string;
4
- }
5
-
6
- interface TimeSpanPickerOptions {
7
- onChange?: (start: string, end: string) => void;
8
- defaultStart?: string;
9
- defaultEnd?: string;
10
- fromString?: string;
11
- toString?: string;
12
- }
13
-
14
- class TimeSpanPicker {
15
- private container: HTMLElement;
16
- private startTimeInput: HTMLInputElement;
17
- private endTimeInput: HTMLInputElement;
18
- private onChange?: (start: string, end: string) => void;
19
- private readonly uid: string;
20
- private fromString: string;
21
- private toString: string;
22
-
23
- constructor(elementOrSelector: string | HTMLElement, options?: TimeSpanPickerOptions) {
24
- const element = typeof elementOrSelector === 'string'
25
- ? (elementOrSelector.startsWith('#') || elementOrSelector.startsWith('.')
26
- ? document.querySelector<HTMLElement>(elementOrSelector)
27
- : document.getElementById(elementOrSelector))
28
- : elementOrSelector;
29
-
30
- if (!element) {
31
- throw new Error(`TimeSpanPicker: Element not found for "${elementOrSelector}"`);
32
- }
33
-
34
- this.container = element;
35
- this.onChange = options?.onChange;
36
- this.uid = `tsp-${Math.random().toString(36).slice(2, 9)}`;
37
- this.fromString = options?.fromString ?? 'From';
38
- this.toString = options?.toString ?? 'To';
39
-
40
- this.render();
41
-
42
- this.startTimeInput = this.queryInput('.timespan-start');
43
- this.endTimeInput = this.queryInput('.timespan-end');
44
-
45
- if (options?.defaultStart) {
46
- this.startTimeInput.value = options.defaultStart;
47
- }
48
- if (options?.defaultEnd) {
49
- this.endTimeInput.value = options.defaultEnd;
50
- }
51
-
52
- this.attachEventListeners();
53
-
54
- // Render initial state if defaults provided
55
- if (options?.defaultStart || options?.defaultEnd) {
56
- this.updateUI();
57
- }
58
- }
59
-
60
- private queryInput(selector: string): HTMLInputElement {
61
- const input = this.container.querySelector<HTMLInputElement>(selector);
62
- if (!input) {
63
- throw new Error(`Input with selector "${selector}" not found`);
64
- }
65
- return input;
66
- }
67
-
68
- private render(): void {
69
- const startId = `${this.uid}-start`;
70
- const endId = `${this.uid}-end`;
71
- this.container.innerHTML = `
72
- <div class="timespan-picker">
73
- <div class="timespan-field timespan-field-start">
74
- <label for="${startId}">${this.fromString}</label>
75
- <input type="time" class="timespan-start" id="${startId}"/>
76
- </div>
77
-
78
- <div class="timespan-center">
79
- <span class="timespan-arrow">→</span>
80
- <span class="timespan-duration"></span>
81
- </div>
82
-
83
- <div class="timespan-field timespan-field-end">
84
- <label for="${endId}">${this.toString}</label>
85
- <input type="time" class="timespan-end" id="${endId}"/>
86
- </div>
87
- </div>
88
- <div class="timespan-bar" aria-hidden="true">
89
- <div class="timespan-bar-fill"></div>
90
- </div>
91
- `;
92
- }
93
-
94
- private readonly handleStartChange = (): void => { this.handleChange(); };
95
- private readonly handleEndChange = (): void => { this.handleChange(); };
96
-
97
- private attachEventListeners(): void {
98
- this.startTimeInput.addEventListener('change', this.handleStartChange);
99
- this.endTimeInput.addEventListener('change', this.handleEndChange);
100
- }
101
-
102
- private toMinutes(time: string): number {
103
- const [h, m] = time.split(':').map(Number);
104
- return h * 60 + m;
105
- }
106
-
107
- private formatDuration(minutes: number): string {
108
- const h = Math.floor(minutes / 60);
109
- const m = minutes % 60;
110
- if (h && m) return `${h}h ${m}m`;
111
- if (h) return `${h}h`;
112
- return `${m}m`;
113
- }
114
-
115
- private updateUI(): void {
116
- const picker = this.container.querySelector<HTMLElement>('.timespan-picker');
117
- const durationEl = this.container.querySelector<HTMLElement>('.timespan-duration');
118
- const barFill = this.container.querySelector<HTMLElement>('.timespan-bar-fill');
119
-
120
- const start = this.startTimeInput.value;
121
- const end = this.endTimeInput.value;
122
- const isError = !!(start && end && start >= end);
123
-
124
- picker?.classList.toggle('is-error', isError);
125
-
126
- if (isError) {
127
- this.endTimeInput.setCustomValidity('End time must be after start time');
128
- if (durationEl) durationEl.textContent = '!';
129
- return;
130
- }
131
-
132
- this.endTimeInput.setCustomValidity('');
133
-
134
- if (start && end && durationEl && barFill) {
135
- const startMins = this.toMinutes(start);
136
- const endMins = this.toMinutes(end);
137
- const duration = endMins - startMins;
138
-
139
- durationEl.textContent = this.formatDuration(duration);
140
-
141
- const startPct = ((startMins / 1440) * 100).toFixed(2);
142
- const widthPct = ((duration / 1440) * 100).toFixed(2);
143
- barFill.style.left = `${startPct}%`;
144
- barFill.style.width = `${widthPct}%`;
145
- } else {
146
- if (durationEl) durationEl.textContent = '';
147
- if (barFill) {
148
- barFill.style.left = '0';
149
- barFill.style.width = '0';
150
- }
151
- }
152
- }
153
-
154
- private handleChange(): void {
155
- this.updateUI();
156
-
157
- const { start, end } = this.getValue();
158
- if (this.onChange && start && end) {
159
- this.onChange(start, end);
160
- }
161
- }
162
-
163
- public getValue(): TimeSpan {
164
- return {
165
- start: this.startTimeInput.value,
166
- end: this.endTimeInput.value
167
- };
168
- }
169
-
170
- public setValue(start: string, end: string): void {
171
- this.startTimeInput.value = start;
172
- this.endTimeInput.value = end;
173
- this.handleChange();
174
- }
175
-
176
- public reset(): void {
177
- this.startTimeInput.value = '';
178
- this.endTimeInput.value = '';
179
- this.endTimeInput.setCustomValidity('');
180
-
181
- const picker = this.container.querySelector('.timespan-picker');
182
- const durationEl = this.container.querySelector<HTMLElement>('.timespan-duration');
183
- const barFill = this.container.querySelector<HTMLElement>('.timespan-bar-fill');
184
-
185
- picker?.classList.remove('is-error');
186
- if (durationEl) durationEl.textContent = '';
187
- if (barFill) { barFill.style.left = '0'; barFill.style.width = '0'; }
188
- }
189
-
190
- public isValid(): boolean {
191
- const { start, end } = this.getValue();
192
- return !!(start && end && start < end);
193
- }
194
-
195
- public destroy(): void {
196
- this.startTimeInput.removeEventListener('change', this.handleStartChange);
197
- this.endTimeInput.removeEventListener('change', this.handleEndChange);
198
- }
199
- }
200
-
201
- export { TimeSpanPicker };
202
- export type { TimeSpan, TimeSpanPickerOptions };
package/js/toast.ts DELETED
@@ -1,134 +0,0 @@
1
- import { escapeHtml } from './utils.js';
2
-
3
- type ToastType = 'success' | 'error' | 'warning' | 'info';
4
-
5
- interface ToastOptions {
6
- content: string;
7
- header?: string;
8
- type?: ToastType;
9
- closeable?: boolean;
10
- }
11
-
12
- class Toast {
13
- private readonly content: string;
14
- private readonly header: string;
15
- private readonly type?: ToastType;
16
- private readonly closeable: boolean;
17
- private readonly closureIcon: string = '<div class="icon icon-close close"></div>';
18
- private readonly template: string;
19
- private toastElement: HTMLDivElement | null = null;
20
- private timerId: number | null = null;
21
-
22
- constructor(options: ToastOptions);
23
- constructor(content: string, header?: string, type?: ToastType, closeable?: boolean);
24
- constructor(
25
- contentOrOptions: string | ToastOptions,
26
- header: string = '',
27
- type?: ToastType,
28
- closeable: boolean = true
29
- ) {
30
- if (typeof contentOrOptions === 'object') {
31
- this.content = contentOrOptions.content;
32
- this.header = contentOrOptions.header ?? '';
33
- this.type = contentOrOptions.type;
34
- this.closeable = contentOrOptions.closeable ?? true;
35
- } else {
36
- this.content = contentOrOptions;
37
- this.header = header;
38
- this.type = type;
39
- this.closeable = closeable;
40
- }
41
-
42
- this.template = this.buildTemplate();
43
- }
44
-
45
- public show(ms?: number): void {
46
- const div = document.createElement('div');
47
- div.className = 'toast';
48
-
49
- if (this.type) {
50
- div.classList.add(this.type);
51
- }
52
-
53
- div.innerHTML = this.template;
54
- document.body.appendChild(div);
55
- this.toastElement = div;
56
-
57
- requestAnimationFrame(() => {
58
- requestAnimationFrame(() => {
59
- this.toastElement?.classList.add('show');
60
-
61
- const closeButton = this.toastElement?.querySelector<HTMLElement>('.close');
62
- if (closeButton) {
63
- closeButton.addEventListener('click', this.handleClose);
64
- }
65
-
66
- if (ms !== undefined && ms > 0) {
67
- this.startTimer(ms);
68
- }
69
- });
70
- });
71
- }
72
-
73
- public hide = (): void => {
74
- if (this.timerId !== null) {
75
- clearTimeout(this.timerId);
76
- this.timerId = null;
77
- }
78
-
79
- this.toastElement?.classList.remove('show');
80
-
81
- setTimeout(() => {
82
- const closeButton = this.toastElement?.querySelector<HTMLElement>('.close');
83
- if (closeButton) {
84
- closeButton.removeEventListener('click', this.handleClose);
85
- }
86
-
87
- this.toastElement?.remove();
88
- this.toastElement = null;
89
- }, 150);
90
- };
91
-
92
- private handleClose = (): void => {
93
- this.hide();
94
- };
95
-
96
- private startTimer(ms: number, elapsed: number = 0): void {
97
- const stepSize = 250;
98
-
99
- if (elapsed >= ms) {
100
- this.hide();
101
- return;
102
- }
103
-
104
- this.timerId = window.setTimeout(() => {
105
- elapsed += stepSize;
106
- const width = 100 - (100 / ms) * elapsed;
107
-
108
- const barElement = this.toastElement?.querySelector<HTMLElement>('.bar');
109
- if (barElement) {
110
- barElement.style.width = `${width}%`;
111
- this.startTimer(ms, elapsed);
112
- }
113
- }, stepSize);
114
- }
115
-
116
- private buildTemplate(): string {
117
- const parts: string[] = ['<div class="bar"></div>'];
118
-
119
- if (this.closeable) {
120
- parts.push(this.closureIcon);
121
- }
122
-
123
- if (this.header) {
124
- parts.push(`<div class="header">${escapeHtml(this.header)}</div>`);
125
- }
126
-
127
- parts.push(`<div class="content">${escapeHtml(this.content)}</div>`);
128
-
129
- return parts.join('');
130
- }
131
- }
132
-
133
- export { Toast };
134
- export type { ToastOptions, ToastType };
package/js/tooltip.ts DELETED
@@ -1,196 +0,0 @@
1
- // tooltip.ts
2
- import { computePosition } from './position.js';
3
- import type { Placement } from './position.js';
4
-
5
- interface TooltipOptions {
6
- position?: 'top' | 'bottom' | 'left' | 'right' | 'auto';
7
- offset?: number;
8
- delay?: number;
9
- className?: string;
10
- /** Set to true when content is trusted HTML (e.g. from data-tooltip-id).
11
- * Defaults to false — content is treated as plain text and escaped. */
12
- isHtml?: boolean;
13
- }
14
-
15
- class Tooltip {
16
- private static activeTooltip: Tooltip | null = null;
17
- private static idCounter: number = 0;
18
-
19
- private readonly trigger: HTMLElement;
20
- private readonly content: string;
21
- private readonly options: Required<TooltipOptions>;
22
- private tooltipElement: HTMLDivElement | null = null;
23
- private showTimeout: number | null = null;
24
- private isVisible: boolean = false;
25
-
26
- constructor(trigger: HTMLElement, content: string, options: TooltipOptions = {}) {
27
- this.trigger = trigger;
28
- this.content = content;
29
- this.options = {
30
- position: options.position ?? 'auto',
31
- offset: options.offset ?? 8,
32
- delay: options.delay ?? 0,
33
- className: options.className ?? '',
34
- isHtml: options.isHtml ?? false,
35
- };
36
-
37
- this.attachEvents();
38
- }
39
-
40
- public static initializeAll(): void {
41
- const triggers = document.querySelectorAll<HTMLElement>('[data-tooltip]');
42
- triggers.forEach(trigger => {
43
- const content = trigger.getAttribute('data-tooltip');
44
- const position = (trigger.getAttribute('data-tooltip-position') as TooltipOptions['position']) ?? 'auto';
45
- const className = trigger.getAttribute('data-tooltip-class') ?? '';
46
-
47
- if (content) {
48
- new Tooltip(trigger, content, { position, className, isHtml: false });
49
- }
50
- });
51
-
52
- // Also support content from separate elements
53
- const advancedTriggers = document.querySelectorAll<HTMLElement>('[data-tooltip-id]');
54
- advancedTriggers.forEach(trigger => {
55
- const contentId = trigger.getAttribute('data-tooltip-id');
56
- const position = (trigger.getAttribute('data-tooltip-position') as TooltipOptions['position']) ?? 'auto';
57
- const className = trigger.getAttribute('data-tooltip-class') ?? '';
58
-
59
- if (contentId) {
60
- const contentElement = document.getElementById(contentId);
61
- if (contentElement) {
62
- const content = contentElement.innerHTML;
63
- new Tooltip(trigger, content, { position, className, isHtml: true });
64
- }
65
- }
66
- });
67
- }
68
-
69
- public show(): void {
70
- if (this.showTimeout !== null) {
71
- clearTimeout(this.showTimeout);
72
- }
73
-
74
- this.showTimeout = window.setTimeout(() => {
75
- Tooltip.hideActive();
76
- this.createTooltip();
77
- this.position();
78
-
79
- requestAnimationFrame(() => {
80
- this.tooltipElement?.classList.add('visible');
81
- this.isVisible = true;
82
- Tooltip.activeTooltip = this;
83
- });
84
- }, this.options.delay);
85
- }
86
-
87
- public hide(): void {
88
- if (this.showTimeout !== null) {
89
- clearTimeout(this.showTimeout);
90
- this.showTimeout = null;
91
- }
92
-
93
- if (!this.tooltipElement) return;
94
-
95
- this.tooltipElement.classList.remove('visible');
96
- this.isVisible = false;
97
-
98
- setTimeout(() => {
99
- this.tooltipElement?.remove();
100
- this.tooltipElement = null;
101
-
102
- if (Tooltip.activeTooltip === this) {
103
- Tooltip.activeTooltip = null;
104
- }
105
- }, 200);
106
- }
107
-
108
- private static hideActive(): void {
109
- if (Tooltip.activeTooltip) {
110
- Tooltip.activeTooltip.hide();
111
- }
112
- }
113
-
114
- private createTooltip(): void {
115
- const tooltip = document.createElement('div');
116
- tooltip.className = 'tooltip';
117
- tooltip.id = `tooltip-${++Tooltip.idCounter}`;
118
- tooltip.setAttribute('role', 'tooltip');
119
-
120
- const tooltipContent = document.createElement('div');
121
- tooltipContent.className = 'tooltip-content';
122
- if (this.options.isHtml) {
123
- tooltipContent.innerHTML = this.content;
124
- } else {
125
- tooltipContent.textContent = this.content;
126
- }
127
- tooltip.appendChild(tooltipContent);
128
-
129
- if (this.options.className) {
130
- tooltip.classList.add(this.options.className);
131
- }
132
-
133
- document.body.appendChild(tooltip);
134
- this.tooltipElement = tooltip;
135
-
136
- const previousDescribedBy = this.trigger.getAttribute('aria-describedby');
137
- this.trigger.setAttribute('aria-describedby', tooltip.id);
138
- this.trigger.setAttribute('data-previous-describedby', previousDescribedBy || '');
139
- }
140
-
141
- private position(): void {
142
- if (!this.tooltipElement) return;
143
-
144
- const { left, top, placement } = computePosition(
145
- this.trigger.getBoundingClientRect(),
146
- this.tooltipElement.getBoundingClientRect(),
147
- { placement: this.options.position, offset: this.options.offset }
148
- );
149
-
150
- this.tooltipElement.style.left = `${left}px`;
151
- this.tooltipElement.style.top = `${top}px`;
152
- this.tooltipElement.setAttribute('data-position', placement);
153
- }
154
-
155
- private attachEvents(): void {
156
- this.trigger.addEventListener('mouseenter', this.handleMouseEnter);
157
- this.trigger.addEventListener('mouseleave', this.handleMouseLeave);
158
- this.trigger.addEventListener('focus', this.handleFocus);
159
- this.trigger.addEventListener('blur', this.handleBlur);
160
- }
161
-
162
- private handleMouseEnter = (): void => {
163
- this.show();
164
- };
165
-
166
- private handleMouseLeave = (): void => {
167
- this.hide();
168
- };
169
-
170
- private handleFocus = (): void => {
171
- this.show();
172
- };
173
-
174
- private handleBlur = (): void => {
175
- this.hide();
176
- };
177
-
178
- public destroy(): void {
179
- this.hide();
180
- this.trigger.removeEventListener('mouseenter', this.handleMouseEnter);
181
- this.trigger.removeEventListener('mouseleave', this.handleMouseLeave);
182
- this.trigger.removeEventListener('focus', this.handleFocus);
183
- this.trigger.removeEventListener('blur', this.handleBlur);
184
-
185
- const previousDescribedBy = this.trigger.getAttribute('data-previous-describedby');
186
- if (previousDescribedBy) {
187
- this.trigger.setAttribute('aria-describedby', previousDescribedBy);
188
- } else {
189
- this.trigger.removeAttribute('aria-describedby');
190
- }
191
- this.trigger.removeAttribute('data-previous-describedby');
192
- }
193
- }
194
-
195
- export { Tooltip };
196
- export type { TooltipOptions };