Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions addon/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export function ref(name, fn) {
const value = bucketFor(this).get(name);
return maybeReturnCreated(value, createdValues, fn, this);
},
configurable: true,
};
};
}
Expand All @@ -52,6 +53,7 @@ export function globalRef(name, fn) {
const value = bucketFor(getOwner(this) || resolveGlobalRef()).get(name);
return maybeReturnCreated(value, createdValues, fn, this);
},
configurable: true,
};
};
}
Expand All @@ -67,6 +69,7 @@ export function trackedRef(name, fn) {
const value = bucketFor(this).getTracked(name);
return maybeReturnCreated(value, createdValues, fn, this);
},
configurable: true,
};
};
}
Expand All @@ -82,6 +85,7 @@ export function trackedGlobalRef(name, fn) {
const value = bucketFor(getOwner(this) || resolveGlobalRef()).getTracked(name);
return maybeReturnCreated(value, createdValues, fn, this);
},
configurable: true,
};
};
}
7 changes: 6 additions & 1 deletion addon/modifiers/create-ref.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,13 @@ export default class RefModifier extends Modifier {
return this.args.positional[0];
}
willDestroy() {
const element = this.element;
this.cleanMutationObservers();
this.cleanResizeObservers();
getNodeDestructors(this.element).forEach((cb) => cb());
getNodeDestructors(element).forEach((cb) => cb());
if (element === bucketFor(this._ctx).get(this._key)) {
bucketFor(this._ctx).add(this._key, null);
}
delete this.element;
}
}
139 changes: 131 additions & 8 deletions addon/utils/ref.js
Original file line number Diff line number Diff line change
@@ -1,53 +1,146 @@
import { registerDestructor, isDestroying, isDestroyed } from "@ember/destroyable";
import { tracked } from "@glimmer/tracking";
// @ts-check
/*eslint no-undef: "warn"*/

import {
registerDestructor,
isDestroying,
isDestroyed,
} from '@ember/destroyable';
import { tracked } from '@glimmer/tracking';

/**
* @type {object | null}
*/
let lastGlobalRef = null;
/**
* @type {WeakMap<object, ReturnType<typeof createBucket>>}
*/
const buckets = new WeakMap();
/**
* @type {WeakMap<HTMLElement, Array<() => void>>}
*/
const nodeDestructors = new WeakMap();

const hasWeakRef = typeof WeakRef !== 'undefined';

function fromWeakRefIfSupported(node) {
if (hasWeakRef && node instanceof WeakRef) {
return node.deref() ?? null;
}
return node;
}

/**
*
* @param {null | undefined | WeeakRef | HTMLElement } node
* @returns
*/
function toWeakRefIfSupported(node) {
if (node === null || node === undefined) {
return null;
}
if (node instanceof WeakRef) {
return node;
}
if (hasWeakRef) {
return new WeakRef(node);
}
return node;
}

class FieldCell {
@tracked value = null;
/**
/**
* @type {null | (WeakRef<HTMLElement> | HTMLElement)}
*/
@tracked
_element = null;
get value() {
if (this._element) {
return fromWeakRefIfSupported(this._element);
} else {
return null;
}
}
set value(element) {
if (element) {
this._element = toWeakRefIfSupported(element);
} else {
this._element = null;
}
}
}

export function setGlobalRef(value) {
lastGlobalRef = value;
}

export function cleanGlobalRef() {
lastGlobalRef = null;
}

export function resolveGlobalRef() {
return lastGlobalRef;
}

function createBucket() {
return {
/**
* @type { Record<string, HTMLElement> }
*/
bucket: {},
/**
* @type { Record<string, FieldCell> }
*/
keys: {},
/**
* @param {string} key
*/
createTrackedCell(key) {
if (!(key in this.keys)) {
this.keys[key] = new FieldCell();
}
},
/**
* @param {string} name
* @returns { HTMLElement | null }
*/
get(name) {
this.createTrackedCell(name);
return this.bucket[name] || null;
return fromWeakRefIfSupported(this.bucket[name]) || null;
},
/**
* @param {string} name
*/
dirtyTrackedCell(name) {
this.createTrackedCell(name);
const val = this.keys[name].value;
this.keys[name].value = val;
},
/**
* @param {string} name
*/
getTracked(name) {
this.createTrackedCell(name);
return this.keys[name].value;
},
/**
* @param {string} name
* @param {HTMLElement} value
*/
add(name, value) {
this.createTrackedCell(name);
this.keys[name].value = value;
this.bucket[name] = value;
this.bucket[name] = toWeakRefIfSupported(value);
if (!(name in this.notificationsFor)) {
this.notificationsFor[name] = [];
}
this.notificationsFor[name].forEach((fn) => fn());
},
/**
* @param {string} name
* @param {() => void} fn
*/
addNotificationFor(name, fn) {
if (!(name in this.notificationsFor)) {
this.notificationsFor[name] = [];
Expand All @@ -59,26 +152,49 @@ function createBucket() {
);
};
},
/**
* @type { Record<string, Array<() => void>> }
*/
notificationsFor: {},
};
}

/**
*
* @param {HTMLElement} node
* @returns {Array<() => void>}
*/
export function getNodeDestructors(node) {
return nodeDestructors.get(node) || [];
}

/**
* @param {HTMLElement} node
* @param {() => void} cb
*/
export function registerNodeDestructor(node, cb) {
if (!nodeDestructors.has(node)) {
nodeDestructors.set(node, []);
}
nodeDestructors.get(node).push(cb);
nodeDestructors.get(node)?.push(cb);
}
/**
*
* @param {HTMLElement} node
* @param {()=> void} cb
*/
export function unregisterNodeDestructor(node, cb) {
const destructors = nodeDestructors.get(node) || [];
nodeDestructors.set(
node,
destructors.filter((el) => el !== cb)
);
}
/**
*
* @param {object} rawCtx
* @returns {ReturnType<typeof createBucket> | undefined}
*/
export function bucketFor(rawCtx) {
const ctx = rawCtx;
if (!buckets.has(ctx)) {
Expand All @@ -96,7 +212,14 @@ export function bucketFor(rawCtx) {
}
return buckets.get(ctx);
}
/**
*
* @param {string} name
* @param {object} bucketRef
* @param {()=>void} cb
* @returns
*/
export function watchFor(name, bucketRef, cb) {
const bucket = bucketFor(bucketRef);
return bucket.addNotificationFor(name, cb);
}
return bucket?.addNotificationFor(name, cb);
}
24 changes: 0 additions & 24 deletions config/ember-try.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,30 +21,6 @@ module.exports = async function() {
}
}
},
{
name: 'ember-release',
npm: {
devDependencies: {
'ember-source': await getChannelURL('release')
}
}
},
{
name: 'ember-beta',
npm: {
devDependencies: {
'ember-source': await getChannelURL('beta')
}
}
},
{
name: 'ember-canary',
npm: {
devDependencies: {
'ember-source': await getChannelURL('canary')
}
}
},
{
name: 'ember-default-with-jquery',
env: {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ember-ref-bucket",
"version": "3.1.0",
"version": "3.1.1",
"description": "Ember DOM reference modifiers, helpers and decorators",
"keywords": [
"ember-addon",
Expand Down