ember-source
Version:
A JavaScript framework for creating ambitious web applications
250 lines (232 loc) • 10.4 kB
JavaScript
import { d as debugToString } from './debug-to-string-BsFOvUtQ.js';
import { isDevelopingApp } from '@embroider/macros';
import { debugAssert } from '../@glimmer/global-context/index.js';
import { associateDestroyableChild } from '../@glimmer/destroyable/index.js';
import '../@glimmer/validator/index.js';
import { b as createComputeRef, d as createConstRef, U as UNDEFINED_REFERENCE } from './reference-B6HMX4y0.js';
import { a as argsProxyFor } from './args-proxy-B91L3LRK.js';
import { F as FROM_CAPABILITIES, b as buildCapabilities } from './capabilities-DHiXCCuB.js';
function helperCapabilities(managerAPI, options = {}) {
debugAssert(managerAPI === '3.23', () => `Invalid helper manager compatibility specified; you specified ${managerAPI}, but only '3.23' is supported.`);
if (isDevelopingApp() && (!(options.hasValue || options.hasScheduledEffect) || options.hasValue && options.hasScheduledEffect)) {
throw new Error('You must pass either the `hasValue` OR the `hasScheduledEffect` capability when defining a helper manager. Passing neither, or both, is not permitted.');
}
if (isDevelopingApp() && options.hasScheduledEffect) {
throw new Error('The `hasScheduledEffect` capability has not yet been implemented for helper managers. Please pass `hasValue` instead');
}
return buildCapabilities({
hasValue: Boolean(options.hasValue),
hasDestroyable: Boolean(options.hasDestroyable),
hasScheduledEffect: Boolean(options.hasScheduledEffect)
});
}
////////////
function hasValue(manager) {
return manager.capabilities.hasValue;
}
function hasDestroyable(manager) {
return manager.capabilities.hasDestroyable;
}
////////////
class CustomHelperManager {
constructor(factory) {
this.factory = factory;
}
helperManagerDelegates = new WeakMap();
undefinedDelegate = null;
getDelegateForOwner(owner) {
let delegate = this.helperManagerDelegates.get(owner);
if (delegate === undefined) {
let {
factory
} = this;
delegate = factory(owner);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme
if (isDevelopingApp() && !FROM_CAPABILITIES.has(delegate.capabilities)) {
// TODO: This error message should make sense in both Ember and Glimmer https://github.com/glimmerjs/glimmer-vm/issues/1200
throw new Error(`Custom helper managers must have a \`capabilities\` property that is the result of calling the \`capabilities('3.23')\` (imported via \`import { capabilities } from '@ember/helper';\`). Received: \`${JSON.stringify(delegate.capabilities
// eslint-disable-next-line @typescript-eslint/no-base-to-string
)}\` for: \`${delegate}\``);
}
this.helperManagerDelegates.set(owner, delegate);
}
return delegate;
}
getDelegateFor(owner) {
if (owner === undefined) {
let {
undefinedDelegate
} = this;
if (undefinedDelegate === null) {
let {
factory
} = this;
this.undefinedDelegate = undefinedDelegate = factory(undefined);
}
return undefinedDelegate;
} else {
return this.getDelegateForOwner(owner);
}
}
getHelper(definition) {
return (capturedArgs, owner) => {
let manager = this.getDelegateFor(owner);
const args = argsProxyFor(capturedArgs, 'helper');
const bucket = manager.createHelper(definition, args);
if (hasValue(manager)) {
let cache = createComputeRef(() => manager.getValue(bucket), null, isDevelopingApp() && manager.getDebugName && manager.getDebugName(definition));
if (hasDestroyable(manager)) {
associateDestroyableChild(cache, manager.getDestroyable(bucket));
}
return cache;
} else if (hasDestroyable(manager)) {
let ref = createConstRef(undefined, isDevelopingApp() && (manager.getDebugName?.(definition) ?? 'unknown helper'));
associateDestroyableChild(ref, manager.getDestroyable(bucket));
return ref;
} else {
return UNDEFINED_REFERENCE;
}
};
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
class FunctionHelperManager {
capabilities = buildCapabilities({
hasValue: true,
hasDestroyable: false,
hasScheduledEffect: false
});
createHelper(fn, args) {
return {
fn,
args
};
}
getValue({
fn,
args
}) {
if (Object.keys(args.named).length > 0) {
let argsForFn = [...args.positional, args.named];
return fn(...argsForFn);
}
return fn(...args.positional);
}
getDebugName(fn) {
if (fn.name) {
return `(helper function ${fn.name})`;
}
return '(anonymous helper function)';
}
}
const COMPONENT_MANAGERS = new WeakMap();
const MODIFIER_MANAGERS = new WeakMap();
const HELPER_MANAGERS = new WeakMap();
///////////
/**
* There is also Reflect.getPrototypeOf,
* which errors when non-objects are passed.
*
* Since our conditional for figuring out whether to render primitives or not
* may contain non-object values, we don't want to throw errors when we call this.
*/
const getPrototypeOf = Object.getPrototypeOf;
function setManager(map, manager, obj) {
if (isDevelopingApp()) {
debugAssert(obj !== null && (typeof obj === 'object' || typeof obj === 'function'),
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme
`Attempted to set a manager on a non-object value. Managers can only be associated with objects or functions. Value was ${debugToString(obj)}`);
debugAssert(!map.has(obj),
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme
`Attempted to set the same type of manager multiple times on a value. You can only associate one manager of each type with a given value. Value was ${debugToString(obj)}`);
}
map.set(obj, manager);
return obj;
}
function getManager(map, obj) {
let pointer = obj;
while (pointer !== null) {
const manager = map.get(pointer);
if (manager !== undefined) {
return manager;
}
pointer = getPrototypeOf(pointer);
}
return undefined;
}
///////////
function setInternalModifierManager(manager, definition) {
return setManager(MODIFIER_MANAGERS, manager, definition);
}
function getInternalModifierManager(definition, isOptional) {
if (isDevelopingApp()) {
debugAssert(typeof definition === 'object' && definition !== null || typeof definition === 'function', () =>
// eslint-disable-next-line @typescript-eslint/no-base-to-string -- @fixme
`Attempted to use a value as a modifier, but it was not an object or function. Modifier definitions must be objects or functions with an associated modifier manager. The value was: ${definition}`);
}
const manager = getManager(MODIFIER_MANAGERS, definition);
if (manager === undefined) {
if (isDevelopingApp()) {
debugAssert(isOptional,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme
`Attempted to load a modifier, but there wasn't a modifier manager associated with the definition. The definition was: ${debugToString(definition)}`);
}
return null;
}
return manager;
}
function setInternalHelperManager(manager, definition) {
return setManager(HELPER_MANAGERS, manager, definition);
}
const DEFAULT_MANAGER = new CustomHelperManager(() => new FunctionHelperManager());
function getInternalHelperManager(definition, isOptional) {
debugAssert(typeof definition === 'object' && definition !== null || typeof definition === 'function', () =>
// eslint-disable-next-line @typescript-eslint/no-base-to-string -- @fixme
`Attempted to use a value as a helper, but it was not an object or function. Helper definitions must be objects or functions with an associated helper manager. The value was: ${definition}`);
let manager = getManager(HELPER_MANAGERS, definition);
// Functions are special-cased because functions are defined
// as the "default" helper, per: https://github.com/emberjs/rfcs/pull/756
if (manager === undefined && typeof definition === 'function') {
manager = DEFAULT_MANAGER;
}
if (manager) {
return manager;
} else if (isOptional === true) {
return null;
} else if (isDevelopingApp()) {
throw new Error(
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme
`Attempted to load a helper, but there wasn't a helper manager associated with the definition. The definition was: ${debugToString(definition)}`);
}
return null;
}
function setInternalComponentManager(factory, obj) {
return setManager(COMPONENT_MANAGERS, factory, obj);
}
function getInternalComponentManager(definition, isOptional) {
debugAssert(typeof definition === 'object' && definition !== null || typeof definition === 'function', () =>
// eslint-disable-next-line @typescript-eslint/no-base-to-string -- @fixme
`Attempted to use a value as a component, but it was not an object or function. Component definitions must be objects or functions with an associated component manager. The value was: ${definition}`);
const manager = getManager(COMPONENT_MANAGERS, definition);
if (manager === undefined) {
debugAssert(isOptional, () =>
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme
`Attempted to load a component, but there wasn't a component manager associated with the definition. The definition was: ${debugToString(definition)}`);
return null;
}
return manager;
}
///////////
function hasInternalComponentManager(definition) {
return getManager(COMPONENT_MANAGERS, definition) !== undefined;
}
function hasInternalHelperManager(definition) {
return hasDefaultHelperManager(definition) || getManager(HELPER_MANAGERS, definition) !== undefined;
}
function hasInternalModifierManager(definition) {
return getManager(MODIFIER_MANAGERS, definition) !== undefined;
}
function hasDefaultHelperManager(definition) {
return typeof definition === 'function';
}
export { CustomHelperManager as C, getInternalHelperManager as a, getInternalModifierManager as b, hasInternalComponentManager as c, hasInternalHelperManager as d, hasInternalModifierManager as e, hasValue as f, getInternalComponentManager as g, hasDestroyable as h, helperCapabilities as i, setInternalHelperManager as j, setInternalModifierManager as k, setInternalComponentManager as s };