@odoo/owl 3.0.0-alpha.32 → 3.0.0-alpha.34

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/dist/owl.es.js CHANGED
@@ -115,13 +115,15 @@ function removeSources(computation) {
115
115
  sources.clear();
116
116
  }
117
117
  function disposeComputation(computation) {
118
- for (const source of computation.sources) {
118
+ const sources = computation.sources;
119
+ for (const source of sources) {
119
120
  source.observers.delete(computation);
120
- if ("compute" in source && source.isDerived && source.observers.size === 0) {
121
- disposeComputation(source);
121
+ const derived = source;
122
+ if (derived.isDerived && derived.observers.size === 0) {
123
+ disposeComputation(derived);
122
124
  }
123
125
  }
124
- computation.sources.clear();
126
+ sources.clear();
125
127
  computation.state = 1;
126
128
  }
127
129
  function markDownstream(computation) {
@@ -162,6 +164,7 @@ function useScope() {
162
164
  }
163
165
  var Scope = class {
164
166
  app;
167
+ pluginManager;
165
168
  status = STATUS.NEW;
166
169
  computations = [];
167
170
  willStart = [];
@@ -169,6 +172,7 @@ var Scope = class {
169
172
  _destroyCbs = null;
170
173
  constructor(app) {
171
174
  this.app = app;
175
+ this.pluginManager = app.pluginManager;
172
176
  }
173
177
  /**
174
178
  * Pushes this scope on the stack for the duration of `callback`. Any code
@@ -612,14 +616,19 @@ function computed(getter, options = {}) {
612
616
  }
613
617
  function effect(fn) {
614
618
  const computation = createComputation(() => {
615
- setComputation(void 0);
616
- unsubscribeEffect(computation);
617
- setComputation(computation);
619
+ if (computation.value || computation.observers.size) {
620
+ setComputation(void 0);
621
+ unsubscribeEffect(computation);
622
+ setComputation(computation);
623
+ } else {
624
+ removeSources(computation);
625
+ }
618
626
  return fn();
619
627
  }, false);
620
628
  getCurrentComputation()?.observers.add(computation);
621
629
  updateComputation(computation);
622
630
  return function cleanupEffect2() {
631
+ computation.state = 0;
623
632
  const previousComputation = getCurrentComputation();
624
633
  setComputation(void 0);
625
634
  unsubscribeEffect(computation);
@@ -631,7 +640,6 @@ function unsubscribeEffect(effect2) {
631
640
  cleanupEffect(effect2);
632
641
  for (const childEffect of effect2.observers) {
633
642
  childEffect.state = 0;
634
- removeSources(childEffect);
635
643
  unsubscribeEffect(childEffect);
636
644
  }
637
645
  effect2.observers.clear();
@@ -708,19 +716,27 @@ function asyncComputed(fetcher, options = {}) {
708
716
  read.dispose = dispose;
709
717
  return read;
710
718
  }
719
+ function safeReplacer(knownObjects, _key, value) {
720
+ if (typeof value === "function") {
721
+ return value.name || "[Function]";
722
+ }
723
+ if (value && typeof value === "object") {
724
+ const ctor = value.constructor;
725
+ if (ctor && ctor !== Object && ctor !== Array) {
726
+ return `[Instance of ${ctor.name || "anonymous"}]`;
727
+ }
728
+ if (knownObjects.includes(value)) {
729
+ return `[Known object]`;
730
+ }
731
+ knownObjects.push(value);
732
+ }
733
+ return value;
734
+ }
711
735
  function assertType(value, validation, errorMessage = "Value does not match the type") {
712
736
  const issues = validateType(value, validation);
713
737
  if (issues.length) {
714
- const issueStrings = JSON.stringify(
715
- issues,
716
- (key, value2) => {
717
- if (typeof value2 === "function") {
718
- return value2.name;
719
- }
720
- return value2;
721
- },
722
- 2
723
- );
738
+ const knownObjects = [];
739
+ const issueStrings = JSON.stringify(issues, safeReplacer.bind(null, knownObjects), 2);
724
740
  throw new OwlError(`${errorMessage}
725
741
  ${issueStrings}`);
726
742
  }
@@ -736,7 +752,7 @@ function createContext(issues, value, path, parent) {
736
752
  addIssue(issue) {
737
753
  issues.push({
738
754
  received: this.value,
739
- path: this.path,
755
+ path: this.path.join(" > "),
740
756
  ...issue
741
757
  });
742
758
  },
@@ -861,15 +877,20 @@ function validateObject(context, schema, isStrict) {
861
877
  return;
862
878
  }
863
879
  const isShape = !Array.isArray(schema);
864
- let shape = schema;
865
- if (Array.isArray(schema)) {
880
+ let shape;
881
+ let keys;
882
+ if (isShape) {
883
+ keys = Object.keys(schema);
884
+ shape = schema;
885
+ } else {
886
+ keys = schema;
866
887
  shape = {};
867
- for (const key of schema) {
888
+ for (const key of keys) {
868
889
  shape[key] = null;
869
890
  }
870
891
  }
871
892
  const missingKeys = [];
872
- for (const key in shape) {
893
+ for (const key of keys) {
873
894
  const property = key.endsWith("?") ? key.slice(0, -1) : key;
874
895
  if (context.value[property] === void 0) {
875
896
  if (!key.endsWith("?")) {
@@ -884,20 +905,22 @@ function validateObject(context, schema, isStrict) {
884
905
  if (missingKeys.length) {
885
906
  context.addIssue({
886
907
  message: "object value has missing keys",
887
- missingKeys
908
+ missingKeys,
909
+ expectedKeys: keys
888
910
  });
889
911
  }
890
912
  if (isStrict) {
891
913
  const unknownKeys = [];
892
914
  for (const key in context.value) {
893
- if (!(key in shape) && !(`${key}?` in shape)) {
915
+ if (!keys.includes(key) && !(`${key}?` in shape)) {
894
916
  unknownKeys.push(key);
895
917
  }
896
918
  }
897
919
  if (unknownKeys.length) {
898
920
  context.addIssue({
899
921
  message: "object value has unknown keys",
900
- unknownKeys
922
+ unknownKeys,
923
+ expectedKeys: keys
901
924
  });
902
925
  }
903
926
  }
@@ -1103,6 +1126,13 @@ var Plugin = class {
1103
1126
  static set id(shadowId) {
1104
1127
  this._shadowId = shadowId;
1105
1128
  }
1129
+ // Plugins passed to `startPlugins` are started in batches of equal sequence,
1130
+ // ascending (lower first), like Resource/Registry. Each batch's onWillStart
1131
+ // callbacks fully settle before the next batch is instantiated, so
1132
+ // foundational plugins (low sequence) are ready before later plugins even
1133
+ // run their setup. Explicit `plugin(X)` dependencies bypass batching and
1134
+ // start immediately.
1135
+ static sequence = 50;
1106
1136
  __owl__;
1107
1137
  constructor(manager) {
1108
1138
  this.__owl__ = manager;
@@ -1113,14 +1143,17 @@ var Plugin = class {
1113
1143
  var PluginManager = class extends Scope {
1114
1144
  config;
1115
1145
  plugins;
1116
- // Resolves once all pending plugin willStart callbacks have settled. The
1117
- // scope transitions to MOUNTED as the last step of this chain. Consumers
1118
- // (App.mount, providePlugins) await this before treating the manager as
1119
- // ready. `willStart` itself is inherited from Scope.
1146
+ // Resolves once all batches of plugins have started and their willStart
1147
+ // callbacks have settled. The scope transitions to MOUNTED as the last step
1148
+ // of this chain. Consumers (the root's mount(), providePlugins) await this
1149
+ // before treating the manager as ready. `willStart` itself is inherited
1150
+ // from Scope.
1120
1151
  ready = Promise.resolve();
1152
+ hasPendingReady = false;
1121
1153
  constructor(app, options = {}) {
1122
1154
  super(app);
1123
1155
  this.config = options.config ?? {};
1156
+ this.pluginManager = this;
1124
1157
  if (options.parent) {
1125
1158
  const parent = options.parent;
1126
1159
  parent.onDestroy(() => this.destroy());
@@ -1157,24 +1190,61 @@ var PluginManager = class extends Scope {
1157
1190
  return plugin2;
1158
1191
  }
1159
1192
  startPlugins(pluginConstructors) {
1160
- scopeStack.push(this);
1161
- try {
1162
- for (const pluginConstructor of pluginConstructors) {
1163
- this.startPlugin(pluginConstructor);
1193
+ const fresh = pluginConstructors.filter((ctor) => {
1194
+ if (!ctor.id || this.plugins.hasOwnProperty(ctor.id)) {
1195
+ this.startPlugin(ctor);
1196
+ return false;
1164
1197
  }
1165
- } finally {
1166
- scopeStack.pop();
1198
+ return true;
1199
+ });
1200
+ if (!fresh.length) {
1201
+ return;
1167
1202
  }
1168
- const pending = this.willStart.splice(0);
1169
- if (pending.length) {
1170
- this.ready = Promise.all(pending.map((fn) => fn())).then(() => {
1171
- if (this.status < STATUS.MOUNTED) {
1172
- this.status = STATUS.MOUNTED;
1203
+ fresh.sort((p1, p2) => p1.sequence - p2.sequence);
1204
+ const batches = [];
1205
+ for (const ctor of fresh) {
1206
+ const batch = batches[batches.length - 1];
1207
+ if (batch && batch[0].sequence === ctor.sequence) {
1208
+ batch.push(ctor);
1209
+ } else {
1210
+ batches.push([ctor]);
1211
+ }
1212
+ }
1213
+ const startBatch = (batch) => {
1214
+ scopeStack.push(this);
1215
+ try {
1216
+ for (const ctor of batch) {
1217
+ this.startPlugin(ctor);
1173
1218
  }
1174
- });
1175
- } else if (this.status < STATUS.MOUNTED) {
1176
- this.status = STATUS.MOUNTED;
1219
+ } finally {
1220
+ scopeStack.pop();
1221
+ }
1222
+ const pending = this.willStart.splice(0);
1223
+ return pending.length ? Promise.all(pending.map((fn) => fn())) : null;
1224
+ };
1225
+ let chain = this.hasPendingReady ? this.ready : null;
1226
+ for (const batch of batches) {
1227
+ if (chain) {
1228
+ chain = chain.then(() => startBatch(batch));
1229
+ } else {
1230
+ chain = startBatch(batch);
1231
+ }
1232
+ }
1233
+ if (!chain) {
1234
+ if (this.status < STATUS.MOUNTED) {
1235
+ this.status = STATUS.MOUNTED;
1236
+ }
1237
+ return;
1177
1238
  }
1239
+ this.hasPendingReady = true;
1240
+ const ready = this.ready = chain.then(() => {
1241
+ if (this.status < STATUS.MOUNTED) {
1242
+ this.status = STATUS.MOUNTED;
1243
+ }
1244
+ if (this.ready === ready) {
1245
+ this.hasPendingReady = false;
1246
+ }
1247
+ });
1178
1248
  }
1179
1249
  };
1180
1250
  function startPlugins(manager, plugins) {
@@ -1189,9 +1259,103 @@ function startPlugins(manager, plugins) {
1189
1259
  );
1190
1260
  }
1191
1261
  }
1262
+ function onWillStart(fn) {
1263
+ const scope = useScope();
1264
+ scope.willStart.push(scope.decorate(fn, "onWillStart"));
1265
+ }
1266
+ function onWillDestroy(fn) {
1267
+ const scope = useScope();
1268
+ scope.onDestroy(scope.decorate(fn, "onWillDestroy"));
1269
+ }
1270
+ function useEffect(fn) {
1271
+ onWillDestroy(effect(fn));
1272
+ }
1273
+ function useListener(target, eventName, handler, eventParams) {
1274
+ if (typeof target === "function") {
1275
+ useEffect(() => {
1276
+ const el = target();
1277
+ if (el) {
1278
+ el.addEventListener(eventName, handler, eventParams);
1279
+ return () => el.removeEventListener(eventName, handler, eventParams);
1280
+ }
1281
+ return;
1282
+ });
1283
+ } else {
1284
+ target.addEventListener(eventName, handler, eventParams);
1285
+ onWillDestroy(() => target.removeEventListener(eventName, handler, eventParams));
1286
+ }
1287
+ }
1288
+ function useApp() {
1289
+ return useScope().app;
1290
+ }
1291
+ function plugin(pluginType) {
1292
+ const scope = useScope();
1293
+ let plugin2 = scope.pluginManager.getPluginById(pluginType.id);
1294
+ if (!plugin2) {
1295
+ if (scope instanceof PluginManager) {
1296
+ plugin2 = scope.pluginManager.startPlugin(pluginType);
1297
+ } else {
1298
+ throw new OwlError(`Unknown plugin "${pluginType.id}"`);
1299
+ }
1300
+ }
1301
+ return plugin2;
1302
+ }
1303
+ function config(key, type, defaultValue) {
1304
+ const scope = useScope();
1305
+ if (!(scope instanceof PluginManager)) {
1306
+ throw new OwlError("Expected to be in a plugin scope");
1307
+ }
1308
+ if (scope.app.dev && type) {
1309
+ assertType(scope.config, types.object({ [key]: type }), "Config does not match the type");
1310
+ }
1311
+ const configValue = scope.config[key.endsWith("?") ? key.slice(0, -1) : key];
1312
+ return configValue === void 0 ? defaultValue : configValue;
1313
+ }
1314
+ var EventBus = class extends EventTarget {
1315
+ trigger(name, payload) {
1316
+ this.dispatchEvent(new CustomEvent(name, { detail: payload }));
1317
+ }
1318
+ };
1319
+ var Markup = class extends String {
1320
+ };
1321
+ function htmlEscape(str) {
1322
+ if (str instanceof Markup) {
1323
+ return str;
1324
+ }
1325
+ if (str === void 0) {
1326
+ return markup("");
1327
+ }
1328
+ if (typeof str === "number") {
1329
+ return markup(String(str));
1330
+ }
1331
+ [
1332
+ ["&", "&amp;"],
1333
+ ["<", "&lt;"],
1334
+ [">", "&gt;"],
1335
+ ["'", "&#x27;"],
1336
+ ['"', "&quot;"],
1337
+ ["`", "&#x60;"]
1338
+ ].forEach((pairs) => {
1339
+ str = String(str).replace(new RegExp(pairs[0], "g"), pairs[1]);
1340
+ });
1341
+ return markup(str);
1342
+ }
1343
+ function markup(valueOrStrings, ...placeholders) {
1344
+ if (!Array.isArray(valueOrStrings)) {
1345
+ return new Markup(valueOrStrings);
1346
+ }
1347
+ const strings = valueOrStrings;
1348
+ let acc = "";
1349
+ let i = 0;
1350
+ for (; i < placeholders.length; ++i) {
1351
+ acc += strings[i] + htmlEscape(placeholders[i]);
1352
+ }
1353
+ acc += strings[i];
1354
+ return new Markup(acc);
1355
+ }
1192
1356
 
1193
1357
  // ../owl-runtime/dist/owl-runtime.es.js
1194
- var version = "3.0.0-alpha.32";
1358
+ var version = "3.0.0-alpha.34";
1195
1359
  var fibersInError = /* @__PURE__ */ new WeakMap();
1196
1360
  var nodeErrorHandlers = /* @__PURE__ */ new WeakMap();
1197
1361
  function invokeErrorHandlers(node, error, finalize, markFibers) {
@@ -1264,7 +1428,7 @@ function filterOutModifiersFromData(dataList) {
1264
1428
  }
1265
1429
  return { modifiers, data: dataList };
1266
1430
  }
1267
- var config = {
1431
+ var config2 = {
1268
1432
  // whether or not blockdom should normalize DOM whenever a block is created.
1269
1433
  // Normalizing dom mean removing empty text nodes (or containing only spaces)
1270
1434
  shouldNormalizeDom: true,
@@ -1471,20 +1635,20 @@ function toClassObj(expr) {
1471
1635
  }
1472
1636
  }
1473
1637
  var CSS_PROP_CACHE = {};
1474
- function toKebabCase(prop2) {
1475
- if (prop2 in CSS_PROP_CACHE) {
1476
- return CSS_PROP_CACHE[prop2];
1638
+ function toKebabCase(prop) {
1639
+ if (prop in CSS_PROP_CACHE) {
1640
+ return CSS_PROP_CACHE[prop];
1477
1641
  }
1478
- const result = prop2.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase());
1479
- CSS_PROP_CACHE[prop2] = result;
1642
+ const result = prop.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase());
1643
+ CSS_PROP_CACHE[prop] = result;
1480
1644
  return result;
1481
1645
  }
1482
1646
  var IMPORTANT_RE = /\s*!\s*important\s*$/i;
1483
- function setStyleProp(style, prop2, value) {
1647
+ function setStyleProp(style, prop, value) {
1484
1648
  if (IMPORTANT_RE.test(value)) {
1485
- style.setProperty(prop2, value.replace(IMPORTANT_RE, ""), "important");
1649
+ style.setProperty(prop, value.replace(IMPORTANT_RE, ""), "important");
1486
1650
  } else {
1487
- style.setProperty(prop2, value);
1651
+ style.setProperty(prop, value);
1488
1652
  }
1489
1653
  }
1490
1654
  function toStyleObj(expr) {
@@ -1528,19 +1692,19 @@ function toStyleObj(expr) {
1528
1692
  if (colonIdx === -1) {
1529
1693
  continue;
1530
1694
  }
1531
- const prop2 = trim.call(part.slice(0, colonIdx));
1695
+ const prop = trim.call(part.slice(0, colonIdx));
1532
1696
  const value = trim.call(part.slice(colonIdx + 1));
1533
- if (prop2 && value && value !== "undefined") {
1534
- result[prop2] = value;
1697
+ if (prop && value && value !== "undefined") {
1698
+ result[prop] = value;
1535
1699
  }
1536
1700
  }
1537
1701
  return result;
1538
1702
  }
1539
1703
  case "object":
1540
- for (let prop2 in expr) {
1541
- const value = expr[prop2];
1704
+ for (let prop in expr) {
1705
+ const value = expr[prop];
1542
1706
  if (value || value === 0) {
1543
- result[toKebabCase(prop2)] = String(value);
1707
+ result[toKebabCase(prop)] = String(value);
1544
1708
  }
1545
1709
  }
1546
1710
  return result;
@@ -1571,22 +1735,22 @@ function updateClass(val, oldVal) {
1571
1735
  function setStyle(val) {
1572
1736
  val = val === "" ? {} : toStyleObj(val);
1573
1737
  const style = this.style;
1574
- for (let prop2 in val) {
1575
- setStyleProp(style, prop2, val[prop2]);
1738
+ for (let prop in val) {
1739
+ setStyleProp(style, prop, val[prop]);
1576
1740
  }
1577
1741
  }
1578
1742
  function updateStyle(val, oldVal) {
1579
1743
  oldVal = oldVal === "" ? {} : toStyleObj(oldVal);
1580
1744
  val = val === "" ? {} : toStyleObj(val);
1581
1745
  const style = this.style;
1582
- for (let prop2 in oldVal) {
1583
- if (!(prop2 in val)) {
1584
- style.removeProperty(prop2);
1746
+ for (let prop in oldVal) {
1747
+ if (!(prop in val)) {
1748
+ style.removeProperty(prop);
1585
1749
  }
1586
1750
  }
1587
- for (let prop2 in val) {
1588
- if (val[prop2] !== oldVal[prop2]) {
1589
- setStyleProp(style, prop2, val[prop2]);
1751
+ for (let prop in val) {
1752
+ if (val[prop] !== oldVal[prop]) {
1753
+ setStyleProp(style, prop, val[prop]);
1590
1754
  }
1591
1755
  }
1592
1756
  if (!style.cssText) {
@@ -1638,11 +1802,6 @@ function validateTarget(target) {
1638
1802
  }
1639
1803
  throw new OwlError("Cannot mount component: the target is not a valid DOM element");
1640
1804
  }
1641
- var EventBus = class extends EventTarget {
1642
- trigger(name, payload) {
1643
- this.dispatchEvent(new CustomEvent(name, { detail: payload }));
1644
- }
1645
- };
1646
1805
  function whenReady(fn) {
1647
1806
  return new Promise(function(resolve) {
1648
1807
  if (document.readyState !== "loading") {
@@ -1653,43 +1812,6 @@ function whenReady(fn) {
1653
1812
  }).then(fn || function() {
1654
1813
  });
1655
1814
  }
1656
- var Markup = class extends String {
1657
- };
1658
- function htmlEscape(str) {
1659
- if (str instanceof Markup) {
1660
- return str;
1661
- }
1662
- if (str === void 0) {
1663
- return markup("");
1664
- }
1665
- if (typeof str === "number") {
1666
- return markup(String(str));
1667
- }
1668
- [
1669
- ["&", "&amp;"],
1670
- ["<", "&lt;"],
1671
- [">", "&gt;"],
1672
- ["'", "&#x27;"],
1673
- ['"', "&quot;"],
1674
- ["`", "&#x60;"]
1675
- ].forEach((pairs) => {
1676
- str = String(str).replace(new RegExp(pairs[0], "g"), pairs[1]);
1677
- });
1678
- return markup(str);
1679
- }
1680
- function markup(valueOrStrings, ...placeholders) {
1681
- if (!Array.isArray(valueOrStrings)) {
1682
- return new Markup(valueOrStrings);
1683
- }
1684
- const strings = valueOrStrings;
1685
- let acc = "";
1686
- let i = 0;
1687
- for (; i < placeholders.length; ++i) {
1688
- acc += strings[i] + htmlEscape(placeholders[i]);
1689
- }
1690
- acc += strings[i];
1691
- return new Markup(acc);
1692
- }
1693
1815
  function createEventHandler(rawEvent) {
1694
1816
  const eventName = rawEvent.split(".")[0];
1695
1817
  const capture = rawEvent.includes(".capture");
@@ -1711,7 +1833,7 @@ function createElementHandler(evName, capture = false, passive = false) {
1711
1833
  if (!currentTarget || !inOwnerDocument(currentTarget)) return;
1712
1834
  const data = currentTarget[eventKey];
1713
1835
  if (!data) return;
1714
- config.mainEventHandler(data, ev, currentTarget);
1836
+ config2.mainEventHandler(data, ev, currentTarget);
1715
1837
  }
1716
1838
  const options = { capture, passive };
1717
1839
  function setup(data) {
@@ -1751,7 +1873,7 @@ function nativeToSyntheticEvent(eventKey, event) {
1751
1873
  const _data = dom[eventKey];
1752
1874
  if (_data) {
1753
1875
  for (const data of Object.values(_data)) {
1754
- const stopped = config.mainEventHandler(data, event, dom);
1876
+ const stopped = config2.mainEventHandler(data, event, dom);
1755
1877
  if (stopped) return;
1756
1878
  }
1757
1879
  }
@@ -1997,7 +2119,7 @@ function createBlock(str) {
1997
2119
  }
1998
2120
  const doc = new DOMParser().parseFromString(`<t>${str}</t>`, "text/xml");
1999
2121
  const node = doc.firstChild.firstChild;
2000
- if (config.shouldNormalizeDom) {
2122
+ if (config2.shouldNormalizeDom) {
2001
2123
  normalizeNode(node);
2002
2124
  }
2003
2125
  const tree = buildTree(node);
@@ -3120,12 +3242,15 @@ var ComponentNode = class extends Scope {
3120
3242
  parent;
3121
3243
  children = /* @__PURE__ */ Object.create(null);
3122
3244
  willUpdateProps = [];
3245
+ // Fired right after `props` is applied to `node.props` on a parent re-render,
3246
+ // so reactive prop notifications happen once the new values are observable
3247
+ // (after user `onWillUpdateProps` hooks, including async ones, have run).
3248
+ propsUpdated = [];
3123
3249
  willUnmount = [];
3124
3250
  mounted = [];
3125
3251
  willPatch = [];
3126
3252
  patched = [];
3127
3253
  signalComputation;
3128
- pluginManager;
3129
3254
  constructor(C, props2, app, parent, parentKey) {
3130
3255
  super(app);
3131
3256
  this.parent = parent;
@@ -3655,6 +3780,7 @@ function createComponent(app, name, isStatic, hasSlotsProp, hasDynamicPropList,
3655
3780
  () => {
3656
3781
  if (fiber !== node.fiber) return;
3657
3782
  node.props = props2;
3783
+ for (const f of node.propsUpdated) f();
3658
3784
  fiber.render();
3659
3785
  },
3660
3786
  (error) => {
@@ -3663,6 +3789,7 @@ function createComponent(app, name, isStatic, hasSlotsProp, hasDynamicPropList,
3663
3789
  );
3664
3790
  } else {
3665
3791
  node.props = props2;
3792
+ for (const f of node.propsUpdated) f();
3666
3793
  fiber.render();
3667
3794
  }
3668
3795
  }
@@ -4003,10 +4130,6 @@ var mainEventHandler = (data, ev, currentTarget) => {
4003
4130
  }
4004
4131
  return stopped;
4005
4132
  };
4006
- function onWillStart(fn) {
4007
- const scope = useScope();
4008
- scope.willStart.push(scope.decorate(fn, "onWillStart"));
4009
- }
4010
4133
  function onWillUpdateProps(fn) {
4011
4134
  const scope = getComponentScope();
4012
4135
  function swapped(s, nextProps) {
@@ -4030,10 +4153,6 @@ function onWillUnmount(fn) {
4030
4153
  const scope = getComponentScope();
4031
4154
  scope.willUnmount.unshift(scope.decorate(fn, "onWillUnmount"));
4032
4155
  }
4033
- function onWillDestroy(fn) {
4034
- const scope = useScope();
4035
- scope.onDestroy(scope.decorate(fn, "onWillDestroy"));
4036
- }
4037
4156
  function onError(callback) {
4038
4157
  const scope = getComponentScope();
4039
4158
  let handlers = nodeErrorHandlers.get(scope);
@@ -4043,6 +4162,24 @@ function onError(callback) {
4043
4162
  }
4044
4163
  handlers.push(callback.bind(scope.component));
4045
4164
  }
4165
+ function staticProp(key, type, ...args) {
4166
+ const node = getComponentScope();
4167
+ const hasDefault = args.length > 0;
4168
+ const propValue = node.props[key];
4169
+ if (node.app.dev) {
4170
+ if (type !== void 0 && (!hasDefault || propValue !== void 0)) {
4171
+ assertType(propValue, type, `Invalid prop '${key}' in '${node.componentName}'`);
4172
+ }
4173
+ node.willUpdateProps.push((nextProps) => {
4174
+ if (nextProps[key] !== node.props[key]) {
4175
+ throw new OwlError(
4176
+ `Prop '${key}' changed in component '${node.componentName}'. Props declared with \`props.static()\` are static and should not change. If the prop is a signal, pass the same signal reference (its inner value may change).`
4177
+ );
4178
+ }
4179
+ });
4180
+ }
4181
+ return propValue === void 0 && hasDefault ? args[0] : propValue;
4182
+ }
4046
4183
  function componentType() {
4047
4184
  return constructorType(Component);
4048
4185
  }
@@ -4064,35 +4201,51 @@ function validateDefaults(schema) {
4064
4201
  }
4065
4202
  return types2.strictObject(validation);
4066
4203
  }
4067
- function props(type, defaults) {
4204
+ function makeProps(type, defaults) {
4068
4205
  const node = getComponentScope();
4069
4206
  const { app, componentName } = node;
4070
4207
  if (defaults) {
4071
4208
  node.defaultProps = Object.assign(node.defaultProps || {}, defaults);
4072
4209
  }
4073
- function getProp(key) {
4074
- if (node.props[key] === void 0 && defaults) {
4210
+ function resolveValue(props2, key) {
4211
+ if (props2[key] === void 0 && defaults) {
4075
4212
  return defaults[key];
4076
4213
  }
4077
- return node.props[key];
4214
+ return props2[key];
4078
4215
  }
4216
+ const signals = /* @__PURE__ */ Object.create(null);
4079
4217
  const result = /* @__PURE__ */ Object.create(null);
4080
- function applyPropGetters(keys) {
4218
+ function defineProp(key) {
4219
+ signals[key] = signal(resolveValue(node.props, key));
4220
+ Reflect.defineProperty(result, key, {
4221
+ enumerable: true,
4222
+ configurable: true,
4223
+ get: signals[key]
4224
+ });
4225
+ }
4226
+ function defineProps(keys) {
4081
4227
  for (const key of keys) {
4082
- Reflect.defineProperty(result, key, {
4083
- enumerable: true,
4084
- get: getProp.bind(null, key)
4085
- });
4228
+ defineProp(key);
4229
+ }
4230
+ }
4231
+ function updateSignals(keys) {
4232
+ for (const key of keys) {
4233
+ signals[key].set(resolveValue(node.props, key));
4086
4234
  }
4087
4235
  }
4088
4236
  if (type) {
4089
4237
  const keys = (Array.isArray(type) ? type : Object.keys(type)).map(
4090
4238
  (key) => key.endsWith("?") ? key.slice(0, -1) : key
4091
4239
  );
4092
- applyPropGetters(keys);
4240
+ defineProps(keys);
4241
+ node.propsUpdated.push(() => updateSignals(keys));
4093
4242
  if (app.dev) {
4094
4243
  if (defaults) {
4095
- assertType(defaults, validateDefaults(type), `Invalid component default props (${componentName})`);
4244
+ assertType(
4245
+ defaults,
4246
+ validateDefaults(type),
4247
+ `Invalid component default props (${componentName})`
4248
+ );
4096
4249
  }
4097
4250
  const validation = types2.object(type);
4098
4251
  assertType(node.props, validation, `Invalid component props (${componentName})`);
@@ -4118,17 +4271,28 @@ function props(type, defaults) {
4118
4271
  return keys2;
4119
4272
  };
4120
4273
  let keys = getKeys(node.props);
4121
- applyPropGetters(keys);
4122
- node.willUpdateProps.push((np) => {
4274
+ defineProps(keys);
4275
+ node.propsUpdated.push(() => {
4276
+ const nextKeys = getKeys(node.props);
4277
+ const nextKeySet = new Set(nextKeys);
4123
4278
  for (const key of keys) {
4124
- Reflect.deleteProperty(result, key);
4279
+ if (!nextKeySet.has(key)) {
4280
+ Reflect.deleteProperty(result, key);
4281
+ delete signals[key];
4282
+ }
4125
4283
  }
4126
- keys = getKeys(np);
4127
- applyPropGetters(keys);
4284
+ for (const key of nextKeys) {
4285
+ if (!(key in signals)) {
4286
+ defineProp(key);
4287
+ }
4288
+ }
4289
+ updateSignals(nextKeys);
4290
+ keys = nextKeys;
4128
4291
  });
4129
4292
  }
4130
4293
  return result;
4131
4294
  }
4295
+ var props = Object.assign(makeProps, { static: staticProp });
4132
4296
  var ErrorBoundary = class extends Component {
4133
4297
  static template = xml`
4134
4298
  <t t-if="this.props.error()">
@@ -4143,27 +4307,7 @@ var ErrorBoundary = class extends Component {
4143
4307
  onError((e) => this.props.error.set(e));
4144
4308
  }
4145
4309
  };
4146
- function useEffect(fn) {
4147
- onWillDestroy(effect(fn));
4148
- }
4149
- function useListener(target, eventName, handler, eventParams) {
4150
- if (typeof target === "function") {
4151
- useEffect(() => {
4152
- const el = target();
4153
- if (el) {
4154
- el.addEventListener(eventName, handler, eventParams);
4155
- return () => el.removeEventListener(eventName, handler, eventParams);
4156
- }
4157
- return;
4158
- });
4159
- } else {
4160
- target.addEventListener(eventName, handler, eventParams);
4161
- onWillDestroy(() => target.removeEventListener(eventName, handler, eventParams));
4162
- }
4163
- }
4164
- function useApp() {
4165
- return useScope().app;
4166
- }
4310
+ var useApp2 = useApp;
4167
4311
  var PortalContent = class extends Component {
4168
4312
  static template = xml`<t t-call-slot="default"/>`;
4169
4313
  };
@@ -4247,47 +4391,6 @@ var Suspense = class extends Component {
4247
4391
  onWillDestroy(() => root.destroy());
4248
4392
  }
4249
4393
  };
4250
- function prop(key, type, ...args) {
4251
- const node = getComponentScope();
4252
- const hasDefault = args.length > 0;
4253
- const propValue = node.props[key];
4254
- if (node.app.dev) {
4255
- if (type !== void 0 && (!hasDefault || propValue !== void 0)) {
4256
- assertType(propValue, type, `Invalid prop '${key}' in '${node.componentName}'`);
4257
- }
4258
- node.willUpdateProps.push((nextProps) => {
4259
- if (nextProps[key] !== node.props[key]) {
4260
- throw new OwlError(
4261
- `Prop '${key}' changed in component '${node.componentName}'. Props declared with \`prop()\` are static and should not change. If the prop is a signal, pass the same signal reference (its inner value may change).`
4262
- );
4263
- }
4264
- });
4265
- }
4266
- return propValue === void 0 && hasDefault ? args[0] : propValue;
4267
- }
4268
- function plugin(pluginType) {
4269
- const scope = useScope();
4270
- const manager = scope instanceof ComponentNode ? scope.pluginManager : scope;
4271
- let plugin2 = manager.getPluginById(pluginType.id);
4272
- if (!plugin2) {
4273
- if (scope instanceof PluginManager) {
4274
- plugin2 = manager.startPlugin(pluginType);
4275
- } else {
4276
- throw new OwlError(`Unknown plugin "${pluginType.id}"`);
4277
- }
4278
- }
4279
- return plugin2;
4280
- }
4281
- function config2(name, type) {
4282
- const scope = useScope();
4283
- if (!(scope instanceof PluginManager)) {
4284
- throw new OwlError("Expected to be in a plugin scope");
4285
- }
4286
- if (scope.app.dev && type) {
4287
- assertType(scope.config, types2.object({ [name]: type }), "Config does not match the type");
4288
- }
4289
- return scope.config[name.endsWith("?") ? name.slice(0, -1) : name];
4290
- }
4291
4394
  function providePlugins(pluginConstructors, config3) {
4292
4395
  const node = getComponentScope();
4293
4396
  const manager = new PluginManager(node.app, { parent: node.pluginManager, config: config3 });
@@ -4298,10 +4401,10 @@ function providePlugins(pluginConstructors, config3) {
4298
4401
  onWillStart(() => manager.ready);
4299
4402
  }
4300
4403
  }
4301
- config.shouldNormalizeDom = false;
4302
- config.mainEventHandler = mainEventHandler;
4404
+ config2.shouldNormalizeDom = false;
4405
+ config2.mainEventHandler = mainEventHandler;
4303
4406
  var blockDom = {
4304
- config,
4407
+ config: config2,
4305
4408
  // bdom entry points
4306
4409
  mount,
4307
4410
  patch,
@@ -4316,8 +4419,8 @@ var blockDom = {
4316
4419
  };
4317
4420
  var __info__ = {
4318
4421
  version: App.version,
4319
- date: "2026-05-28T07:14:22.717Z",
4320
- hash: "578ed435",
4422
+ date: "2026-06-05T08:44:38.319Z",
4423
+ hash: "3206cf27",
4321
4424
  url: "https://github.com/odoo/owl"
4322
4425
  };
4323
4426
 
@@ -6511,7 +6614,7 @@ export {
6511
6614
  batched,
6512
6615
  blockDom,
6513
6616
  computed,
6514
- config2 as config,
6617
+ config,
6515
6618
  effect,
6516
6619
  getScope,
6517
6620
  globalTemplates,
@@ -6528,7 +6631,6 @@ export {
6528
6631
  onWillUnmount,
6529
6632
  onWillUpdateProps,
6530
6633
  plugin,
6531
- prop,
6532
6634
  props,
6533
6635
  providePlugins,
6534
6636
  proxy,
@@ -6537,7 +6639,7 @@ export {
6537
6639
  toRaw,
6538
6640
  types2 as types,
6539
6641
  untrack,
6540
- useApp,
6642
+ useApp2 as useApp,
6541
6643
  useEffect,
6542
6644
  useListener,
6543
6645
  useScope,