fix: DockerSecretsResolver - don't normalize absolute paths like /var/www/html/...
Some checks failed
Deploy Application / deploy (push) Has been cancelled
Some checks failed
Deploy Application / deploy (push) Has been cancelled
This commit is contained in:
299
resources/js/modules/livecomponent/TriggerManager.js
Normal file
299
resources/js/modules/livecomponent/TriggerManager.js
Normal file
@@ -0,0 +1,299 @@
|
||||
/**
|
||||
* Trigger Manager for LiveComponents
|
||||
*
|
||||
* Handles advanced trigger options:
|
||||
* - delay: Delay before execution
|
||||
* - throttle: Throttle instead of debounce
|
||||
* - once: Execute only once
|
||||
* - changed: Only execute on value change
|
||||
* - from: Event from another element
|
||||
* - load: Execute on element load
|
||||
*/
|
||||
|
||||
export class TriggerManager {
|
||||
constructor() {
|
||||
this.triggeredOnce = new Set(); // Track elements that have triggered once
|
||||
this.throttleTimers = new Map(); // Track throttle timers
|
||||
this.lastValues = new Map(); // Track last values for 'changed' trigger
|
||||
this.loadHandled = new Set(); // Track elements that have handled load event
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup trigger for an element
|
||||
*
|
||||
* @param {HTMLElement} element - Element to setup trigger for
|
||||
* @param {string} componentId - Component ID
|
||||
* @param {string} action - Action name
|
||||
* @param {Function} handler - Action handler function
|
||||
* @param {string} defaultEvent - Default event type (click, input, change, etc.)
|
||||
*/
|
||||
setupTrigger(element, componentId, action, handler, defaultEvent = 'click') {
|
||||
// Parse trigger options
|
||||
const delay = this.parseDelay(element.dataset.lcTriggerDelay);
|
||||
const throttle = this.parseThrottle(element.dataset.lcTriggerThrottle);
|
||||
const once = element.dataset.lcTriggerOnce === 'true';
|
||||
const changed = element.dataset.lcTriggerChanged === 'true';
|
||||
const from = element.dataset.lcTriggerFrom;
|
||||
const load = element.dataset.lcTriggerLoad === 'true';
|
||||
|
||||
// Handle 'once' trigger - check if already triggered
|
||||
if (once) {
|
||||
const triggerKey = `${componentId}_${action}_${this.getElementKey(element)}`;
|
||||
if (this.triggeredOnce.has(triggerKey)) {
|
||||
return; // Already triggered, don't setup again
|
||||
}
|
||||
}
|
||||
|
||||
// Handle 'load' trigger
|
||||
if (load) {
|
||||
const loadKey = `${componentId}_${action}_${this.getElementKey(element)}`;
|
||||
if (!this.loadHandled.has(loadKey)) {
|
||||
this.loadHandled.add(loadKey);
|
||||
// Execute immediately if element is already loaded
|
||||
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
||||
this.executeWithOptions(element, componentId, action, handler, delay, throttle, changed);
|
||||
} else {
|
||||
// Wait for load
|
||||
window.addEventListener('load', () => {
|
||||
this.executeWithOptions(element, componentId, action, handler, delay, throttle, changed);
|
||||
}, { once: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle 'from' trigger - delegate event from another element
|
||||
if (from) {
|
||||
const sourceElement = document.querySelector(from);
|
||||
if (sourceElement) {
|
||||
this.setupDelegatedTrigger(sourceElement, element, componentId, action, handler, defaultEvent, delay, throttle, changed);
|
||||
return; // Don't setup direct trigger
|
||||
} else {
|
||||
console.warn(`[TriggerManager] Source element not found for trigger-from: ${from}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Setup direct trigger
|
||||
const eventType = this.getEventType(element, defaultEvent);
|
||||
const wrappedHandler = (e) => {
|
||||
// Check 'once' trigger
|
||||
if (once) {
|
||||
const triggerKey = `${componentId}_${action}_${this.getElementKey(element)}`;
|
||||
if (this.triggeredOnce.has(triggerKey)) {
|
||||
return; // Already triggered
|
||||
}
|
||||
this.triggeredOnce.add(triggerKey);
|
||||
}
|
||||
|
||||
this.executeWithOptions(element, componentId, action, handler, delay, throttle, changed, e);
|
||||
};
|
||||
|
||||
element.addEventListener(eventType, wrappedHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup delegated trigger (trigger-from)
|
||||
*
|
||||
* @param {HTMLElement} sourceElement - Element that triggers the event
|
||||
* @param {HTMLElement} targetElement - Element that receives the action
|
||||
* @param {string} componentId - Component ID
|
||||
* @param {string} action - Action name
|
||||
* @param {Function} handler - Action handler
|
||||
* @param {string} eventType - Event type
|
||||
* @param {number} delay - Delay in ms
|
||||
* @param {number} throttle - Throttle in ms
|
||||
* @param {boolean} changed - Only on value change
|
||||
*/
|
||||
setupDelegatedTrigger(sourceElement, targetElement, componentId, action, handler, eventType, delay, throttle, changed) {
|
||||
const wrappedHandler = (e) => {
|
||||
this.executeWithOptions(targetElement, componentId, action, handler, delay, throttle, changed, e);
|
||||
};
|
||||
|
||||
sourceElement.addEventListener(eventType, wrappedHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute handler with trigger options
|
||||
*
|
||||
* @param {HTMLElement} element - Element
|
||||
* @param {string} componentId - Component ID
|
||||
* @param {string} action - Action name
|
||||
* @param {Function} handler - Handler function
|
||||
* @param {number} delay - Delay in ms
|
||||
* @param {number} throttle - Throttle in ms
|
||||
* @param {boolean} changed - Only on value change
|
||||
* @param {Event} event - Original event
|
||||
*/
|
||||
executeWithOptions(element, componentId, action, handler, delay, throttle, changed, event) {
|
||||
// Check 'changed' trigger
|
||||
if (changed) {
|
||||
const currentValue = this.getElementValue(element);
|
||||
const valueKey = `${componentId}_${action}_${this.getElementKey(element)}`;
|
||||
const lastValue = this.lastValues.get(valueKey);
|
||||
|
||||
if (currentValue === lastValue) {
|
||||
return; // Value hasn't changed
|
||||
}
|
||||
|
||||
this.lastValues.set(valueKey, currentValue);
|
||||
}
|
||||
|
||||
// Apply throttle
|
||||
if (throttle > 0) {
|
||||
const throttleKey = `${componentId}_${action}_${this.getElementKey(element)}`;
|
||||
const lastExecution = this.throttleTimers.get(throttleKey);
|
||||
|
||||
if (lastExecution && Date.now() - lastExecution < throttle) {
|
||||
return; // Throttled
|
||||
}
|
||||
|
||||
this.throttleTimers.set(throttleKey, Date.now());
|
||||
}
|
||||
|
||||
// Apply delay
|
||||
if (delay > 0) {
|
||||
setTimeout(() => {
|
||||
handler(event);
|
||||
}, delay);
|
||||
} else {
|
||||
handler(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse delay value (e.g., "500ms", "1s", "500")
|
||||
*
|
||||
* @param {string} delayStr - Delay string
|
||||
* @returns {number} - Delay in milliseconds
|
||||
*/
|
||||
parseDelay(delayStr) {
|
||||
if (!delayStr) return 0;
|
||||
|
||||
const match = delayStr.match(/^(\d+)(ms|s)?$/);
|
||||
if (!match) return 0;
|
||||
|
||||
const value = parseInt(match[1]);
|
||||
const unit = match[2] || 'ms';
|
||||
|
||||
return unit === 's' ? value * 1000 : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse throttle value (e.g., "100ms", "1s", "100")
|
||||
*
|
||||
* @param {string} throttleStr - Throttle string
|
||||
* @returns {number} - Throttle in milliseconds
|
||||
*/
|
||||
parseThrottle(throttleStr) {
|
||||
if (!throttleStr) return 0;
|
||||
|
||||
const match = throttleStr.match(/^(\d+)(ms|s)?$/);
|
||||
if (!match) return 0;
|
||||
|
||||
const value = parseInt(match[1]);
|
||||
const unit = match[2] || 'ms';
|
||||
|
||||
return unit === 's' ? value * 1000 : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get event type for element
|
||||
*
|
||||
* @param {HTMLElement} element - Element
|
||||
* @param {string} defaultEvent - Default event type
|
||||
* @returns {string} - Event type
|
||||
*/
|
||||
getEventType(element, defaultEvent) {
|
||||
// For form elements, use appropriate event
|
||||
if (element.tagName === 'FORM') {
|
||||
return 'submit';
|
||||
}
|
||||
if (element.tagName === 'SELECT') {
|
||||
return 'change';
|
||||
}
|
||||
if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') {
|
||||
return element.type === 'checkbox' || element.type === 'radio' ? 'change' : 'input';
|
||||
}
|
||||
|
||||
return defaultEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get element value for 'changed' trigger
|
||||
*
|
||||
* @param {HTMLElement} element - Element
|
||||
* @returns {string} - Element value
|
||||
*/
|
||||
getElementValue(element) {
|
||||
if (element.tagName === 'INPUT') {
|
||||
if (element.type === 'checkbox' || element.type === 'radio') {
|
||||
return element.checked ? 'true' : 'false';
|
||||
}
|
||||
return element.value || '';
|
||||
}
|
||||
if (element.tagName === 'TEXTAREA' || element.tagName === 'SELECT') {
|
||||
return element.value || '';
|
||||
}
|
||||
return element.textContent || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get unique key for element (for tracking)
|
||||
*
|
||||
* @param {HTMLElement} element - Element
|
||||
* @returns {string} - Unique key
|
||||
*/
|
||||
getElementKey(element) {
|
||||
return element.id || element.name || `${element.tagName}_${element.className}` || 'unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up resources for component
|
||||
*
|
||||
* @param {string} componentId - Component ID
|
||||
*/
|
||||
cleanup(componentId) {
|
||||
// Remove all entries for this component
|
||||
const keysToRemove = [];
|
||||
for (const key of this.triggeredOnce) {
|
||||
if (key.startsWith(`${componentId}_`)) {
|
||||
keysToRemove.push(key);
|
||||
}
|
||||
}
|
||||
keysToRemove.forEach(key => this.triggeredOnce.delete(key));
|
||||
|
||||
// Clean throttle timers
|
||||
const timersToRemove = [];
|
||||
for (const key of this.throttleTimers.keys()) {
|
||||
if (key.startsWith(`${componentId}_`)) {
|
||||
timersToRemove.push(key);
|
||||
}
|
||||
}
|
||||
timersToRemove.forEach(key => this.throttleTimers.delete(key));
|
||||
|
||||
// Clean last values
|
||||
const valuesToRemove = [];
|
||||
for (const key of this.lastValues.keys()) {
|
||||
if (key.startsWith(`${componentId}_`)) {
|
||||
valuesToRemove.push(key);
|
||||
}
|
||||
}
|
||||
valuesToRemove.forEach(key => this.lastValues.delete(key));
|
||||
|
||||
// Clean load handled
|
||||
const loadToRemove = [];
|
||||
for (const key of this.loadHandled) {
|
||||
if (key.startsWith(`${componentId}_`)) {
|
||||
loadToRemove.push(key);
|
||||
}
|
||||
}
|
||||
loadToRemove.forEach(key => this.loadHandled.delete(key));
|
||||
}
|
||||
}
|
||||
|
||||
// Create singleton instance
|
||||
export const triggerManager = new TriggerManager();
|
||||
export default triggerManager;
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user