-
Notifications
You must be signed in to change notification settings - Fork 68
/
Copy pathjs_backed_map.dart
167 lines (140 loc) · 5.45 KB
/
js_backed_map.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
@JS()
library js_map;
import 'dart:collection';
import 'dart:core';
import 'dart:js_util' as js_util;
import 'package:js/js.dart';
import 'package:react/src/js_interop_util.dart';
import 'package:react/src/react_client/private_utils.dart';
/// A view into a JavaScript object ([jsObject]) that conforms to the Dart [Map] interface.
///
/// The keys are all enumerable properties of the [jsObject], though non-enumerable properties may also be accessed.
/// For this reason, it is recommended to use plain, simple JavaScript objects.
///
/// Keys should be strings, as any other object (primitive or non-primitive), will be coerced by JavaScript
/// to a string upon read/write. This includes `null`, which gets coerced to the string `'null'`.
///
/// For performance reasons, keys are not validated.
///
/// Iteration order is arbitrary, and is based on the current browser's implementation.
///
/// Two JsBackedMap instances are considered equal if they are backed by the same [jsObject].
class JsBackedMap extends MapBase<dynamic, dynamic> {
final JsMap jsObject;
/// Creates a JsBackedMap instance backed by a new [jsObject].
JsBackedMap() : jsObject = JsMap();
/// Creates a JsBackedMap instance backed by [jsObject].
JsBackedMap.backedBy(this.jsObject);
/// Creates a JsBackedMap instance that contains all key/value pairs of [other].
factory JsBackedMap.from(Map other) => JsBackedMap()..addAll(other);
/// Creates a JsBackedMap instance that contains all key/value pairs of the JS object [jsOther].
factory JsBackedMap.fromJs(JsMap jsOther) => JsBackedMap()..addAllFromJs(jsOther);
// Private helpers with narrower typing than we want to expose, for use in other methods
List<dynamic> get _keys => objectKeys(jsObject);
// Use keys to access the value instead oof `Object.values` since MSIE 11 doesn't support it
List<dynamic> get _values => _keys.map((key) => this[key]).toList();
/// Adds all key/value pairs of the JS object [jsOther] to this map.
///
/// If a key of [jsOther] is already in this map, its value is overwritten.
///
/// The operation is equivalent to doing `this[key] = value` for each key and associated value in [jsOther].
///
/// This is similar to [addAll], but for a JsMap instead of a JsBackedMap/Map.
void addAllFromJs(JsMap jsOther) {
_Object.assign(jsObject, jsOther);
}
// ----------------------------------
// MapBase implementations
// ----------------------------------
@override
dynamic /*?*/ operator [](Object? key) {
// `?? null` as a workaround to `undefined` coming through and causing issues: https://github.com/dart-lang/sdk/issues/36116
// ignore: unnecessary_null_in_if_null_operators
return DartValueWrapper.unwrapIfNeeded(js_util.getProperty(jsObject, nonNullableJsPropertyName(key)) ?? null);
}
@override
void operator []=(dynamic key, dynamic value) {
js_util.setProperty(jsObject, nonNullableJsPropertyName(key), DartValueWrapper.wrapIfNeeded(value));
}
@override
Iterable<dynamic> get keys => _keys;
@override
dynamic /*?*/ remove(Object? key) {
final value = this[key];
_Reflect.deleteProperty(jsObject, key);
return value;
}
@override
void clear() {
for (final key in _keys) {
_Reflect.deleteProperty(jsObject, key);
}
}
// ----------------------------------
// Optimized MapBase overrides
// ----------------------------------
@override
void addAll(Map other) {
if (other is JsBackedMap) {
// Object.assign is more efficient than iterating through and setting properties in Dart.
addAllFromJs(other.jsObject);
} else {
super.addAll(other);
}
}
@override
bool containsKey(Object? key) => js_util.hasProperty(jsObject, nonNullableJsPropertyName(key));
@override
Iterable<dynamic> get values => _values;
// todo figure out if this is faster than default implementation
@override
bool containsValue(Object? value) => _values.contains(value);
@override
bool operator ==(other) => other is JsBackedMap && other.jsObject == jsObject;
@override
int get hashCode {
// Work around DDC throwing when accessing hashCode on frozen JS objects:
// https://github.com/dart-lang/sdk/issues/36354
try {
return jsObject.hashCode;
} catch (_) {}
// While constant hashCode like this one are not high-quality and may cause
// hashCode collisions more often, they are completely valid.
// For more information, see the `Object.hashCode` doc comment.
return 0;
}
}
/// A utility interop class for a plain JS "object"/"map"/"dictionary".
///
/// This class provides no functionality, and exists solely as a placeholder
/// type for use in [JsBackedMap] and other related utilities.
@JS()
@anonymous
class JsMap {
external factory JsMap();
}
/// Returns a JsMap version of [map], which will be either:
///
/// - the backing JsMap if [map] is a [JsBackedMap]
/// - a JsMap copy of [map]
///
/// This method is useful when the map needs to be passed to a JS function
/// and you want to avoid copying it when possible.
///
/// If a copy is always needed, use [JsBackedMap.from] instead.
JsMap jsBackingMapOrJsCopy(Map map) {
// todo is it faster to just always do .from?
if (map is JsBackedMap) {
return map.jsObject;
} else {
return JsBackedMap.from(map).jsObject;
}
}
@JS('Object')
abstract class _Object {
external static void assign(JsMap to, JsMap from);
}
@JS('Reflect')
abstract class _Reflect {
external static bool deleteProperty(JsMap target, dynamic propertyKey);
}