Skip to content

Commit 2ed6210

Browse files
committed
Revert "ha"
This reverts commit 9d30ce6.
1 parent 9348e55 commit 2ed6210

File tree

1 file changed

+311
-0
lines changed

1 file changed

+311
-0
lines changed

packages/@ember/object/index.ts

Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
import { assert } from '@ember/debug';
2+
import { ENV } from '@ember/-internals/environment';
3+
import type { ElementDescriptor, ExtendedMethodDecorator } from '@ember/-internals/metal';
4+
import {
5+
isElementDescriptor,
6+
expandProperties,
7+
setClassicDecorator,
8+
} from '@ember/-internals/metal';
9+
import { getFactoryFor } from '@ember/-internals/container';
10+
import { setObservers } from '@ember/-internals/utils';
11+
import type { AnyFn } from '@ember/-internals/utility-types';
12+
import CoreObject from '@ember/object/core';
13+
import Observable from '@ember/object/observable';
14+
15+
export {
16+
notifyPropertyChange,
17+
defineProperty,
18+
get,
19+
set,
20+
getProperties,
21+
setProperties,
22+
computed,
23+
trySet,
24+
} from '@ember/-internals/metal';
25+
26+
/**
27+
@module @ember/object
28+
*/
29+
30+
/**
31+
`EmberObject` is the main base class for all Ember objects. It is a subclass
32+
of `CoreObject` with the `Observable` mixin applied. For details,
33+
see the documentation for each of these.
34+
35+
@class EmberObject
36+
@extends CoreObject
37+
@uses Observable
38+
@public
39+
*/
40+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
41+
interface EmberObject extends Observable {}
42+
class EmberObject extends CoreObject.extend(Observable) {
43+
get _debugContainerKey() {
44+
let factory = getFactoryFor(this);
45+
return factory !== undefined && factory.fullName;
46+
}
47+
}
48+
49+
export default EmberObject;
50+
51+
/**
52+
Decorator that turns the target function into an Action which can be accessed
53+
directly by reference.
54+
55+
```js
56+
import Component from '@ember/component';
57+
import { tracked } from '@glimmer/tracking';
58+
import { action } from '@ember/object';
59+
60+
export default class Tooltip extends Component {
61+
@tracked isShowing = false;
62+
63+
@action
64+
toggleShowing() {
65+
this.isShowing = !this.isShowing;
66+
}
67+
}
68+
```
69+
```hbs
70+
<!-- template.hbs -->
71+
<button {{on "click" this.toggleShowing}}>Show tooltip</button>
72+
73+
{{#if isShowing}}
74+
<div class="tooltip">
75+
I'm a tooltip!
76+
</div>
77+
{{/if}}
78+
```
79+
80+
It also binds the function directly to the instance, so it can be used in any
81+
context and will correctly refer to the class it came from:
82+
83+
```js
84+
import Component from '@ember/component';
85+
import { tracked } from '@glimmer/tracking';
86+
import { action } from '@ember/object';
87+
88+
export default class Tooltip extends Component {
89+
constructor() {
90+
super(...arguments);
91+
92+
// this.toggleShowing is still bound correctly when added to
93+
// the event listener
94+
document.addEventListener('click', this.toggleShowing);
95+
}
96+
97+
@tracked isShowing = false;
98+
99+
@action
100+
toggleShowing() {
101+
this.isShowing = !this.isShowing;
102+
}
103+
}
104+
```
105+
106+
@public
107+
@method action
108+
@for @ember/object
109+
@static
110+
@param {Function|undefined} callback The function to turn into an action,
111+
when used in classic classes
112+
@return {PropertyDecorator} property decorator instance
113+
*/
114+
115+
const BINDINGS_MAP = new WeakMap();
116+
117+
interface HasProto {
118+
constructor: {
119+
proto(): void;
120+
};
121+
}
122+
123+
function hasProto(obj: unknown): obj is HasProto {
124+
return (
125+
obj != null &&
126+
(obj as any).constructor !== undefined &&
127+
typeof ((obj as any).constructor as any).proto === 'function'
128+
);
129+
}
130+
131+
interface HasActions {
132+
actions: Record<string | symbol, unknown>;
133+
}
134+
135+
function setupAction(
136+
target: Partial<HasActions>,
137+
key: string | symbol,
138+
actionFn: Function
139+
): TypedPropertyDescriptor<unknown> {
140+
if (hasProto(target)) {
141+
target.constructor.proto();
142+
}
143+
144+
if (!Object.prototype.hasOwnProperty.call(target, 'actions')) {
145+
let parentActions = target.actions;
146+
// we need to assign because of the way mixins copy actions down when inheriting
147+
target.actions = parentActions ? Object.assign({}, parentActions) : {};
148+
}
149+
150+
assert("[BUG] Somehow the target doesn't have actions!", target.actions != null);
151+
152+
target.actions[key] = actionFn;
153+
154+
return {
155+
get() {
156+
let bindings = BINDINGS_MAP.get(this);
157+
158+
if (bindings === undefined) {
159+
bindings = new Map();
160+
BINDINGS_MAP.set(this, bindings);
161+
}
162+
163+
let fn = bindings.get(actionFn);
164+
165+
if (fn === undefined) {
166+
fn = actionFn.bind(this);
167+
bindings.set(actionFn, fn);
168+
}
169+
170+
return fn;
171+
},
172+
};
173+
}
174+
175+
export function action(
176+
target: ElementDescriptor[0],
177+
key: ElementDescriptor[1],
178+
desc: ElementDescriptor[2]
179+
): PropertyDescriptor;
180+
export function action(desc: PropertyDescriptor): ExtendedMethodDecorator;
181+
export function action(
182+
...args: ElementDescriptor | [PropertyDescriptor]
183+
): PropertyDescriptor | ExtendedMethodDecorator {
184+
let actionFn: object | Function;
185+
186+
if (!isElementDescriptor(args)) {
187+
actionFn = args[0];
188+
189+
let decorator: ExtendedMethodDecorator = function (
190+
target,
191+
key,
192+
_desc,
193+
_meta,
194+
isClassicDecorator
195+
) {
196+
assert(
197+
'The @action decorator may only be passed a method when used in classic classes. You should decorate methods directly in native classes',
198+
isClassicDecorator
199+
);
200+
201+
assert(
202+
'The action() decorator must be passed a method when used in classic classes',
203+
typeof actionFn === 'function'
204+
);
205+
206+
return setupAction(target, key, actionFn);
207+
};
208+
209+
setClassicDecorator(decorator);
210+
211+
return decorator;
212+
}
213+
214+
let [target, key, desc] = args;
215+
216+
actionFn = desc?.value;
217+
218+
assert(
219+
'The @action decorator must be applied to methods when used in native classes',
220+
typeof actionFn === 'function'
221+
);
222+
223+
// SAFETY: TS types are weird with decorators. This should work.
224+
return setupAction(target, key, actionFn);
225+
}
226+
227+
// SAFETY: TS types are weird with decorators. This should work.
228+
setClassicDecorator(action as ExtendedMethodDecorator);
229+
230+
// ..........................................................
231+
// OBSERVER HELPER
232+
//
233+
234+
type ObserverDefinition<T extends AnyFn> = {
235+
dependentKeys: string[];
236+
fn: T;
237+
sync: boolean;
238+
};
239+
240+
/**
241+
Specify a method that observes property changes.
242+
243+
```javascript
244+
import EmberObject from '@ember/object';
245+
import { observer } from '@ember/object';
246+
247+
export default EmberObject.extend({
248+
valueObserver: observer('value', function() {
249+
// Executes whenever the "value" property changes
250+
})
251+
});
252+
```
253+
254+
Also available as `Function.prototype.observes` if prototype extensions are
255+
enabled.
256+
257+
@method observer
258+
@for @ember/object
259+
@param {String} propertyNames*
260+
@param {Function} func
261+
@return func
262+
@public
263+
@static
264+
*/
265+
export function observer<T extends AnyFn>(
266+
...args:
267+
| [propertyName: string, ...additionalPropertyNames: string[], func: T]
268+
| [ObserverDefinition<T>]
269+
): T {
270+
let funcOrDef = args.pop();
271+
272+
assert(
273+
'observer must be provided a function or an observer definition',
274+
typeof funcOrDef === 'function' || (typeof funcOrDef === 'object' && funcOrDef !== null)
275+
);
276+
277+
let func: T;
278+
let dependentKeys: string[];
279+
let sync: boolean;
280+
281+
if (typeof funcOrDef === 'function') {
282+
func = funcOrDef;
283+
dependentKeys = args as string[];
284+
sync = !ENV._DEFAULT_ASYNC_OBSERVERS;
285+
} else {
286+
func = funcOrDef.fn;
287+
dependentKeys = funcOrDef.dependentKeys;
288+
sync = funcOrDef.sync;
289+
}
290+
291+
assert('observer called without a function', typeof func === 'function');
292+
assert(
293+
'observer called without valid path',
294+
Array.isArray(dependentKeys) &&
295+
dependentKeys.length > 0 &&
296+
dependentKeys.every((p) => typeof p === 'string' && Boolean(p.length))
297+
);
298+
assert('observer called without sync', typeof sync === 'boolean');
299+
300+
let paths: string[] = [];
301+
302+
for (let dependentKey of dependentKeys) {
303+
expandProperties(dependentKey, (path: string) => paths.push(path));
304+
}
305+
306+
setObservers(func as Function, {
307+
paths,
308+
sync,
309+
});
310+
return func;
311+
}

0 commit comments

Comments
 (0)