/* eslint-disable @typescript-eslint/no-explicit-any */ import { Lunchbox } from "."; import { ThreeLunchbox } from "./three-lunchbox"; /** Get potentially nested property in object */ export const get = ( obj: any, path: string | string[], defValue?: T ) => { // If path is not defined or it has false value if (!path) return undefined; // Check if path is string or array. Regex : ensure that we do not have '.' and brackets. // Regex explained: https://regexr.com/58j0k const pathArray = Array.isArray(path) ? path : path.match(/([^[.\]])+/g); // Find value const result = pathArray?.reduce( (prevObj: Record, key: string) => prevObj && prevObj[key], obj ); // If found value is undefined return default value; otherwise return the value return result === undefined ? defValue : result; }; /* eslint-enable @typescript-eslint/no-explicit-any */ /** Check if `obj` contains a constructor */ /* eslint-disable @typescript-eslint/no-explicit-any */ export function isClass(obj: any): obj is IsClass { if (!obj) return false; const isCtorClass = obj.constructor && obj.constructor.toString().substring(0, 5) === 'class'; if (obj.prototype === undefined) { return isCtorClass; } const isPrototypeCtorClass = obj.prototype.constructor && obj.prototype.constructor.toString && obj.prototype.constructor.toString().substring(0, 5) === 'class'; return isCtorClass || isPrototypeCtorClass; } export type IsClass = { new(...args: any[]): T } /* eslint-enable @typescript-eslint/no-explicit-any */ /** `isNumber` from lodash */ const buildIsNumber = () => { /** * lodash (Custom Build) * Build: `lodash modularize exports="npm" -o ./` * Copyright 2012-2016 The Dojo Foundation * Based on Underscore.js 1.8.3 * Copyright 2009-2016 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors * Available under MIT license */ /** `Object#toString` result references. */ const numberTag = '[object Number]'; /** Used for built-in method references. */ const objectProto = Object.prototype; /** * Used to resolve the * [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring) * of values. */ const objectToString = objectProto.toString; /** * Checks if `value` is object-like. A value is object-like if it's not `null` * and has a `typeof` result of "object". * * @static * @memberOf _ * @since 4.0.0 * @category Lang * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is object-like, else `false`. * @example * * _.isObjectLike({}); * // => true * * _.isObjectLike([1, 2, 3]); * // => true * * _.isObjectLike(_.noop); * // => false * * _.isObjectLike(null); * // => false */ /* eslint-disable @typescript-eslint/no-explicit-any */ function isObjectLike(value: any) { return !!value && typeof value == 'object'; } /** * Checks if `value` is classified as a `Number` primitive or object. * * **Note:** To exclude `Infinity`, `-Infinity`, and `NaN`, which are * classified as numbers, use the `_.isFinite` method. * * @static * @memberOf _ * @since 0.1.0 * @category Lang * @param {*} value The value to check. * @returns {boolean} Returns `true` if `value` is a number, else `false`. * @example * * _.isNumber(3); * // => true * * _.isNumber(Number.MIN_VALUE); * // => true * * _.isNumber(Infinity); * // => true * * _.isNumber('3'); * // => false */ const output = function isNumber(value: any): value is number { return ( typeof value == 'number' || (isObjectLike(value) && objectToString.call(value) == numberTag) ); }; /* eslint-enable @typescript-eslint/no-explicit-any */ return output; }; /** Check if a given value is a number */ export const isNumber = buildIsNumber(); /** Set a potentially-nested property to a given value */ /* eslint-disable @typescript-eslint/no-explicit-any */ export const set = ( obj: Record, path: string | string[], value: unknown ) => { // Regex explained: https://regexr.com/58j0k const pathArray = Array.isArray(path) ? path : path.match(/([^[.\]])+/g); pathArray?.reduce((acc, key: string, i: number) => { if (acc[key] === undefined) acc[key] = {}; if (i === pathArray.length - 1) acc[key] = value; return acc[key]; }, obj); }; /* eslint-enable @typescript-eslint/no-explicit-any */ export const THREE_UUID_ATTRIBUTE_NAME = 'data-three-uuid'; /** Get the parent of the given `el` that can has an instance or scene available. */ export const getCandidateParent = (el: HTMLElement) => { let parent = (el.parentNode) as unknown as Lunchbox | ThreeLunchbox | Node | null | undefined; while (parent && !(parent as Lunchbox)?.instance && !(parent as ThreeLunchbox)?.three?.scene){ parent = parent?.parentNode || (parent as ShadowRoot)?.host || ((parent.getRootNode?.() as ShadowRoot)?.host); } return parent; } // Source - https://stackoverflow.com/a/67676665 // Posted by Mendy // Retrieved 2025-12-30, License - CC BY-SA 4.0 export function closestPassShadow(node: HTMLElement | ParentNode | null, selector: string | ((el: HTMLElement | ParentNode | null) => boolean)) { if (!node) { return null; } if (node instanceof ShadowRoot) { return closestPassShadow(node.host, selector); } if (node instanceof HTMLElement) { if (typeof selector === 'string' && node.matches(selector)) { return node; } else if (typeof selector === 'function' && selector(node)){ return node; } else { return closestPassShadow(node.parentNode, selector); } } return closestPassShadow(node.parentNode, selector); }