@expressive/mvc 0.9.6 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -8,7 +8,10 @@ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'defau
8
8
 
9
9
  var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
10
10
 
11
- const { assign, create, defineProperties, defineProperty, entries, getPrototypeOf, getOwnPropertyNames, getOwnPropertyDescriptor, getOwnPropertySymbols, values } = Object;
11
+ const { assign, create, defineProperties, defineProperty, entries, getPrototypeOf, getOwnPropertyNames, getOwnPropertyDescriptor, getOwnPropertySymbols, values } = Object;
12
+ function unique(array) {
13
+ return Array.from(new Set(array));
14
+ }
12
15
 
13
16
  class Issue extends Error {
14
17
  constructor() {
@@ -659,6 +662,7 @@ function createValueEffect(callback) {
659
662
  function ref(arg, mapper) {
660
663
  return add(function ref(key) {
661
664
  let value = {};
665
+ this.state.set(key, undefined);
662
666
  if (typeof arg != "object")
663
667
  value = createRef(this, key, arg);
664
668
  else
@@ -955,31 +959,31 @@ const Oops$6 = issues({
955
959
  StrictNoUpdate: () => `Update is not expected but one is in progress!`
956
960
  });
957
961
  function addEventListener(source, arg1, arg2, arg3) {
962
+ if (typeof arg1 == "string")
963
+ arg1 = [arg1];
964
+ return Control.for(source, control => {
965
+ for (const key of arg1)
966
+ try {
967
+ void source[key];
968
+ }
969
+ catch (e) { }
970
+ const removeListener = control.addListener(key => {
971
+ if (!key && !arg1.length)
972
+ arg2.call(source, []);
973
+ if (arg1.includes(key)) {
974
+ if (arg3)
975
+ removeListener();
976
+ return arg2.bind(source);
977
+ }
978
+ });
979
+ return removeListener;
980
+ });
981
+ }
982
+ function awaitUpdate(source, arg1, arg2) {
958
983
  const single = typeof arg1 == "string";
959
984
  const keys = typeof arg1 == "string" ? [arg1] :
960
985
  typeof arg1 == "object" ? arg1 :
961
986
  undefined;
962
- if (typeof arg2 == "function" && keys)
963
- return Control.for(source, control => {
964
- for (const key of keys)
965
- try {
966
- void source[key];
967
- }
968
- catch (e) { }
969
- const invoke = single
970
- ? () => arg2.call(source, control.state.get(arg1), arg1)
971
- : (frame) => arg2.call(source, frame);
972
- const removeListener = control.addListener(key => {
973
- if (!key && !keys.length)
974
- invoke([]);
975
- if (keys.includes(key)) {
976
- if (arg3)
977
- removeListener();
978
- return invoke;
979
- }
980
- });
981
- return removeListener;
982
- });
983
987
  return new Promise((resolve, reject) => {
984
988
  const timeout = (message) => {
985
989
  const k = keys ? keys.join(", ") : "any";
@@ -1044,34 +1048,47 @@ class Model {
1044
1048
  on(arg1, arg2, arg3) {
1045
1049
  if (typeof arg1 == "function")
1046
1050
  return createEffect(this.is, arg1, arg2);
1047
- return addEventListener(this.is, arg1, arg2, arg3);
1051
+ if (typeof arg2 != "function")
1052
+ return awaitUpdate(this.is, arg1, arg2);
1053
+ if (arg1)
1054
+ return addEventListener(this.is, arg1, arg2, arg3);
1048
1055
  }
1049
- export(subset) {
1056
+ get(arg1, arg2) {
1050
1057
  const { state } = Control.for(this);
1051
- const output = {};
1052
- const keys = subset || state.keys();
1053
- for (const key of keys)
1054
- output[key] = state.get(key);
1055
- return output;
1058
+ const extract = typeof arg1 == "string"
1059
+ ? () => state.get(arg1)
1060
+ : () => {
1061
+ const output = {};
1062
+ for (const key of arg1 || state.keys())
1063
+ output[key] = state.get(key);
1064
+ return output;
1065
+ };
1066
+ if (!arg1 || arg2 === undefined)
1067
+ return extract();
1068
+ if (typeof arg2 == "function")
1069
+ return this.on(arg1, () => arg2(extract()));
1070
+ const timeout = typeof arg2 == "number" ? arg2 : undefined;
1071
+ return this.on(arg1, timeout).then(extract);
1056
1072
  }
1057
- update(arg1, arg2) {
1073
+ set(arg1, arg2) {
1058
1074
  const control = Control.for(this);
1059
- if (typeof arg1 == "object") {
1060
- for (const key in arg1)
1075
+ if (typeof arg1 == "object")
1076
+ for (const key in arg1) {
1061
1077
  if (arg2 === true || (arg2 ? arg2.includes(key) : control.state.has(key))) {
1062
1078
  control.state.set(key, arg1[key]);
1063
1079
  control.update(key);
1064
1080
  }
1065
- }
1081
+ }
1066
1082
  else if (typeof arg1 == "string") {
1067
1083
  control.update(arg1);
1068
- if (1 in arguments && arg1 in this) {
1069
- const method = this[arg1];
1070
- if (typeof method == "function")
1071
- if (typeof arg2 != "boolean")
1084
+ if (1 in arguments) {
1085
+ if (control.state.has(arg1))
1086
+ control.state.set(arg1, arg2);
1087
+ else if (arg1 in this) {
1088
+ const method = this[arg1];
1089
+ if (typeof method == "function")
1072
1090
  method.call(this, arg2);
1073
- else if (arg2)
1074
- method.call(this);
1091
+ }
1075
1092
  }
1076
1093
  }
1077
1094
  return {
@@ -1085,7 +1102,7 @@ class Model {
1085
1102
  /**
1086
1103
  * Clean up side effects and mark this instance for garbage-collection.
1087
1104
  */
1088
- destroy() {
1105
+ end() {
1089
1106
  Control.for(this).clear();
1090
1107
  }
1091
1108
  /**
@@ -1124,9 +1141,7 @@ class Lookup {
1124
1141
  this.table = new Map();
1125
1142
  }
1126
1143
  get local() {
1127
- return [
1128
- ...new Set(getOwnPropertySymbols(this).map(symbol => this[symbol]))
1129
- ];
1144
+ return unique(getOwnPropertySymbols(this).map(symbol => this[symbol]));
1130
1145
  }
1131
1146
  key(T) {
1132
1147
  let key = this.table.get(T);
@@ -1139,10 +1154,7 @@ class Lookup {
1139
1154
  get(T) {
1140
1155
  return this[this.key(T)];
1141
1156
  }
1142
- push() {
1143
- return create(this);
1144
- }
1145
- inject(T, I, writable) {
1157
+ add(T, I, writable) {
1146
1158
  do {
1147
1159
  const key = this.key(T);
1148
1160
  const conflict = this.hasOwnProperty(key);
@@ -1154,6 +1166,9 @@ class Lookup {
1154
1166
  T = getPrototypeOf(T);
1155
1167
  } while (T !== Model);
1156
1168
  }
1169
+ push() {
1170
+ return create(this);
1171
+ }
1157
1172
  pop() {
1158
1173
  const items = new Set();
1159
1174
  for (const key of getOwnPropertySymbols(this)) {
@@ -1162,40 +1177,33 @@ class Lookup {
1162
1177
  items.add(entry.value);
1163
1178
  }
1164
1179
  for (const model of items)
1165
- model.destroy();
1180
+ model.end();
1166
1181
  }
1167
1182
  }
1168
1183
  const LookupContext = React__default["default"].createContext(new Lookup());
1169
1184
  const useLookup = () => React__default["default"].useContext(LookupContext);
1170
- function useContext(Type, arg) {
1185
+ function useContext(Type, required) {
1171
1186
  const instance = useLookup().get(Type);
1172
- if (!instance && arg !== false)
1187
+ if (!instance && required !== false)
1173
1188
  throw Oops$4.NotFound(Type.name);
1174
- switch (typeof arg) {
1175
- case "string":
1176
- return instance[arg];
1177
- case "function":
1178
- React__default["default"].useLayoutEffect(() => arg(instance), []);
1179
- default:
1180
- return instance;
1181
- }
1189
+ return instance;
1182
1190
  }
1183
1191
 
1184
1192
  const Oops$3 = issues({
1185
1193
  NotAllowed: (parent, child) => `Global '${parent}' attempted to attach '${child}' but it is not also a singleton.`,
1186
1194
  AmbientRequired: (requested, requester, key) => `Attempted to find an instance of ${requested} in context. It is required for [${requester}.${key}], but one could not be found.`
1187
1195
  });
1188
- const PendingContext = new WeakMap();
1189
- const ContextWasUsed = new WeakMap();
1196
+ const Pending = new WeakMap();
1197
+ const Applied = new WeakMap();
1190
1198
  function tap(type, argument) {
1191
1199
  return add(function tap(key) {
1192
- if ("set" in type)
1200
+ if (type.global)
1193
1201
  return {
1194
1202
  recursive: true,
1195
1203
  value: type.get()
1196
1204
  };
1197
1205
  const { subject } = this;
1198
- if ("set" in subject.constructor)
1206
+ if (subject.constructor.global)
1199
1207
  throw Oops$3.NotAllowed(subject, type.name);
1200
1208
  getPending(subject).push(context => {
1201
1209
  let instance = context.get(type);
@@ -1215,24 +1223,24 @@ function tap(type, argument) {
1215
1223
  });
1216
1224
  }
1217
1225
  function usePeerContext(subject) {
1218
- if (ContextWasUsed.has(subject)) {
1219
- if (ContextWasUsed.get(subject))
1226
+ if (Applied.has(subject)) {
1227
+ if (Applied.get(subject))
1220
1228
  useLookup();
1221
1229
  return;
1222
1230
  }
1223
- const pending = PendingContext.get(subject);
1231
+ const pending = Pending.get(subject);
1224
1232
  if (pending) {
1225
1233
  const local = useLookup();
1226
1234
  for (const init of pending)
1227
1235
  init(local);
1228
- PendingContext.delete(subject);
1236
+ Pending.delete(subject);
1229
1237
  }
1230
- ContextWasUsed.set(subject, !!pending);
1238
+ Applied.set(subject, !!pending);
1231
1239
  }
1232
1240
  function getPending(subject) {
1233
- let pending = PendingContext.get(subject);
1241
+ let pending = Pending.get(subject);
1234
1242
  if (!pending)
1235
- PendingContext.set(subject, pending = []);
1243
+ Pending.set(subject, pending = []);
1236
1244
  return pending;
1237
1245
  }
1238
1246
 
@@ -1243,9 +1251,9 @@ function use(init) {
1243
1251
  return state[0];
1244
1252
  }
1245
1253
 
1246
- function useModel(source, arg, arg2) {
1254
+ function useNew(source, arg1, arg2) {
1247
1255
  const instance = React__default["default"].useMemo(() => {
1248
- const callback = arg2 || arg;
1256
+ const callback = arg2 || arg1;
1249
1257
  const instance = Model.isTypeof(source) ?
1250
1258
  source.new() :
1251
1259
  source();
@@ -1255,27 +1263,27 @@ function useModel(source, arg, arg2) {
1255
1263
  return instance;
1256
1264
  }, []);
1257
1265
  usePeerContext(instance);
1258
- if (Array.isArray(arg)) {
1266
+ if (Array.isArray(arg1)) {
1259
1267
  const update = React.useState(0)[1];
1260
1268
  React__default["default"].useLayoutEffect(() => {
1261
- if (arg.length && instance instanceof Model)
1262
- instance.on(arg, () => update(x => x + 1));
1269
+ if (arg1.length && instance instanceof Model)
1270
+ instance.on(arg1, () => update(x => x + 1));
1263
1271
  return () => {
1264
1272
  if (Model.isTypeof(source))
1265
- instance.destroy();
1273
+ instance.end();
1266
1274
  };
1267
1275
  }, []);
1268
1276
  return instance;
1269
1277
  }
1270
1278
  const local = use(refresh => (Control.for(instance).subscribe(() => refresh)));
1271
- if (typeof arg == "object")
1272
- local.assign(arg, arg2);
1279
+ if (typeof arg1 == "object")
1280
+ local.assign(arg1, arg2);
1273
1281
  React__default["default"].useLayoutEffect(() => {
1274
1282
  local.commit();
1275
1283
  return () => {
1276
1284
  local.release();
1277
1285
  if (Model.isTypeof(source))
1278
- instance.destroy();
1286
+ instance.end();
1279
1287
  };
1280
1288
  }, []);
1281
1289
  return local.proxy;
@@ -1341,7 +1349,7 @@ function useTap(source, arg1, arg2) {
1341
1349
  return () => source;
1342
1350
  if (Model.isTypeof(source))
1343
1351
  return "get" in source
1344
- ? () => source.get(true)
1352
+ ? () => source.get()
1345
1353
  : () => useContext(source);
1346
1354
  return source;
1347
1355
  }, [])();
@@ -1357,22 +1365,72 @@ function useTap(source, arg1, arg2) {
1357
1365
  return value;
1358
1366
  }
1359
1367
 
1368
+ const Global$1 = new WeakMap();
1369
+ const Oops$2 = issues({
1370
+ DoesNotExist: (name) => `Tried to access singleton ${name}, but none exist! Did you forget to initialize?\nCall ${name}.new() before attempting to access, or consider using ${name}.use() instead.`,
1371
+ AlreadyExists: (name) => `Shared instance of ${name} already exists! Consider unmounting existing, or use ${name}.reset() to force-delete it.`
1372
+ });
1360
1373
  class MVC extends Model {
1361
- static get(arg) {
1362
- return useContext(this, arg);
1374
+ end(force) {
1375
+ const type = this.constructor;
1376
+ if (type.keepAlive && !force)
1377
+ return false;
1378
+ super.end();
1379
+ Global$1.delete(type);
1380
+ return true;
1381
+ }
1382
+ static new(...args) {
1383
+ if (Global$1.has(this))
1384
+ if (this.keepAlive)
1385
+ return Global$1.get(this);
1386
+ else
1387
+ throw Oops$2.AlreadyExists(this.name);
1388
+ const instance = super.new(...args);
1389
+ if (this.global)
1390
+ Global$1.set(this, instance);
1391
+ return instance;
1392
+ }
1393
+ static get(arg1, arg2) {
1394
+ let instance;
1395
+ const required = arg1 === undefined || arg1 === true;
1396
+ if (this.global) {
1397
+ instance = Global$1.get(this);
1398
+ if (!instance && required)
1399
+ throw Oops$2.DoesNotExist(this.name);
1400
+ }
1401
+ else
1402
+ instance = useContext(this, required);
1403
+ function callback(effect) {
1404
+ try {
1405
+ React__default["default"].useLayoutEffect(() => effect(instance), []);
1406
+ }
1407
+ catch (err) {
1408
+ if (instance)
1409
+ instance.on(effect, []);
1410
+ else
1411
+ effect(undefined);
1412
+ }
1413
+ }
1414
+ if (typeof arg1 === "string")
1415
+ return instance[arg1];
1416
+ if (typeof arg1 === "function" && instance)
1417
+ callback(arg1);
1418
+ else if (arg2)
1419
+ callback(arg2);
1420
+ return instance;
1363
1421
  }
1364
1422
  static tap(key, expect) {
1365
1423
  return useTap(this, key, expect);
1366
1424
  }
1367
1425
  static use(a, b) {
1368
- return useModel(this, a, b);
1426
+ return useNew(this, a, b);
1369
1427
  }
1370
1428
  static meta(path, expect) {
1371
1429
  return useTap(() => this, path, expect);
1372
1430
  }
1373
1431
  }
1374
1432
 
1375
- const Oops$2 = issues({
1433
+ const Oops$1 = issues({
1376
1434
  BadProps: () => `Provider expects either a render function, 'get' or 'has' props.`
1377
1435
  });
1378
1436
  function Consumer(props) {
@@ -1383,58 +1441,50 @@ function Consumer(props) {
1383
1441
  if (typeof callback == "function")
1384
1442
  callback(useContext(type, !!has));
1385
1443
  else
1386
- throw Oops$2.BadProps();
1444
+ throw Oops$1.BadProps();
1387
1445
  return null;
1388
1446
  }
1389
1447
 
1390
- const Oops$1 = issues({
1448
+ const Oops = issues({
1391
1449
  NoType: () => "Provider 'for' prop must be Model, typeof Model or a collection of them."
1392
1450
  });
1393
1451
  function Provider(props) {
1394
- const context = useNewContext(props.for);
1452
+ const context = useNewContext(props.for, props.and);
1395
1453
  const render = props.children;
1396
- useAppliedProps(context, props);
1397
1454
  React__default["default"].useLayoutEffect(() => () => context.pop(), []);
1398
1455
  return React__default["default"].createElement(LookupContext.Provider, { value: context }, typeof render == "function"
1399
1456
  ? React__default["default"].createElement(RenderFunction, { context, render })
1400
1457
  : render);
1401
1458
  }
1402
- function useNewContext(include) {
1459
+ function useNewContext(include, assign) {
1403
1460
  const ambient = useLookup();
1404
- return React__default["default"].useMemo(() => {
1461
+ const context = React__default["default"].useMemo(() => {
1405
1462
  if (!include)
1406
- throw Oops$1.NoType();
1407
- const layer = ambient.push();
1463
+ throw Oops.NoType();
1464
+ const next = ambient.push();
1408
1465
  function register(I) {
1409
1466
  if (I instanceof Model)
1410
- layer.inject(I.constructor, I, false);
1467
+ next.add(I.constructor, I, false);
1411
1468
  else
1412
- layer.inject(I, I.new(), true);
1469
+ next.add(I, I.new(), true);
1413
1470
  }
1414
1471
  if (include instanceof Model || typeof include == "function")
1415
1472
  register(include);
1416
1473
  else
1417
1474
  values(include).forEach(register);
1418
- for (const instance of layer.local)
1475
+ for (const instance of next.local)
1419
1476
  if (instance)
1420
1477
  for (const apply of getPending(instance))
1421
- apply(layer);
1422
- return layer;
1478
+ apply(next);
1479
+ return next;
1423
1480
  }, []);
1424
- }
1425
- function useAppliedProps(context, props) {
1426
- const apply = React__default["default"].useMemo(() => {
1427
- return (props) => {
1428
- entries(props).forEach(([key, value]) => {
1429
- if (key == "for" || key == "children")
1430
- return;
1431
- for (const into of context.local)
1432
- if (into && key in into)
1433
- into[key] = value;
1434
- });
1435
- };
1436
- }, []);
1437
- apply(props);
1481
+ if (assign) {
1482
+ for (const key in assign)
1483
+ for (const into of context.local)
1484
+ if (into && key in into)
1485
+ into[key] = assign[key];
1486
+ }
1487
+ return context;
1438
1488
  }
1439
1489
  function RenderFunction(props) {
1440
1490
  const hook = use(refresh => {
@@ -1448,57 +1498,9 @@ function RenderFunction(props) {
1448
1498
  return props.render(hook.proxy);
1449
1499
  }
1450
1500
 
1451
- const Oops = issues({
1452
- DoesNotExist: (name) => `Tried to access singleton ${name}, but none exist! Did you forget to initialize?\nCall ${name}.new() before attempting to access, or consider using ${name}.use() instead.`,
1453
- AlreadyExists: (name) => `Shared instance of ${name} already exists! Consider unmounting existing, or use ${name}.reset() to force-delete it.`
1454
- });
1455
- const Active = new WeakMap();
1456
1501
  class Global extends MVC {
1457
- constructor() {
1458
- super();
1459
- Active.set(this.constructor, this);
1460
- }
1461
- destroy(force) {
1462
- const type = this.constructor;
1463
- if (type.keepAlive && !force)
1464
- return false;
1465
- Control.for(this).clear();
1466
- Active.delete(type);
1467
- return true;
1468
- }
1469
- static new(...args) {
1470
- if (Active.has(this))
1471
- if (this.keepAlive)
1472
- return Active.get(this);
1473
- else
1474
- throw Oops.AlreadyExists(this.name);
1475
- const instance = new this(...args);
1476
- Control.for(instance);
1477
- return instance;
1478
- }
1479
- /**
1480
- * Update the active instance of this class.
1481
- * Returns a thenable; resolves after successful update.
1482
- * If instance does not already exist, one will be created.
1483
- */
1484
- static set(values) {
1485
- return this.get(true).update(values);
1486
- }
1487
- static get(arg) {
1488
- const instance = Active.get(this);
1489
- if (!instance && arg !== false)
1490
- throw Oops.DoesNotExist(this.name);
1491
- return (typeof arg == "string" ?
1492
- instance[arg] :
1493
- instance);
1494
- }
1495
- /** Destroy current instance of Global, if it exists. */
1496
- static reset() {
1497
- const current = Active.get(this);
1498
- if (current)
1499
- current.destroy(true);
1500
- }
1501
1502
  }
1503
+ Global.global = true;
1502
1504
  Global.keepAlive = true;
1503
1505
 
1504
1506
  const LOCAL = "$debug local";
@@ -1554,7 +1556,6 @@ exports.set = set;
1554
1556
  exports.tap = tap;
1555
1557
  exports.use = use$1;
1556
1558
  exports.useContext = useContext;
1557
- exports.useModel = useModel;
1559
+ exports.useNew = useNew;
1558
1560
  exports.useTap = useTap;
1559
- exports.useValue = useValue;
1560
1561
  //# sourceMappingURL=index.js.map