@stimulus-plumbers/controllers 0.2.8 → 0.3.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/README.md +3 -0
- package/dist/stimulus-plumbers-controllers.es.js +450 -436
- package/dist/stimulus-plumbers-controllers.umd.js +1 -1
- package/package.json +1 -1
- package/src/controllers/calendar_month_controller.js +3 -1
- package/src/controllers/calendar_month_observer_controller.js +1 -1
- package/src/controllers/clipboard_controller.js +1 -1
- package/src/controllers/combobox_date_controller.js +2 -2
- package/src/controllers/combobox_dropdown_controller.js +25 -16
- package/src/controllers/combobox_time_controller.js +1 -1
- package/src/controllers/flipper_controller.js +1 -1
- package/src/controllers/input_combobox_controller.js +33 -24
- package/src/controllers/input_format_controller.js +18 -23
- package/src/controllers/input_search_controller.js +44 -0
- package/src/controllers/modal_controller.js +20 -19
- package/src/index.js +11 -4
- package/src/plumbers/calendar.js +1 -1
- package/src/plumbers/content_loader.js +9 -63
- package/src/plumbers/dismisser.js +3 -51
- package/src/plumbers/flipper.js +12 -120
- package/src/plumbers/formatter.js +65 -0
- package/src/plumbers/{input_format/formatters → formatters}/credit_card.js +1 -1
- package/src/plumbers/{input_format/formatters → formatters}/currency.js +1 -1
- package/src/plumbers/{input_format/formatters → formatters}/date.js +2 -2
- package/src/plumbers/{input_format/formatters → formatters}/phone.js +1 -1
- package/src/plumbers/{input_format/formatters → formatters}/plain.js +1 -1
- package/src/plumbers/{input_format/formatters → formatters}/time.js +2 -2
- package/src/plumbers/index.js +1 -2
- package/src/plumbers/plumber/config.js +6 -0
- package/src/plumbers/plumber/date.js +14 -0
- package/src/plumbers/plumber/geometry.js +36 -0
- package/src/plumbers/plumber/index.js +2 -1
- package/src/plumbers/plumber/window_observer.js +22 -0
- package/src/plumbers/shifter.js +8 -80
- package/src/plumbers/visibility.js +1 -1
- package/src/requestor.js +24 -0
- package/src/researcher.js +39 -0
- package/src/plumbers/combobox_dropdown.js +0 -60
- package/src/plumbers/input_format/index.js +0 -90
- package/src/plumbers/plumber/support.js +0 -101
- /package/src/{aria.js → accessibility/aria.js} +0 -0
- /package/src/{focus.js → accessibility/focus.js} +0 -0
- /package/src/{keyboard.js → accessibility/keyboard.js} +0 -0
package/src/plumbers/index.js
CHANGED
|
@@ -3,10 +3,9 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
export { initCalendar } from './calendar';
|
|
6
|
-
export { initComboboxDropdown } from './combobox_dropdown';
|
|
7
6
|
export { attachContentLoader } from './content_loader';
|
|
8
7
|
export { attachDismisser } from './dismisser';
|
|
9
8
|
export { attachFlipper } from './flipper';
|
|
10
|
-
export {
|
|
9
|
+
export { attachFormatter } from './formatter';
|
|
11
10
|
export { attachShifter } from './shifter';
|
|
12
11
|
export { attachVisibility } from './visibility';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function isValidDate(value) {
|
|
2
|
+
return value instanceof Date && !isNaN(value);
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function tryParseDate(...values) {
|
|
6
|
+
if (values.length === 0) throw 'Missing values to parse as date';
|
|
7
|
+
if (values.length === 1) {
|
|
8
|
+
const parsed = new Date(values[0]);
|
|
9
|
+
if (values[0] && isValidDate(parsed)) return parsed;
|
|
10
|
+
} else {
|
|
11
|
+
const parsed = new Date(...values);
|
|
12
|
+
if (isValidDate(parsed)) return parsed;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export const directionMap = {
|
|
2
|
+
get top() {
|
|
3
|
+
return 'bottom';
|
|
4
|
+
},
|
|
5
|
+
get bottom() {
|
|
6
|
+
return 'top';
|
|
7
|
+
},
|
|
8
|
+
get left() {
|
|
9
|
+
return 'right';
|
|
10
|
+
},
|
|
11
|
+
get right() {
|
|
12
|
+
return 'left';
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function defineRect({ x, y, width, height }) {
|
|
17
|
+
return { x, y, width, height, left: x, right: x + width, top: y, bottom: y + height };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function viewportRect() {
|
|
21
|
+
return defineRect({
|
|
22
|
+
x: 0,
|
|
23
|
+
y: 0,
|
|
24
|
+
width: window.innerWidth || document.documentElement.clientWidth,
|
|
25
|
+
height: window.innerHeight || document.documentElement.clientHeight,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function isWithinViewport(target) {
|
|
30
|
+
if (!(target instanceof HTMLElement)) return false;
|
|
31
|
+
const outer = viewportRect();
|
|
32
|
+
const inner = target.getBoundingClientRect();
|
|
33
|
+
const vertical = inner.top <= outer.height && inner.top + inner.height > 0;
|
|
34
|
+
const horizontal = inner.left <= outer.width && inner.left + inner.width > 0;
|
|
35
|
+
return vertical && horizontal;
|
|
36
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import Plumber from './index';
|
|
2
|
+
|
|
3
|
+
export default class WindowObserver extends Plumber {
|
|
4
|
+
observe(handler) {
|
|
5
|
+
this._handler = handler;
|
|
6
|
+
this.events.forEach((e) => window.addEventListener(e, handler, true));
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
unobserve() {
|
|
10
|
+
if (!this._handler) return;
|
|
11
|
+
this.events.forEach((e) => window.removeEventListener(e, this._handler, true));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
enhance() {
|
|
15
|
+
const context = this;
|
|
16
|
+
const superDisconnect = this.controller.disconnect?.bind(this.controller) || (() => {});
|
|
17
|
+
this.controller.disconnect = () => {
|
|
18
|
+
context.unobserve();
|
|
19
|
+
superDisconnect();
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
}
|
package/src/plumbers/shifter.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { viewportRect, directionMap, defineRect } from './plumber/
|
|
1
|
+
import WindowObserver from './plumber/window_observer';
|
|
2
|
+
import { viewportRect, directionMap, defineRect } from './plumber/geometry';
|
|
3
3
|
|
|
4
4
|
const defaultOptions = {
|
|
5
5
|
events: ['resize'],
|
|
@@ -8,16 +8,7 @@ const defaultOptions = {
|
|
|
8
8
|
respectMotion: true,
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
export class Shifter extends
|
|
12
|
-
/**
|
|
13
|
-
* Creates a new Shifter plumber instance for viewport boundary shifting.
|
|
14
|
-
* @param {Object} controller - Stimulus controller instance
|
|
15
|
-
* @param {Object} [options] - Configuration options
|
|
16
|
-
* @param {string[]} [options.events=['resize']] - Events triggering shift calculation
|
|
17
|
-
* @param {string[]} [options.boundaries=['top','left','right']] - Boundaries to check (valid values: 'top', 'bottom', 'left', 'right')
|
|
18
|
-
* @param {string} [options.onShifted='shifted'] - Callback name when shifted
|
|
19
|
-
* @param {boolean} [options.respectMotion=true] - Respect prefers-reduced-motion preference
|
|
20
|
-
*/
|
|
11
|
+
export class Shifter extends WindowObserver {
|
|
21
12
|
constructor(controller, options = {}) {
|
|
22
13
|
super(controller, options);
|
|
23
14
|
|
|
@@ -29,13 +20,9 @@ export class Shifter extends Plumber {
|
|
|
29
20
|
this.prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
30
21
|
|
|
31
22
|
this.enhance();
|
|
32
|
-
this.observe();
|
|
23
|
+
this.observe(this.shift);
|
|
33
24
|
}
|
|
34
25
|
|
|
35
|
-
/**
|
|
36
|
-
* Calculates and applies transform to shift element within viewport boundaries.
|
|
37
|
-
* @returns {Promise<void>}
|
|
38
|
-
*/
|
|
39
26
|
shift = async () => {
|
|
40
27
|
if (!this.visible) return;
|
|
41
28
|
|
|
@@ -44,21 +31,13 @@ export class Shifter extends Plumber {
|
|
|
44
31
|
const translateX = overflow['left'] || overflow['right'] || 0;
|
|
45
32
|
const translateY = overflow['top'] || overflow['bottom'] || 0;
|
|
46
33
|
|
|
47
|
-
// Disable transitions for users who prefer reduced motion
|
|
48
34
|
this.element.style.transition = this.respectMotion && this.prefersReducedMotion ? 'none' : '';
|
|
49
|
-
|
|
50
35
|
this.element.style.transform = `translate(${translateX}px, ${translateY}px)`;
|
|
51
36
|
|
|
52
37
|
await this.awaitCallback(this.onShifted, overflow);
|
|
53
38
|
this.dispatch('shifted', { detail: overflow });
|
|
54
39
|
};
|
|
55
40
|
|
|
56
|
-
/**
|
|
57
|
-
* Calculates overflow distances for each boundary direction.
|
|
58
|
-
* @param {DOMRect} targetRect - Target element's bounding rect
|
|
59
|
-
* @param {Object} translations - Current transform translations
|
|
60
|
-
* @returns {Object} Overflow distances by direction
|
|
61
|
-
*/
|
|
62
41
|
overflowRect(targetRect, translations) {
|
|
63
42
|
const overflow = {};
|
|
64
43
|
const viewport = viewportRect();
|
|
@@ -81,14 +60,6 @@ export class Shifter extends Plumber {
|
|
|
81
60
|
return overflow;
|
|
82
61
|
}
|
|
83
62
|
|
|
84
|
-
/**
|
|
85
|
-
* Calculates distance from inner rect to outer boundary in given direction.
|
|
86
|
-
* @param {Object} inner - Inner rect object
|
|
87
|
-
* @param {string} direction - Direction ('top', 'bottom', 'left', 'right')
|
|
88
|
-
* @param {Object} outer - Outer rect object
|
|
89
|
-
* @returns {number} Distance to boundary (negative if overflowing)
|
|
90
|
-
* @throws {string} If direction is invalid
|
|
91
|
-
*/
|
|
92
63
|
directionDistance(inner, direction, outer) {
|
|
93
64
|
switch (direction) {
|
|
94
65
|
case 'top':
|
|
@@ -102,63 +73,20 @@ export class Shifter extends Plumber {
|
|
|
102
73
|
}
|
|
103
74
|
}
|
|
104
75
|
|
|
105
|
-
/**
|
|
106
|
-
* Extracts current translate values from element's transform style.
|
|
107
|
-
* @param {HTMLElement} target - Target element
|
|
108
|
-
* @returns {Object} Translation object with x and y values
|
|
109
|
-
*/
|
|
110
76
|
elementTranslations(target) {
|
|
111
77
|
const style = window.getComputedStyle(target);
|
|
112
78
|
const matrix = style['transform'] || style['webkitTransform'] || style['mozTransform'];
|
|
113
|
-
|
|
114
|
-
if (matrix === 'none' || typeof matrix === 'undefined') {
|
|
115
|
-
return { x: 0, y: 0 };
|
|
116
|
-
}
|
|
117
|
-
|
|
79
|
+
if (matrix === 'none' || typeof matrix === 'undefined') return { x: 0, y: 0 };
|
|
118
80
|
const matrixType = matrix.includes('3d') ? '3d' : '2d';
|
|
119
81
|
const matrixValues = matrix.match(/matrix.*\((.+)\)/)[1].split(', ');
|
|
120
|
-
|
|
121
|
-
if (matrixType === '2d') {
|
|
122
|
-
return { x: Number(matrixValues[4]), y: Number(matrixValues[5]) };
|
|
123
|
-
}
|
|
82
|
+
if (matrixType === '2d') return { x: Number(matrixValues[4]), y: Number(matrixValues[5]) };
|
|
124
83
|
return { x: 0, y: 0 };
|
|
125
84
|
}
|
|
126
85
|
|
|
127
|
-
/**
|
|
128
|
-
* Starts observing configured events for shifting.
|
|
129
|
-
*/
|
|
130
|
-
observe() {
|
|
131
|
-
this.events.forEach((event) => {
|
|
132
|
-
window.addEventListener(event, this.shift, true);
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Stops observing events for shifting.
|
|
138
|
-
*/
|
|
139
|
-
unobserve() {
|
|
140
|
-
this.events.forEach((event) => {
|
|
141
|
-
window.removeEventListener(event, this.shift, true);
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
|
|
145
86
|
enhance() {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
Object.assign(this.controller, {
|
|
149
|
-
disconnect: () => {
|
|
150
|
-
context.unobserve();
|
|
151
|
-
superDisconnect();
|
|
152
|
-
},
|
|
153
|
-
shift: context.shift.bind(context),
|
|
154
|
-
});
|
|
87
|
+
super.enhance();
|
|
88
|
+
this.controller.shift = this.shift;
|
|
155
89
|
}
|
|
156
90
|
}
|
|
157
91
|
|
|
158
|
-
/**
|
|
159
|
-
* Factory function to create and attach a Shifter plumber to a controller.
|
|
160
|
-
* @param {Object} controller - Stimulus controller instance
|
|
161
|
-
* @param {Object} [options] - Configuration options
|
|
162
|
-
* @returns {Shifter} Shifter plumber instance
|
|
163
|
-
*/
|
|
164
92
|
export const attachShifter = (controller, options) => new Shifter(controller, options);
|
package/src/requestor.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export class Requestor {
|
|
2
|
+
constructor() {
|
|
3
|
+
this._abortController = null;
|
|
4
|
+
this._timer = null;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
schedule(fn, delay) {
|
|
8
|
+
clearTimeout(this._timer);
|
|
9
|
+
this._timer = setTimeout(fn, delay);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async request(url, options = {}) {
|
|
13
|
+
this._abortController?.abort();
|
|
14
|
+
this._abortController = new AbortController();
|
|
15
|
+
const res = await fetch(url, { ...options, signal: this._abortController.signal });
|
|
16
|
+
if (!res.ok) throw new Error(`${res.status}`);
|
|
17
|
+
return res;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
cancel() {
|
|
21
|
+
clearTimeout(this._timer);
|
|
22
|
+
this._abortController?.abort();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export function fuzzyMatcher(needle, haystack) {
|
|
2
|
+
let ni = 0;
|
|
3
|
+
for (let i = 0; i < haystack.length && ni < needle.length; i++) {
|
|
4
|
+
if (haystack[i] === needle[ni]) ni++;
|
|
5
|
+
}
|
|
6
|
+
return ni === needle.length;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function matchContains(needle, haystack) {
|
|
10
|
+
return haystack.includes(needle);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function matchPrefix(needle, haystack) {
|
|
14
|
+
return haystack.startsWith(needle);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getStrategy(strategy) {
|
|
18
|
+
if (strategy === 'contains') return matchContains;
|
|
19
|
+
if (strategy === 'prefix') return matchPrefix;
|
|
20
|
+
return fuzzyMatcher;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function extractDOMValue(el, field) {
|
|
24
|
+
if (field === 'textContent') return el.textContent?.trim().toLowerCase() ?? '';
|
|
25
|
+
return (el.getAttribute(field) ?? '').toLowerCase();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function filterOptions(listbox, query, options = {}) {
|
|
29
|
+
const { strategy = 'fuzzy', matcher, fields = ['textContent'] } = options;
|
|
30
|
+
const matchFn = typeof matcher === 'function' ? matcher : getStrategy(strategy);
|
|
31
|
+
const needle = query.toLowerCase();
|
|
32
|
+
let visible = 0;
|
|
33
|
+
listbox.querySelectorAll('[role="option"]').forEach((opt) => {
|
|
34
|
+
const match = fields.some((field) => matchFn(needle, extractDOMValue(opt, field)));
|
|
35
|
+
opt.hidden = !match;
|
|
36
|
+
if (match) visible++;
|
|
37
|
+
});
|
|
38
|
+
return visible;
|
|
39
|
+
}
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import Plumber from './plumber';
|
|
2
|
-
|
|
3
|
-
export class ComboboxDropdown extends Plumber {
|
|
4
|
-
constructor(controller, options = {}) {
|
|
5
|
-
super(controller, options);
|
|
6
|
-
this.debounceTimer = null;
|
|
7
|
-
this.abortController = null;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
fuzzyFilter(listbox, query) {
|
|
11
|
-
const needle = query.toLowerCase();
|
|
12
|
-
let visible = 0;
|
|
13
|
-
listbox.querySelectorAll('[role="option"]').forEach((opt) => {
|
|
14
|
-
const match = this.fuzzyMatch(needle, opt.textContent.trim().toLowerCase());
|
|
15
|
-
opt.hidden = !match;
|
|
16
|
-
if (match) visible++;
|
|
17
|
-
});
|
|
18
|
-
return visible;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
fuzzyMatch(needle, haystack) {
|
|
22
|
-
let ni = 0;
|
|
23
|
-
for (let i = 0; i < haystack.length && ni < needle.length; i++) {
|
|
24
|
-
if (haystack[i] === needle[ni]) ni++;
|
|
25
|
-
}
|
|
26
|
-
return ni === needle.length;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
scheduleFetch(query, delay, callback) {
|
|
30
|
-
clearTimeout(this.debounceTimer);
|
|
31
|
-
this.debounceTimer = setTimeout(() => this.fetch(query, callback), delay);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
async fetch(query, { url, field, onLoading, onLoaded, onError }) {
|
|
35
|
-
this.abortController?.abort();
|
|
36
|
-
this.abortController = new AbortController();
|
|
37
|
-
onLoading?.(true);
|
|
38
|
-
const fetchUrl = new URL(url, window.location.href);
|
|
39
|
-
fetchUrl.searchParams.set(field, query);
|
|
40
|
-
try {
|
|
41
|
-
const res = await fetch(fetchUrl, {
|
|
42
|
-
signal: this.abortController.signal,
|
|
43
|
-
headers: { Accept: 'text/html', 'X-Requested-With': 'XMLHttpRequest' },
|
|
44
|
-
});
|
|
45
|
-
if (!res.ok) throw new Error(`${res.status}`);
|
|
46
|
-
onLoaded?.(await res.text());
|
|
47
|
-
} catch (err) {
|
|
48
|
-
if (err.name !== 'AbortError') onError?.(err);
|
|
49
|
-
} finally {
|
|
50
|
-
onLoading?.(false);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
cancel() {
|
|
55
|
-
clearTimeout(this.debounceTimer);
|
|
56
|
-
this.abortController?.abort();
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export const initComboboxDropdown = (controller, options) => new ComboboxDropdown(controller, options);
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import Plumber from '../plumber';
|
|
2
|
-
import { PlainInputFormatter } from './formatters/plain';
|
|
3
|
-
import { CreditCardInputFormatter } from './formatters/credit_card';
|
|
4
|
-
import { PhoneInputFormatter } from './formatters/phone';
|
|
5
|
-
import { CurrencyInputFormatter } from './formatters/currency';
|
|
6
|
-
import { DateInputFormatter } from './formatters/date';
|
|
7
|
-
import { TimeInputFormatter } from './formatters/time';
|
|
8
|
-
|
|
9
|
-
export { PlainInputFormatter } from './formatters/plain';
|
|
10
|
-
export { CreditCardInputFormatter } from './formatters/credit_card';
|
|
11
|
-
export { PhoneInputFormatter } from './formatters/phone';
|
|
12
|
-
export { CurrencyInputFormatter } from './formatters/currency';
|
|
13
|
-
export { DateInputFormatter } from './formatters/date';
|
|
14
|
-
export { TimeInputFormatter } from './formatters/time';
|
|
15
|
-
|
|
16
|
-
export const FORMATTER_TYPES = {
|
|
17
|
-
PLAIN: 'plain',
|
|
18
|
-
CREDIT_CARD: 'creditCard',
|
|
19
|
-
PHONE: 'phone',
|
|
20
|
-
CURRENCY: 'currency',
|
|
21
|
-
DATE: 'date',
|
|
22
|
-
TIME: 'time',
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
const registry = new Map([
|
|
26
|
-
[FORMATTER_TYPES.PLAIN, PlainInputFormatter],
|
|
27
|
-
[FORMATTER_TYPES.CREDIT_CARD, CreditCardInputFormatter],
|
|
28
|
-
[FORMATTER_TYPES.PHONE, PhoneInputFormatter],
|
|
29
|
-
[FORMATTER_TYPES.CURRENCY, CurrencyInputFormatter],
|
|
30
|
-
[FORMATTER_TYPES.DATE, DateInputFormatter],
|
|
31
|
-
[FORMATTER_TYPES.TIME, TimeInputFormatter],
|
|
32
|
-
]);
|
|
33
|
-
|
|
34
|
-
const defaultOptions = {
|
|
35
|
-
type: FORMATTER_TYPES.PLAIN,
|
|
36
|
-
options: {},
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
export class InputFormat extends Plumber {
|
|
40
|
-
/**
|
|
41
|
-
* Registers a custom input formatter for a given type identifier.
|
|
42
|
-
* @param {string} type - The type identifier (e.g. 'iban')
|
|
43
|
-
* @param {Object} formatter - Object with normalize, validate, and optionally format/mask methods
|
|
44
|
-
*/
|
|
45
|
-
static register(type, formatter) {
|
|
46
|
-
registry.set(type, formatter);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Creates a new InputFormat plumber instance.
|
|
51
|
-
* @param {Object} controller - Stimulus controller instance
|
|
52
|
-
* @param {Object} [options] - Configuration options
|
|
53
|
-
* @param {string} [options.type='plain'] - Formatter type identifier
|
|
54
|
-
* @param {Object} [options.options={}] - Type-specific options (e.g. locale, currency)
|
|
55
|
-
*/
|
|
56
|
-
constructor(controller, options = {}) {
|
|
57
|
-
super(controller, options);
|
|
58
|
-
this.type = options.type ?? defaultOptions.type;
|
|
59
|
-
this.options = options.options ?? defaultOptions.options;
|
|
60
|
-
this.enhance();
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
enhance() {
|
|
64
|
-
const context = this;
|
|
65
|
-
const formatter = registry.get(context.type) ?? registry.get(FORMATTER_TYPES.PLAIN);
|
|
66
|
-
|
|
67
|
-
const helpers = {
|
|
68
|
-
normalize: (raw) => formatter.normalize?.(raw, context.options) ?? (typeof raw === 'string' ? raw : ''),
|
|
69
|
-
validate: (value) => formatter.validate?.(value, context.options) ?? true,
|
|
70
|
-
format: (value) => formatter.format?.(value, context.options) ?? (typeof value === 'string' ? value : ''),
|
|
71
|
-
mask: (value) => formatter.mask?.(value, context.options) ?? null,
|
|
72
|
-
maskable: () => typeof formatter.mask === 'function',
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
Object.defineProperty(this.controller, 'inputFormat', {
|
|
76
|
-
get() {
|
|
77
|
-
return helpers;
|
|
78
|
-
},
|
|
79
|
-
configurable: true,
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Factory function to create and attach an InputFormat plumber to a controller.
|
|
86
|
-
* @param {Object} controller - Stimulus controller instance
|
|
87
|
-
* @param {Object} [options] - Configuration options
|
|
88
|
-
* @returns {InputFormat} InputFormat plumber instance
|
|
89
|
-
*/
|
|
90
|
-
export const attachInputFormat = (controller, options) => new InputFormat(controller, options);
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
export const visibilityConfig = {
|
|
2
|
-
get visibleOnly() {
|
|
3
|
-
return true;
|
|
4
|
-
},
|
|
5
|
-
hiddenClass: null,
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Maps each direction to its opposite direction.
|
|
10
|
-
* Used for flipping and boundary calculations.
|
|
11
|
-
*/
|
|
12
|
-
export const directionMap = {
|
|
13
|
-
get top() {
|
|
14
|
-
return 'bottom';
|
|
15
|
-
},
|
|
16
|
-
get bottom() {
|
|
17
|
-
return 'top';
|
|
18
|
-
},
|
|
19
|
-
get left() {
|
|
20
|
-
return 'right';
|
|
21
|
-
},
|
|
22
|
-
get right() {
|
|
23
|
-
return 'left';
|
|
24
|
-
},
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Creates a rect object with position and dimension properties.
|
|
29
|
-
* @param {Object} params - Rectangle parameters
|
|
30
|
-
* @param {number} params.x - X coordinate
|
|
31
|
-
* @param {number} params.y - Y coordinate
|
|
32
|
-
* @param {number} params.width - Width
|
|
33
|
-
* @param {number} params.height - Height
|
|
34
|
-
* @returns {Object} Rect object with x, y, width, height, left, right, top, bottom properties
|
|
35
|
-
*/
|
|
36
|
-
export function defineRect({ x, y, width, height }) {
|
|
37
|
-
return {
|
|
38
|
-
x: x,
|
|
39
|
-
y: y,
|
|
40
|
-
width: width,
|
|
41
|
-
height: height,
|
|
42
|
-
left: x,
|
|
43
|
-
right: x + width,
|
|
44
|
-
top: y,
|
|
45
|
-
bottom: y + height,
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Returns the current viewport dimensions as a rect object.
|
|
51
|
-
* @returns {Object} Viewport rect with dimensions and boundaries
|
|
52
|
-
*/
|
|
53
|
-
export function viewportRect() {
|
|
54
|
-
return defineRect({
|
|
55
|
-
x: 0,
|
|
56
|
-
y: 0,
|
|
57
|
-
width: window.innerWidth || document.documentElement.clientWidth,
|
|
58
|
-
height: window.innerHeight || document.documentElement.clientHeight,
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Checks if an element is within the visible viewport.
|
|
64
|
-
* @param {HTMLElement} target - Element to check
|
|
65
|
-
* @returns {boolean} True if element is within viewport
|
|
66
|
-
*/
|
|
67
|
-
export function isWithinViewport(target) {
|
|
68
|
-
if (!(target instanceof HTMLElement)) return false;
|
|
69
|
-
|
|
70
|
-
const outer = viewportRect();
|
|
71
|
-
const inner = target.getBoundingClientRect();
|
|
72
|
-
const vertical = inner.top <= outer.height && inner.top + inner.height > 0;
|
|
73
|
-
const horizontal = inner.left <= outer.width && inner.left + inner.width > 0;
|
|
74
|
-
return vertical && horizontal;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Validates if a value is a valid Date object.
|
|
79
|
-
* @param {*} value - Value to check
|
|
80
|
-
* @returns {boolean} True if value is a valid Date
|
|
81
|
-
*/
|
|
82
|
-
export function isValidDate(value) {
|
|
83
|
-
return value instanceof Date && !isNaN(value);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Attempts to parse values into a Date object.
|
|
88
|
-
* @param {...*} values - Date values to parse
|
|
89
|
-
* @returns {Date|undefined} Parsed Date object or undefined if invalid
|
|
90
|
-
* @throws {string} If no values provided
|
|
91
|
-
*/
|
|
92
|
-
export function tryParseDate(...values) {
|
|
93
|
-
if (values.length === 0) throw 'Missing values to parse as date';
|
|
94
|
-
if (values.length === 1) {
|
|
95
|
-
const parsed = new Date(values[0]);
|
|
96
|
-
if (values[0] && isValidDate(parsed)) return parsed;
|
|
97
|
-
} else {
|
|
98
|
-
const parsed = new Date(...values);
|
|
99
|
-
if (isValidDate(parsed)) return parsed;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|