@expressive/mvc 0.9.6 → 0.10.1

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.esm.js CHANGED
@@ -1,6 +1,9 @@
1
1
  import React, { useState, useMemo } from 'react';
2
2
 
3
- const { assign, create, defineProperties, defineProperty, entries, getPrototypeOf, getOwnPropertyNames, getOwnPropertyDescriptor, getOwnPropertySymbols, values } = Object;
3
+ const { assign, create, defineProperties, defineProperty, entries, getPrototypeOf, getOwnPropertyNames, getOwnPropertyDescriptor, getOwnPropertySymbols, values } = Object;
4
+ function unique(array) {
5
+ return Array.from(new Set(array));
6
+ }
4
7
 
5
8
  class Issue extends Error {
6
9
  constructor() {
@@ -651,6 +654,7 @@ function createValueEffect(callback) {
651
654
  function ref(arg, mapper) {
652
655
  return add(function ref(key) {
653
656
  let value = {};
657
+ this.state.set(key, undefined);
654
658
  if (typeof arg != "object")
655
659
  value = createRef(this, key, arg);
656
660
  else
@@ -947,31 +951,31 @@ const Oops$6 = issues({
947
951
  StrictNoUpdate: () => `Update is not expected but one is in progress!`
948
952
  });
949
953
  function addEventListener(source, arg1, arg2, arg3) {
954
+ if (typeof arg1 == "string")
955
+ arg1 = [arg1];
956
+ return Control.for(source, control => {
957
+ for (const key of arg1)
958
+ try {
959
+ void source[key];
960
+ }
961
+ catch (e) { }
962
+ const removeListener = control.addListener(key => {
963
+ if (!key && !arg1.length)
964
+ arg2.call(source, []);
965
+ if (arg1.includes(key)) {
966
+ if (arg3)
967
+ removeListener();
968
+ return arg2.bind(source);
969
+ }
970
+ });
971
+ return removeListener;
972
+ });
973
+ }
974
+ function awaitUpdate(source, arg1, arg2) {
950
975
  const single = typeof arg1 == "string";
951
976
  const keys = typeof arg1 == "string" ? [arg1] :
952
977
  typeof arg1 == "object" ? arg1 :
953
978
  undefined;
954
- if (typeof arg2 == "function" && keys)
955
- return Control.for(source, control => {
956
- for (const key of keys)
957
- try {
958
- void source[key];
959
- }
960
- catch (e) { }
961
- const invoke = single
962
- ? () => arg2.call(source, control.state.get(arg1), arg1)
963
- : (frame) => arg2.call(source, frame);
964
- const removeListener = control.addListener(key => {
965
- if (!key && !keys.length)
966
- invoke([]);
967
- if (keys.includes(key)) {
968
- if (arg3)
969
- removeListener();
970
- return invoke;
971
- }
972
- });
973
- return removeListener;
974
- });
975
979
  return new Promise((resolve, reject) => {
976
980
  const timeout = (message) => {
977
981
  const k = keys ? keys.join(", ") : "any";
@@ -1036,34 +1040,47 @@ class Model {
1036
1040
  on(arg1, arg2, arg3) {
1037
1041
  if (typeof arg1 == "function")
1038
1042
  return createEffect(this.is, arg1, arg2);
1039
- return addEventListener(this.is, arg1, arg2, arg3);
1043
+ if (typeof arg2 != "function")
1044
+ return awaitUpdate(this.is, arg1, arg2);
1045
+ if (arg1)
1046
+ return addEventListener(this.is, arg1, arg2, arg3);
1040
1047
  }
1041
- export(subset) {
1048
+ get(arg1, arg2) {
1042
1049
  const { state } = Control.for(this);
1043
- const output = {};
1044
- const keys = subset || state.keys();
1045
- for (const key of keys)
1046
- output[key] = state.get(key);
1047
- return output;
1050
+ const extract = typeof arg1 == "string"
1051
+ ? () => state.get(arg1)
1052
+ : () => {
1053
+ const output = {};
1054
+ for (const key of arg1 || state.keys())
1055
+ output[key] = state.get(key);
1056
+ return output;
1057
+ };
1058
+ if (!arg1 || arg2 === undefined)
1059
+ return extract();
1060
+ if (typeof arg2 == "function")
1061
+ return this.on(arg1, () => arg2(extract()));
1062
+ const timeout = typeof arg2 == "number" ? arg2 : undefined;
1063
+ return this.on(arg1, timeout).then(extract);
1048
1064
  }
1049
- update(arg1, arg2) {
1065
+ set(arg1, arg2) {
1050
1066
  const control = Control.for(this);
1051
- if (typeof arg1 == "object") {
1052
- for (const key in arg1)
1067
+ if (typeof arg1 == "object")
1068
+ for (const key in arg1) {
1053
1069
  if (arg2 === true || (arg2 ? arg2.includes(key) : control.state.has(key))) {
1054
1070
  control.state.set(key, arg1[key]);
1055
1071
  control.update(key);
1056
1072
  }
1057
- }
1073
+ }
1058
1074
  else if (typeof arg1 == "string") {
1059
1075
  control.update(arg1);
1060
- if (1 in arguments && arg1 in this) {
1061
- const method = this[arg1];
1062
- if (typeof method == "function")
1063
- if (typeof arg2 != "boolean")
1076
+ if (1 in arguments) {
1077
+ if (control.state.has(arg1))
1078
+ control.state.set(arg1, arg2);
1079
+ else if (arg1 in this) {
1080
+ const method = this[arg1];
1081
+ if (typeof method == "function")
1064
1082
  method.call(this, arg2);
1065
- else if (arg2)
1066
- method.call(this);
1083
+ }
1067
1084
  }
1068
1085
  }
1069
1086
  return {
@@ -1077,7 +1094,7 @@ class Model {
1077
1094
  /**
1078
1095
  * Clean up side effects and mark this instance for garbage-collection.
1079
1096
  */
1080
- destroy() {
1097
+ end() {
1081
1098
  Control.for(this).clear();
1082
1099
  }
1083
1100
  /**
@@ -1116,9 +1133,7 @@ class Lookup {
1116
1133
  this.table = new Map();
1117
1134
  }
1118
1135
  get local() {
1119
- return [
1120
- ...new Set(getOwnPropertySymbols(this).map(symbol => this[symbol]))
1121
- ];
1136
+ return unique(getOwnPropertySymbols(this).map(symbol => this[symbol]));
1122
1137
  }
1123
1138
  key(T) {
1124
1139
  let key = this.table.get(T);
@@ -1131,10 +1146,7 @@ class Lookup {
1131
1146
  get(T) {
1132
1147
  return this[this.key(T)];
1133
1148
  }
1134
- push() {
1135
- return create(this);
1136
- }
1137
- inject(T, I, writable) {
1149
+ add(T, I, writable) {
1138
1150
  do {
1139
1151
  const key = this.key(T);
1140
1152
  const conflict = this.hasOwnProperty(key);
@@ -1146,6 +1158,9 @@ class Lookup {
1146
1158
  T = getPrototypeOf(T);
1147
1159
  } while (T !== Model);
1148
1160
  }
1161
+ push() {
1162
+ return create(this);
1163
+ }
1149
1164
  pop() {
1150
1165
  const items = new Set();
1151
1166
  for (const key of getOwnPropertySymbols(this)) {
@@ -1154,40 +1169,33 @@ class Lookup {
1154
1169
  items.add(entry.value);
1155
1170
  }
1156
1171
  for (const model of items)
1157
- model.destroy();
1172
+ model.end();
1158
1173
  }
1159
1174
  }
1160
1175
  const LookupContext = React.createContext(new Lookup());
1161
1176
  const useLookup = () => React.useContext(LookupContext);
1162
- function useContext(Type, arg) {
1177
+ function useContext(Type, required) {
1163
1178
  const instance = useLookup().get(Type);
1164
- if (!instance && arg !== false)
1179
+ if (!instance && required !== false)
1165
1180
  throw Oops$4.NotFound(Type.name);
1166
- switch (typeof arg) {
1167
- case "string":
1168
- return instance[arg];
1169
- case "function":
1170
- React.useLayoutEffect(() => arg(instance), []);
1171
- default:
1172
- return instance;
1173
- }
1181
+ return instance;
1174
1182
  }
1175
1183
 
1176
1184
  const Oops$3 = issues({
1177
1185
  NotAllowed: (parent, child) => `Global '${parent}' attempted to attach '${child}' but it is not also a singleton.`,
1178
1186
  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.`
1179
1187
  });
1180
- const PendingContext = new WeakMap();
1181
- const ContextWasUsed = new WeakMap();
1188
+ const Pending = new WeakMap();
1189
+ const Applied = new WeakMap();
1182
1190
  function tap(type, argument) {
1183
1191
  return add(function tap(key) {
1184
- if ("set" in type)
1192
+ if (type.global)
1185
1193
  return {
1186
1194
  recursive: true,
1187
1195
  value: type.get()
1188
1196
  };
1189
1197
  const { subject } = this;
1190
- if ("set" in subject.constructor)
1198
+ if (subject.constructor.global)
1191
1199
  throw Oops$3.NotAllowed(subject, type.name);
1192
1200
  getPending(subject).push(context => {
1193
1201
  let instance = context.get(type);
@@ -1207,24 +1215,24 @@ function tap(type, argument) {
1207
1215
  });
1208
1216
  }
1209
1217
  function usePeerContext(subject) {
1210
- if (ContextWasUsed.has(subject)) {
1211
- if (ContextWasUsed.get(subject))
1218
+ if (Applied.has(subject)) {
1219
+ if (Applied.get(subject))
1212
1220
  useLookup();
1213
1221
  return;
1214
1222
  }
1215
- const pending = PendingContext.get(subject);
1223
+ const pending = Pending.get(subject);
1216
1224
  if (pending) {
1217
1225
  const local = useLookup();
1218
1226
  for (const init of pending)
1219
1227
  init(local);
1220
- PendingContext.delete(subject);
1228
+ Pending.delete(subject);
1221
1229
  }
1222
- ContextWasUsed.set(subject, !!pending);
1230
+ Applied.set(subject, !!pending);
1223
1231
  }
1224
1232
  function getPending(subject) {
1225
- let pending = PendingContext.get(subject);
1233
+ let pending = Pending.get(subject);
1226
1234
  if (!pending)
1227
- PendingContext.set(subject, pending = []);
1235
+ Pending.set(subject, pending = []);
1228
1236
  return pending;
1229
1237
  }
1230
1238
 
@@ -1235,9 +1243,9 @@ function use(init) {
1235
1243
  return state[0];
1236
1244
  }
1237
1245
 
1238
- function useModel(source, arg, arg2) {
1246
+ function useNew(source, arg1, arg2) {
1239
1247
  const instance = React.useMemo(() => {
1240
- const callback = arg2 || arg;
1248
+ const callback = arg2 || arg1;
1241
1249
  const instance = Model.isTypeof(source) ?
1242
1250
  source.new() :
1243
1251
  source();
@@ -1247,27 +1255,27 @@ function useModel(source, arg, arg2) {
1247
1255
  return instance;
1248
1256
  }, []);
1249
1257
  usePeerContext(instance);
1250
- if (Array.isArray(arg)) {
1258
+ if (Array.isArray(arg1)) {
1251
1259
  const update = useState(0)[1];
1252
1260
  React.useLayoutEffect(() => {
1253
- if (arg.length && instance instanceof Model)
1254
- instance.on(arg, () => update(x => x + 1));
1261
+ if (arg1.length && instance instanceof Model)
1262
+ instance.on(arg1, () => update(x => x + 1));
1255
1263
  return () => {
1256
1264
  if (Model.isTypeof(source))
1257
- instance.destroy();
1265
+ instance.end();
1258
1266
  };
1259
1267
  }, []);
1260
1268
  return instance;
1261
1269
  }
1262
1270
  const local = use(refresh => (Control.for(instance).subscribe(() => refresh)));
1263
- if (typeof arg == "object")
1264
- local.assign(arg, arg2);
1271
+ if (typeof arg1 == "object")
1272
+ local.assign(arg1, arg2);
1265
1273
  React.useLayoutEffect(() => {
1266
1274
  local.commit();
1267
1275
  return () => {
1268
1276
  local.release();
1269
1277
  if (Model.isTypeof(source))
1270
- instance.destroy();
1278
+ instance.end();
1271
1279
  };
1272
1280
  }, []);
1273
1281
  return local.proxy;
@@ -1316,9 +1324,11 @@ function useValue(source, compute, suspend) {
1316
1324
  }
1317
1325
  defineProperty(sub, "proxy", {
1318
1326
  get() {
1319
- if (value === undefined && suspend)
1327
+ if (value !== undefined)
1328
+ return value;
1329
+ if (suspend)
1320
1330
  throw new Promise(res => retry = res);
1321
- return value;
1331
+ return null;
1322
1332
  }
1323
1333
  });
1324
1334
  return sub;
@@ -1333,7 +1343,7 @@ function useTap(source, arg1, arg2) {
1333
1343
  return () => source;
1334
1344
  if (Model.isTypeof(source))
1335
1345
  return "get" in source
1336
- ? () => source.get(true)
1346
+ ? () => source.get()
1337
1347
  : () => useContext(source);
1338
1348
  return source;
1339
1349
  }, [])();
@@ -1349,22 +1359,72 @@ function useTap(source, arg1, arg2) {
1349
1359
  return value;
1350
1360
  }
1351
1361
 
1362
+ const Global$1 = new WeakMap();
1363
+ const Oops$2 = issues({
1364
+ 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.`,
1365
+ AlreadyExists: (name) => `Shared instance of ${name} already exists! Consider unmounting existing, or use ${name}.reset() to force-delete it.`
1366
+ });
1352
1367
  class MVC extends Model {
1353
- static get(arg) {
1354
- return useContext(this, arg);
1368
+ end(force) {
1369
+ const type = this.constructor;
1370
+ if (type.keepAlive && !force)
1371
+ return false;
1372
+ super.end();
1373
+ Global$1.delete(type);
1374
+ return true;
1375
+ }
1376
+ static new(...args) {
1377
+ if (Global$1.has(this))
1378
+ if (this.keepAlive)
1379
+ return Global$1.get(this);
1380
+ else
1381
+ throw Oops$2.AlreadyExists(this.name);
1382
+ const instance = super.new(...args);
1383
+ if (this.global)
1384
+ Global$1.set(this, instance);
1385
+ return instance;
1386
+ }
1387
+ static get(arg1, arg2) {
1388
+ let instance;
1389
+ const required = arg1 === undefined || arg1 === true;
1390
+ if (this.global) {
1391
+ instance = Global$1.get(this);
1392
+ if (!instance && required)
1393
+ throw Oops$2.DoesNotExist(this.name);
1394
+ }
1395
+ else
1396
+ instance = useContext(this, required);
1397
+ function callback(effect) {
1398
+ try {
1399
+ React.useLayoutEffect(() => effect(instance), []);
1400
+ }
1401
+ catch (err) {
1402
+ if (instance)
1403
+ instance.on(effect, []);
1404
+ else
1405
+ effect(undefined);
1406
+ }
1407
+ }
1408
+ if (typeof arg1 === "string")
1409
+ return instance[arg1];
1410
+ if (typeof arg1 === "function" && instance)
1411
+ callback(arg1);
1412
+ else if (arg2)
1413
+ callback(arg2);
1414
+ return instance;
1355
1415
  }
1356
1416
  static tap(key, expect) {
1357
1417
  return useTap(this, key, expect);
1358
1418
  }
1359
1419
  static use(a, b) {
1360
- return useModel(this, a, b);
1420
+ return useNew(this, a, b);
1361
1421
  }
1362
1422
  static meta(path, expect) {
1363
1423
  return useTap(() => this, path, expect);
1364
1424
  }
1365
1425
  }
1366
1426
 
1367
- const Oops$2 = issues({
1427
+ const Oops$1 = issues({
1368
1428
  BadProps: () => `Provider expects either a render function, 'get' or 'has' props.`
1369
1429
  });
1370
1430
  function Consumer(props) {
@@ -1375,58 +1435,50 @@ function Consumer(props) {
1375
1435
  if (typeof callback == "function")
1376
1436
  callback(useContext(type, !!has));
1377
1437
  else
1378
- throw Oops$2.BadProps();
1438
+ throw Oops$1.BadProps();
1379
1439
  return null;
1380
1440
  }
1381
1441
 
1382
- const Oops$1 = issues({
1442
+ const Oops = issues({
1383
1443
  NoType: () => "Provider 'for' prop must be Model, typeof Model or a collection of them."
1384
1444
  });
1385
1445
  function Provider(props) {
1386
- const context = useNewContext(props.for);
1446
+ const context = useNewContext(props.for, props.and);
1387
1447
  const render = props.children;
1388
- useAppliedProps(context, props);
1389
1448
  React.useLayoutEffect(() => () => context.pop(), []);
1390
1449
  return React.createElement(LookupContext.Provider, { value: context }, typeof render == "function"
1391
1450
  ? React.createElement(RenderFunction, { context, render })
1392
1451
  : render);
1393
1452
  }
1394
- function useNewContext(include) {
1453
+ function useNewContext(include, assign) {
1395
1454
  const ambient = useLookup();
1396
- return React.useMemo(() => {
1455
+ const context = React.useMemo(() => {
1397
1456
  if (!include)
1398
- throw Oops$1.NoType();
1399
- const layer = ambient.push();
1457
+ throw Oops.NoType();
1458
+ const next = ambient.push();
1400
1459
  function register(I) {
1401
1460
  if (I instanceof Model)
1402
- layer.inject(I.constructor, I, false);
1461
+ next.add(I.constructor, I, false);
1403
1462
  else
1404
- layer.inject(I, I.new(), true);
1463
+ next.add(I, I.new(), true);
1405
1464
  }
1406
1465
  if (include instanceof Model || typeof include == "function")
1407
1466
  register(include);
1408
1467
  else
1409
1468
  values(include).forEach(register);
1410
- for (const instance of layer.local)
1469
+ for (const instance of next.local)
1411
1470
  if (instance)
1412
1471
  for (const apply of getPending(instance))
1413
- apply(layer);
1414
- return layer;
1415
- }, []);
1416
- }
1417
- function useAppliedProps(context, props) {
1418
- const apply = React.useMemo(() => {
1419
- return (props) => {
1420
- entries(props).forEach(([key, value]) => {
1421
- if (key == "for" || key == "children")
1422
- return;
1423
- for (const into of context.local)
1424
- if (into && key in into)
1425
- into[key] = value;
1426
- });
1427
- };
1472
+ apply(next);
1473
+ return next;
1428
1474
  }, []);
1429
- apply(props);
1475
+ if (assign) {
1476
+ for (const key in assign)
1477
+ for (const into of context.local)
1478
+ if (into && key in into)
1479
+ into[key] = assign[key];
1480
+ }
1481
+ return context;
1430
1482
  }
1431
1483
  function RenderFunction(props) {
1432
1484
  const hook = use(refresh => {
@@ -1440,57 +1492,9 @@ function RenderFunction(props) {
1440
1492
  return props.render(hook.proxy);
1441
1493
  }
1442
1494
 
1443
- const Oops = issues({
1444
- 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.`,
1445
- AlreadyExists: (name) => `Shared instance of ${name} already exists! Consider unmounting existing, or use ${name}.reset() to force-delete it.`
1446
- });
1447
- const Active = new WeakMap();
1448
1495
  class Global extends MVC {
1449
- constructor() {
1450
- super();
1451
- Active.set(this.constructor, this);
1452
- }
1453
- destroy(force) {
1454
- const type = this.constructor;
1455
- if (type.keepAlive && !force)
1456
- return false;
1457
- Control.for(this).clear();
1458
- Active.delete(type);
1459
- return true;
1460
- }
1461
- static new(...args) {
1462
- if (Active.has(this))
1463
- if (this.keepAlive)
1464
- return Active.get(this);
1465
- else
1466
- throw Oops.AlreadyExists(this.name);
1467
- const instance = new this(...args);
1468
- Control.for(instance);
1469
- return instance;
1470
- }
1471
- /**
1472
- * Update the active instance of this class.
1473
- * Returns a thenable; resolves after successful update.
1474
- * If instance does not already exist, one will be created.
1475
- */
1476
- static set(values) {
1477
- return this.get(true).update(values);
1478
- }
1479
- static get(arg) {
1480
- const instance = Active.get(this);
1481
- if (!instance && arg !== false)
1482
- throw Oops.DoesNotExist(this.name);
1483
- return (typeof arg == "string" ?
1484
- instance[arg] :
1485
- instance);
1486
- }
1487
- /** Destroy current instance of Global, if it exists. */
1488
- static reset() {
1489
- const current = Active.get(this);
1490
- if (current)
1491
- current.destroy(true);
1492
- }
1493
1496
  }
1497
+ Global.global = true;
1494
1498
  Global.keepAlive = true;
1495
1499
 
1496
1500
  const LOCAL = "$debug local";
@@ -1530,5 +1534,5 @@ const Debug = {
1530
1534
  UPDATE
1531
1535
  };
1532
1536
 
1533
- export { Consumer, Debug, Global, MVC, MVC as Model, Provider, add, MVC as default, get, has, ref, run, set, tap, use$1 as use, useContext, useModel, useTap, useValue };
1537
+ export { Consumer, Debug, Global, MVC, MVC as Model, Provider, add, MVC as default, get, has, ref, run, set, tap, use$1 as use, useContext, useNew, useTap };
1534
1538
  //# sourceMappingURL=index.esm.js.map