@fluentui/web-components 3.0.0-beta.57 → 3.0.0-beta.59

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.
@@ -1174,6 +1174,109 @@ css.partial = (strings, ...values) => {
1174
1174
  return new CSSPartial(styles, behaviors);
1175
1175
  };
1176
1176
 
1177
+ const bindingStartMarker = /fe-b\$\$start\$\$(\d+)\$\$(.+)\$\$fe-b/;
1178
+ const bindingEndMarker = /fe-b\$\$end\$\$(\d+)\$\$(.+)\$\$fe-b/;
1179
+ const repeatViewStartMarker = /fe-repeat\$\$start\$\$(\d+)\$\$fe-repeat/;
1180
+ const repeatViewEndMarker = /fe-repeat\$\$end\$\$(\d+)\$\$fe-repeat/;
1181
+ const elementBoundaryStartMarker = /^(?:.{0,1000})fe-eb\$\$start\$\$(.+?)\$\$fe-eb/;
1182
+ const elementBoundaryEndMarker = /fe-eb\$\$end\$\$(.{0,1000})\$\$fe-eb(?:.{0,1000})$/;
1183
+ function isComment$1(node) {
1184
+ return node && node.nodeType === Node.COMMENT_NODE;
1185
+ }
1186
+ /**
1187
+ * Markup utilities to aid in template hydration.
1188
+ * @internal
1189
+ */
1190
+ const HydrationMarkup = Object.freeze({
1191
+ attributeMarkerName: "data-fe-b",
1192
+ attributeBindingSeparator: " ",
1193
+ contentBindingStartMarker(index, uniqueId) {
1194
+ return `fe-b$$start$$${index}$$${uniqueId}$$fe-b`;
1195
+ },
1196
+ contentBindingEndMarker(index, uniqueId) {
1197
+ return `fe-b$$end$$${index}$$${uniqueId}$$fe-b`;
1198
+ },
1199
+ repeatStartMarker(index) {
1200
+ return `fe-repeat$$start$$${index}$$fe-repeat`;
1201
+ },
1202
+ repeatEndMarker(index) {
1203
+ return `fe-repeat$$end$$${index}$$fe-repeat`;
1204
+ },
1205
+ isContentBindingStartMarker(content) {
1206
+ return bindingStartMarker.test(content);
1207
+ },
1208
+ isContentBindingEndMarker(content) {
1209
+ return bindingEndMarker.test(content);
1210
+ },
1211
+ isRepeatViewStartMarker(content) {
1212
+ return repeatViewStartMarker.test(content);
1213
+ },
1214
+ isRepeatViewEndMarker(content) {
1215
+ return repeatViewEndMarker.test(content);
1216
+ },
1217
+ isElementBoundaryStartMarker(node) {
1218
+ return isComment$1(node) && elementBoundaryStartMarker.test(node.data.trim());
1219
+ },
1220
+ isElementBoundaryEndMarker(node) {
1221
+ return isComment$1(node) && elementBoundaryEndMarker.test(node.data);
1222
+ },
1223
+ /**
1224
+ * Returns the indexes of the ViewBehaviorFactories affecting
1225
+ * attributes for the element, or null if no factories were found.
1226
+ */
1227
+ parseAttributeBinding(node) {
1228
+ const attr = node.getAttribute(this.attributeMarkerName);
1229
+ return attr === null ? attr : attr.split(this.attributeBindingSeparator).map(i => parseInt(i));
1230
+ },
1231
+ /**
1232
+ * Parses the ViewBehaviorFactory index from string data. Returns
1233
+ * the binding index or null if the index cannot be retrieved.
1234
+ */
1235
+ parseContentBindingStartMarker(content) {
1236
+ return parseIndexAndIdMarker(bindingStartMarker, content);
1237
+ },
1238
+ parseContentBindingEndMarker(content) {
1239
+ return parseIndexAndIdMarker(bindingEndMarker, content);
1240
+ },
1241
+ /**
1242
+ * Parses the index of a repeat directive from a content string.
1243
+ */
1244
+ parseRepeatStartMarker(content) {
1245
+ return parseIntMarker(repeatViewStartMarker, content);
1246
+ },
1247
+ parseRepeatEndMarker(content) {
1248
+ return parseIntMarker(repeatViewEndMarker, content);
1249
+ },
1250
+ /**
1251
+ * Parses element Id from element boundary markers
1252
+ */
1253
+ parseElementBoundaryStartMarker(content) {
1254
+ return parseStringMarker(elementBoundaryStartMarker, content.trim());
1255
+ },
1256
+ parseElementBoundaryEndMarker(content) {
1257
+ return parseStringMarker(elementBoundaryEndMarker, content);
1258
+ }
1259
+ });
1260
+ function parseIntMarker(regex, content) {
1261
+ const match = regex.exec(content);
1262
+ return match === null ? match : parseInt(match[1]);
1263
+ }
1264
+ function parseStringMarker(regex, content) {
1265
+ const match = regex.exec(content);
1266
+ return match === null ? match : match[1];
1267
+ }
1268
+ function parseIndexAndIdMarker(regex, content) {
1269
+ const match = regex.exec(content);
1270
+ return match === null ? match : [parseInt(match[1]), match[2]];
1271
+ }
1272
+ /**
1273
+ * @internal
1274
+ */
1275
+ const Hydratable = Symbol.for("fe-hydration");
1276
+ function isHydratable(value) {
1277
+ return value[Hydratable] === Hydratable;
1278
+ }
1279
+
1177
1280
  const marker = `fast-${Math.random().toString(36).substring(2, 8)}`;
1178
1281
  const interpolationStart = `${marker}{`;
1179
1282
  const interpolationEnd = `}${marker}`;
@@ -1340,250 +1443,209 @@ class StatelessAttachedAttributeDirective {
1340
1443
  }
1341
1444
  makeSerializationNoop(StatelessAttachedAttributeDirective);
1342
1445
 
1343
- function updateContent(target, aspect, value, controller) {
1344
- // If there's no actual value, then this equates to the
1345
- // empty string for the purposes of content bindings.
1346
- if (value === null || value === undefined) {
1347
- value = "";
1446
+ class HydrationTargetElementError extends Error {
1447
+ constructor(
1448
+ /**
1449
+ * The error message
1450
+ */
1451
+ message,
1452
+ /**
1453
+ * The Compiled View Behavior Factories that belong to the view.
1454
+ */
1455
+ factories,
1456
+ /**
1457
+ * The node to target factory.
1458
+ */
1459
+ node) {
1460
+ super(message);
1461
+ this.factories = factories;
1462
+ this.node = node;
1348
1463
  }
1349
- // If the value has a "create" method, then it's a ContentTemplate.
1350
- if (value.create) {
1351
- target.textContent = "";
1352
- let view = target.$fastView;
1353
- // If there's no previous view that we might be able to
1354
- // reuse then create a new view from the template.
1355
- if (view === void 0) {
1356
- view = value.create();
1357
- } else {
1358
- // If there is a previous view, but it wasn't created
1359
- // from the same template as the new value, then we
1360
- // need to remove the old view if it's still in the DOM
1361
- // and create a new view from the template.
1362
- if (target.$fastTemplate !== value) {
1363
- if (view.isComposed) {
1364
- view.remove();
1365
- view.unbind();
1366
- }
1367
- view = value.create();
1368
- }
1369
- }
1370
- // It's possible that the value is the same as the previous template
1371
- // and that there's actually no need to compose it.
1372
- if (!view.isComposed) {
1373
- view.isComposed = true;
1374
- view.bind(controller.source, controller.context);
1375
- view.insertBefore(target);
1376
- target.$fastView = view;
1377
- target.$fastTemplate = value;
1378
- } else if (view.needsBindOnly) {
1379
- view.needsBindOnly = false;
1380
- view.bind(controller.source, controller.context);
1464
+ }
1465
+ function isComment(node) {
1466
+ return node.nodeType === Node.COMMENT_NODE;
1467
+ }
1468
+ function isText(node) {
1469
+ return node.nodeType === Node.TEXT_NODE;
1470
+ }
1471
+ /**
1472
+ * Returns a range object inclusive of all nodes including and between the
1473
+ * provided first and last node.
1474
+ * @param first - The first node
1475
+ * @param last - This last node
1476
+ * @returns
1477
+ */
1478
+ function createRangeForNodes(first, last) {
1479
+ const range = document.createRange();
1480
+ range.setStart(first, 0);
1481
+ // The lastIndex should be inclusive of the end of the lastChild. Obtain offset based
1482
+ // on usageNotes: https://developer.mozilla.org/en-US/docs/Web/API/Range/setEnd#usage_notes
1483
+ range.setEnd(last, isComment(last) || isText(last) ? last.data.length : last.childNodes.length);
1484
+ return range;
1485
+ }
1486
+ function isShadowRoot(node) {
1487
+ return node instanceof DocumentFragment && "mode" in node;
1488
+ }
1489
+ /**
1490
+ * Maps {@link CompiledViewBehaviorFactory} ids to the corresponding node targets for the view.
1491
+ * @param firstNode - The first node of the view.
1492
+ * @param lastNode - The last node of the view.
1493
+ * @param factories - The Compiled View Behavior Factories that belong to the view.
1494
+ * @returns - A {@link ViewBehaviorTargets } object for the factories in the view.
1495
+ */
1496
+ function buildViewBindingTargets(firstNode, lastNode, factories) {
1497
+ const range = createRangeForNodes(firstNode, lastNode);
1498
+ const treeRoot = range.commonAncestorContainer;
1499
+ const walker = document.createTreeWalker(treeRoot, NodeFilter.SHOW_ELEMENT + NodeFilter.SHOW_COMMENT + NodeFilter.SHOW_TEXT, {
1500
+ acceptNode(node) {
1501
+ return range.comparePoint(node, 0) === 0 ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
1381
1502
  }
1382
- } else {
1383
- const view = target.$fastView;
1384
- // If there is a view and it's currently composed into
1385
- // the DOM, then we need to remove it.
1386
- if (view !== void 0 && view.isComposed) {
1387
- view.isComposed = false;
1388
- view.remove();
1389
- if (view.needsBindOnly) {
1390
- view.needsBindOnly = false;
1391
- } else {
1392
- view.unbind();
1393
- }
1503
+ });
1504
+ const targets = {};
1505
+ const boundaries = {};
1506
+ let node = walker.currentNode = firstNode;
1507
+ while (node !== null) {
1508
+ switch (node.nodeType) {
1509
+ case Node.ELEMENT_NODE:
1510
+ {
1511
+ targetElement(node, factories, targets);
1512
+ break;
1513
+ }
1514
+ case Node.COMMENT_NODE:
1515
+ {
1516
+ targetComment(node, walker, factories, targets, boundaries);
1517
+ break;
1518
+ }
1394
1519
  }
1395
- target.textContent = value;
1520
+ node = walker.nextNode();
1396
1521
  }
1397
- }
1398
- function updateTokenList(target, aspect, value) {
1399
- var _a;
1400
- const lookup = `${this.id}-t`;
1401
- const state = (_a = target[lookup]) !== null && _a !== void 0 ? _a : target[lookup] = {
1402
- v: 0,
1403
- cv: Object.create(null)
1522
+ range.detach();
1523
+ return {
1524
+ targets,
1525
+ boundaries
1404
1526
  };
1405
- const classVersions = state.cv;
1406
- let version = state.v;
1407
- const tokenList = target[aspect];
1408
- // Add the classes, tracking the version at which they were added.
1409
- if (value !== null && value !== undefined && value.length) {
1410
- const names = value.split(/\s+/);
1411
- for (let i = 0, ii = names.length; i < ii; ++i) {
1412
- const currentName = names[i];
1413
- if (currentName === "") {
1414
- continue;
1527
+ }
1528
+ function targetElement(node, factories, targets) {
1529
+ // Check for attributes and map any factories.
1530
+ const attrFactoryIds = HydrationMarkup.parseAttributeBinding(node);
1531
+ if (attrFactoryIds !== null) {
1532
+ for (const id of attrFactoryIds) {
1533
+ if (!factories[id]) {
1534
+ throw new HydrationTargetElementError(`HydrationView was unable to successfully target factory on ${node.nodeName} inside ${node.getRootNode().host.nodeName}. This likely indicates a template mismatch between SSR rendering and hydration.`, factories, node);
1415
1535
  }
1416
- classVersions[currentName] = version;
1417
- tokenList.add(currentName);
1536
+ targetFactory(factories[id], node, targets);
1418
1537
  }
1538
+ node.removeAttribute(HydrationMarkup.attributeMarkerName);
1419
1539
  }
1420
- state.v = version + 1;
1421
- // If this is the first call to add classes, there's no need to remove old ones.
1422
- if (version === 0) {
1540
+ }
1541
+ function targetComment(node, walker, factories, targets, boundaries) {
1542
+ if (HydrationMarkup.isElementBoundaryStartMarker(node)) {
1543
+ skipToElementBoundaryEndMarker(node, walker);
1423
1544
  return;
1424
1545
  }
1425
- // Remove classes from the previous version.
1426
- version -= 1;
1427
- for (const name in classVersions) {
1428
- if (classVersions[name] === version) {
1429
- tokenList.remove(name);
1546
+ if (HydrationMarkup.isContentBindingStartMarker(node.data)) {
1547
+ const parsed = HydrationMarkup.parseContentBindingStartMarker(node.data);
1548
+ if (parsed === null) {
1549
+ return;
1430
1550
  }
1431
- }
1432
- }
1433
- const sinkLookup = {
1434
- [DOMAspect.attribute]: DOM.setAttribute,
1435
- [DOMAspect.booleanAttribute]: DOM.setBooleanAttribute,
1436
- [DOMAspect.property]: (t, a, v) => t[a] = v,
1437
- [DOMAspect.content]: updateContent,
1438
- [DOMAspect.tokenList]: updateTokenList,
1439
- [DOMAspect.event]: () => void 0
1440
- };
1441
- /**
1442
- * A directive that applies bindings.
1443
- * @public
1444
- */
1445
- class HTMLBindingDirective {
1446
- /**
1447
- * Creates an instance of HTMLBindingDirective.
1448
- * @param dataBinding - The binding configuration to apply.
1449
- */
1450
- constructor(dataBinding) {
1451
- this.dataBinding = dataBinding;
1452
- this.updateTarget = null;
1453
- /**
1454
- * The type of aspect to target.
1455
- */
1456
- this.aspectType = DOMAspect.content;
1457
- }
1458
- /**
1459
- * Creates HTML to be used within a template.
1460
- * @param add - Can be used to add behavior factories to a template.
1461
- */
1462
- createHTML(add) {
1463
- return Markup.interpolation(add(this));
1464
- }
1465
- /**
1466
- * Creates a behavior.
1467
- */
1468
- createBehavior() {
1469
- var _a;
1470
- if (this.updateTarget === null) {
1471
- const sink = sinkLookup[this.aspectType];
1472
- const policy = (_a = this.dataBinding.policy) !== null && _a !== void 0 ? _a : this.policy;
1473
- if (!sink) {
1474
- throw FAST.error(1205 /* Message.unsupportedBindingBehavior */);
1551
+ const [index, id] = parsed;
1552
+ const factory = factories[index];
1553
+ const nodes = [];
1554
+ let current = walker.nextSibling();
1555
+ node.data = "";
1556
+ const first = current;
1557
+ // Search for the binding end marker that closes the binding.
1558
+ while (current !== null) {
1559
+ if (isComment(current)) {
1560
+ const parsed = HydrationMarkup.parseContentBindingEndMarker(current.data);
1561
+ if (parsed && parsed[1] === id) {
1562
+ break;
1563
+ }
1475
1564
  }
1476
- this.data = `${this.id}-d`;
1477
- this.updateTarget = policy.protect(this.targetTagName, this.aspectType, this.targetAspect, sink);
1565
+ nodes.push(current);
1566
+ current = walker.nextSibling();
1478
1567
  }
1479
- return this;
1480
- }
1481
- /** @internal */
1482
- bind(controller) {
1483
- var _a;
1484
- const target = controller.targets[this.targetNodeId];
1485
- switch (this.aspectType) {
1486
- case DOMAspect.event:
1487
- target[this.data] = controller;
1488
- target.addEventListener(this.targetAspect, this, this.dataBinding.options);
1489
- break;
1490
- case DOMAspect.content:
1491
- controller.onUnbind(this);
1492
- // intentional fall through
1493
- default:
1494
- const observer = (_a = target[this.data]) !== null && _a !== void 0 ? _a : target[this.data] = this.dataBinding.createObserver(this, this);
1495
- observer.target = target;
1496
- observer.controller = controller;
1497
- this.updateTarget(target, this.targetAspect, observer.bind(controller), controller);
1498
- break;
1568
+ if (current === null) {
1569
+ const root = node.getRootNode();
1570
+ throw new Error(`Error hydrating Comment node inside "${isShadowRoot(root) ? root.host.nodeName : root.nodeName}".`);
1499
1571
  }
1500
- }
1501
- /** @internal */
1502
- unbind(controller) {
1503
- const target = controller.targets[this.targetNodeId];
1504
- const view = target.$fastView;
1505
- if (view !== void 0 && view.isComposed) {
1506
- view.unbind();
1507
- view.needsBindOnly = true;
1572
+ current.data = "";
1573
+ if (nodes.length === 1 && isText(nodes[0])) {
1574
+ targetFactory(factory, nodes[0], targets);
1575
+ } else {
1576
+ // If current === first, it means there is no content in
1577
+ // the view. This happens when a `when` directive evaluates false,
1578
+ // or whenever a content binding returns null or undefined.
1579
+ // In that case, there will never be any content
1580
+ // to hydrate and Binding can simply create a HTMLView
1581
+ // whenever it needs to.
1582
+ if (current !== first && current.previousSibling !== null) {
1583
+ boundaries[factory.targetNodeId] = {
1584
+ first,
1585
+ last: current.previousSibling
1586
+ };
1587
+ }
1588
+ // Binding evaluates to null / undefined or a template.
1589
+ // If binding revaluates to string, it will replace content in target
1590
+ // So we always insert a text node to ensure that
1591
+ // text content binding will be written to this text node instead of comment
1592
+ const dummyTextNode = current.parentNode.insertBefore(document.createTextNode(""), current);
1593
+ targetFactory(factory, dummyTextNode, targets);
1508
1594
  }
1509
1595
  }
1510
- /** @internal */
1511
- handleEvent(event) {
1512
- const controller = event.currentTarget[this.data];
1513
- if (controller.isBound) {
1514
- ExecutionContext.setEvent(event);
1515
- const result = this.dataBinding.evaluate(controller.source, controller.context);
1516
- ExecutionContext.setEvent(null);
1517
- if (result !== true) {
1518
- event.preventDefault();
1596
+ }
1597
+ /**
1598
+ * Moves TreeWalker to element boundary end marker
1599
+ * @param node - element boundary start marker node
1600
+ * @param walker - tree walker
1601
+ */
1602
+ function skipToElementBoundaryEndMarker(node, walker) {
1603
+ const id = HydrationMarkup.parseElementBoundaryStartMarker(node.data);
1604
+ let current = walker.nextSibling();
1605
+ while (current !== null) {
1606
+ if (isComment(current)) {
1607
+ const parsed = HydrationMarkup.parseElementBoundaryEndMarker(current.data);
1608
+ if (parsed && parsed === id) {
1609
+ break;
1519
1610
  }
1520
1611
  }
1612
+ current = walker.nextSibling();
1521
1613
  }
1522
- /** @internal */
1523
- handleChange(binding, observer) {
1524
- const target = observer.target;
1525
- const controller = observer.controller;
1526
- this.updateTarget(target, this.targetAspect, observer.bind(controller), controller);
1614
+ }
1615
+ function targetFactory(factory, node, targets) {
1616
+ if (factory.targetNodeId === undefined) {
1617
+ // Dev error, this shouldn't ever be thrown
1618
+ throw new Error("Factory could not be target to the node");
1527
1619
  }
1620
+ targets[factory.targetNodeId] = node;
1528
1621
  }
1529
- HTMLDirective.define(HTMLBindingDirective, {
1530
- aspected: true
1531
- });
1532
1622
 
1623
+ var _a;
1533
1624
  function removeNodeSequence(firstNode, lastNode) {
1534
1625
  const parent = firstNode.parentNode;
1535
1626
  let current = firstNode;
1536
1627
  let next;
1537
1628
  while (current !== lastNode) {
1538
1629
  next = current.nextSibling;
1630
+ if (!next) {
1631
+ throw new Error(`Unmatched first/last child inside "${lastNode.getRootNode().host.nodeName}".`);
1632
+ }
1539
1633
  parent.removeChild(current);
1540
1634
  current = next;
1541
1635
  }
1542
1636
  parent.removeChild(lastNode);
1543
1637
  }
1544
- /**
1545
- * The standard View implementation, which also implements ElementView and SyntheticView.
1546
- * @public
1547
- */
1548
- class HTMLView {
1549
- /**
1550
- * Constructs an instance of HTMLView.
1551
- * @param fragment - The html fragment that contains the nodes for this view.
1552
- * @param behaviors - The behaviors to be applied to this view.
1553
- */
1554
- constructor(fragment, factories, targets) {
1555
- this.fragment = fragment;
1556
- this.factories = factories;
1557
- this.targets = targets;
1558
- this.behaviors = null;
1559
- this.unbindables = [];
1560
- /**
1561
- * The data that the view is bound to.
1562
- */
1563
- this.source = null;
1564
- /**
1565
- * Indicates whether the controller is bound.
1566
- */
1567
- this.isBound = false;
1568
- /**
1569
- * Indicates how the source's lifetime relates to the controller's lifetime.
1570
- */
1571
- this.sourceLifetime = SourceLifetime.unknown;
1572
- /**
1573
- * The execution context the view is running within.
1574
- */
1575
- this.context = this;
1576
- /**
1577
- * The index of the current item within a repeat context.
1578
- */
1579
- this.index = 0;
1580
- /**
1581
- * The length of the current collection within a repeat context.
1582
- */
1583
- this.length = 0;
1584
- this.firstChild = fragment.firstChild;
1585
- this.lastChild = fragment.lastChild;
1586
- }
1638
+ class DefaultExecutionContext {
1639
+ constructor() {
1640
+ /**
1641
+ * The index of the current item within a repeat context.
1642
+ */
1643
+ this.index = 0;
1644
+ /**
1645
+ * The length of the current collection within a repeat context.
1646
+ */
1647
+ this.length = 0;
1648
+ }
1587
1649
  /**
1588
1650
  * The current event within an event handler.
1589
1651
  */
@@ -1637,6 +1699,43 @@ class HTMLView {
1637
1699
  eventTarget() {
1638
1700
  return this.event.target;
1639
1701
  }
1702
+ }
1703
+ /**
1704
+ * The standard View implementation, which also implements ElementView and SyntheticView.
1705
+ * @public
1706
+ */
1707
+ class HTMLView extends DefaultExecutionContext {
1708
+ /**
1709
+ * Constructs an instance of HTMLView.
1710
+ * @param fragment - The html fragment that contains the nodes for this view.
1711
+ * @param behaviors - The behaviors to be applied to this view.
1712
+ */
1713
+ constructor(fragment, factories, targets) {
1714
+ super();
1715
+ this.fragment = fragment;
1716
+ this.factories = factories;
1717
+ this.targets = targets;
1718
+ this.behaviors = null;
1719
+ this.unbindables = [];
1720
+ /**
1721
+ * The data that the view is bound to.
1722
+ */
1723
+ this.source = null;
1724
+ /**
1725
+ * Indicates whether the controller is bound.
1726
+ */
1727
+ this.isBound = false;
1728
+ /**
1729
+ * Indicates how the source's lifetime relates to the controller's lifetime.
1730
+ */
1731
+ this.sourceLifetime = SourceLifetime.unknown;
1732
+ /**
1733
+ * The execution context the view is running within.
1734
+ */
1735
+ this.context = this;
1736
+ this.firstChild = fragment.firstChild;
1737
+ this.lastChild = fragment.lastChild;
1738
+ }
1640
1739
  /**
1641
1740
  * Appends the view's DOM nodes to the referenced node.
1642
1741
  * @param node - The parent node to append the view's DOM nodes to.
@@ -1666,101 +1765,510 @@ class HTMLView {
1666
1765
  }
1667
1766
  }
1668
1767
  /**
1669
- * Removes the view's DOM nodes.
1670
- * The nodes are not disposed and the view can later be re-inserted.
1768
+ * Removes the view's DOM nodes.
1769
+ * The nodes are not disposed and the view can later be re-inserted.
1770
+ */
1771
+ remove() {
1772
+ const fragment = this.fragment;
1773
+ const end = this.lastChild;
1774
+ let current = this.firstChild;
1775
+ let next;
1776
+ while (current !== end) {
1777
+ next = current.nextSibling;
1778
+ fragment.appendChild(current);
1779
+ current = next;
1780
+ }
1781
+ fragment.appendChild(end);
1782
+ }
1783
+ /**
1784
+ * Removes the view and unbinds its behaviors, disposing of DOM nodes afterward.
1785
+ * Once a view has been disposed, it cannot be inserted or bound again.
1786
+ */
1787
+ dispose() {
1788
+ removeNodeSequence(this.firstChild, this.lastChild);
1789
+ this.unbind();
1790
+ }
1791
+ onUnbind(behavior) {
1792
+ this.unbindables.push(behavior);
1793
+ }
1794
+ /**
1795
+ * Binds a view's behaviors to its binding source.
1796
+ * @param source - The binding source for the view's binding behaviors.
1797
+ * @param context - The execution context to run the behaviors within.
1798
+ */
1799
+ bind(source, context = this) {
1800
+ if (this.source === source) {
1801
+ return;
1802
+ }
1803
+ let behaviors = this.behaviors;
1804
+ if (behaviors === null) {
1805
+ this.source = source;
1806
+ this.context = context;
1807
+ this.behaviors = behaviors = new Array(this.factories.length);
1808
+ const factories = this.factories;
1809
+ for (let i = 0, ii = factories.length; i < ii; ++i) {
1810
+ const behavior = factories[i].createBehavior();
1811
+ behavior.bind(this);
1812
+ behaviors[i] = behavior;
1813
+ }
1814
+ } else {
1815
+ if (this.source !== null) {
1816
+ this.evaluateUnbindables();
1817
+ }
1818
+ this.isBound = false;
1819
+ this.source = source;
1820
+ this.context = context;
1821
+ for (let i = 0, ii = behaviors.length; i < ii; ++i) {
1822
+ behaviors[i].bind(this);
1823
+ }
1824
+ }
1825
+ this.isBound = true;
1826
+ }
1827
+ /**
1828
+ * Unbinds a view's behaviors from its binding source.
1829
+ */
1830
+ unbind() {
1831
+ if (!this.isBound || this.source === null) {
1832
+ return;
1833
+ }
1834
+ this.evaluateUnbindables();
1835
+ this.source = null;
1836
+ this.context = this;
1837
+ this.isBound = false;
1838
+ }
1839
+ evaluateUnbindables() {
1840
+ const unbindables = this.unbindables;
1841
+ for (let i = 0, ii = unbindables.length; i < ii; ++i) {
1842
+ unbindables[i].unbind(this);
1843
+ }
1844
+ unbindables.length = 0;
1845
+ }
1846
+ /**
1847
+ * Efficiently disposes of a contiguous range of synthetic view instances.
1848
+ * @param views - A contiguous range of views to be disposed.
1849
+ */
1850
+ static disposeContiguousBatch(views) {
1851
+ if (views.length === 0) {
1852
+ return;
1853
+ }
1854
+ removeNodeSequence(views[0].firstChild, views[views.length - 1].lastChild);
1855
+ for (let i = 0, ii = views.length; i < ii; ++i) {
1856
+ views[i].unbind();
1857
+ }
1858
+ }
1859
+ }
1860
+ makeSerializationNoop(HTMLView);
1861
+ Observable.defineProperty(HTMLView.prototype, "index");
1862
+ Observable.defineProperty(HTMLView.prototype, "length");
1863
+ const HydrationStage = {
1864
+ unhydrated: "unhydrated",
1865
+ hydrating: "hydrating",
1866
+ hydrated: "hydrated"
1867
+ };
1868
+ /** @public */
1869
+ class HydrationBindingError extends Error {
1870
+ constructor(
1871
+ /**
1872
+ * The error message
1873
+ */
1874
+ message,
1875
+ /**
1876
+ * The factory that was unable to be bound
1877
+ */
1878
+ factory,
1879
+ /**
1880
+ * A DocumentFragment containing a clone of the
1881
+ * view's Nodes.
1882
+ */
1883
+ fragment,
1884
+ /**
1885
+ * String representation of the HTML in the template that
1886
+ * threw the binding error.
1887
+ */
1888
+ templateString) {
1889
+ super(message);
1890
+ this.factory = factory;
1891
+ this.fragment = fragment;
1892
+ this.templateString = templateString;
1893
+ }
1894
+ }
1895
+ class HydrationView extends DefaultExecutionContext {
1896
+ constructor(firstChild, lastChild, sourceTemplate, hostBindingTarget) {
1897
+ super();
1898
+ this.firstChild = firstChild;
1899
+ this.lastChild = lastChild;
1900
+ this.sourceTemplate = sourceTemplate;
1901
+ this.hostBindingTarget = hostBindingTarget;
1902
+ this[_a] = Hydratable;
1903
+ this.context = this;
1904
+ this.source = null;
1905
+ this.isBound = false;
1906
+ this.sourceLifetime = SourceLifetime.unknown;
1907
+ this.unbindables = [];
1908
+ this.fragment = null;
1909
+ this.behaviors = null;
1910
+ this._hydrationStage = HydrationStage.unhydrated;
1911
+ this._bindingViewBoundaries = {};
1912
+ this._targets = {};
1913
+ this.factories = sourceTemplate.compile().factories;
1914
+ }
1915
+ get hydrationStage() {
1916
+ return this._hydrationStage;
1917
+ }
1918
+ get targets() {
1919
+ return this._targets;
1920
+ }
1921
+ get bindingViewBoundaries() {
1922
+ return this._bindingViewBoundaries;
1923
+ }
1924
+ /**
1925
+ * no-op. Hydrated views are don't need to be moved from a documentFragment
1926
+ * to the target node.
1927
+ */
1928
+ insertBefore(node) {
1929
+ // No-op in cases where this is called before the view is removed,
1930
+ // because the nodes will already be in the document and just need hydrating.
1931
+ if (this.fragment === null) {
1932
+ return;
1933
+ }
1934
+ if (this.fragment.hasChildNodes()) {
1935
+ node.parentNode.insertBefore(this.fragment, node);
1936
+ } else {
1937
+ const end = this.lastChild;
1938
+ if (node.previousSibling === end) return;
1939
+ const parentNode = node.parentNode;
1940
+ let current = this.firstChild;
1941
+ let next;
1942
+ while (current !== end) {
1943
+ next = current.nextSibling;
1944
+ parentNode.insertBefore(current, node);
1945
+ current = next;
1946
+ }
1947
+ parentNode.insertBefore(end, node);
1948
+ }
1949
+ }
1950
+ /**
1951
+ * Appends the view to a node. In cases where this is called before the
1952
+ * view has been removed, the method will no-op.
1953
+ * @param node - the node to append the view to.
1954
+ */
1955
+ appendTo(node) {
1956
+ if (this.fragment !== null) {
1957
+ node.appendChild(this.fragment);
1958
+ }
1959
+ }
1960
+ remove() {
1961
+ const fragment = this.fragment || (this.fragment = document.createDocumentFragment());
1962
+ const end = this.lastChild;
1963
+ let current = this.firstChild;
1964
+ let next;
1965
+ while (current !== end) {
1966
+ next = current.nextSibling;
1967
+ if (!next) {
1968
+ throw new Error(`Unmatched first/last child inside "${end.getRootNode().host.nodeName}".`);
1969
+ }
1970
+ fragment.appendChild(current);
1971
+ current = next;
1972
+ }
1973
+ fragment.appendChild(end);
1974
+ }
1975
+ bind(source, context = this) {
1976
+ var _b, _c;
1977
+ if (this.hydrationStage !== HydrationStage.hydrated) {
1978
+ this._hydrationStage = HydrationStage.hydrating;
1979
+ }
1980
+ if (this.source === source) {
1981
+ return;
1982
+ }
1983
+ let behaviors = this.behaviors;
1984
+ if (behaviors === null) {
1985
+ this.source = source;
1986
+ this.context = context;
1987
+ try {
1988
+ const {
1989
+ targets,
1990
+ boundaries
1991
+ } = buildViewBindingTargets(this.firstChild, this.lastChild, this.factories);
1992
+ this._targets = targets;
1993
+ this._bindingViewBoundaries = boundaries;
1994
+ } catch (error) {
1995
+ if (error instanceof HydrationTargetElementError) {
1996
+ let templateString = this.sourceTemplate.html;
1997
+ if (typeof templateString !== "string") {
1998
+ templateString = templateString.innerHTML;
1999
+ }
2000
+ error.templateString = templateString;
2001
+ }
2002
+ throw error;
2003
+ }
2004
+ this.behaviors = behaviors = new Array(this.factories.length);
2005
+ const factories = this.factories;
2006
+ for (let i = 0, ii = factories.length; i < ii; ++i) {
2007
+ const factory = factories[i];
2008
+ if (factory.targetNodeId === "h" && this.hostBindingTarget) {
2009
+ targetFactory(factory, this.hostBindingTarget, this._targets);
2010
+ }
2011
+ // If the binding has been targeted or it is a host binding and the view has a hostBindingTarget
2012
+ if (factory.targetNodeId in this.targets) {
2013
+ const behavior = factory.createBehavior();
2014
+ behavior.bind(this);
2015
+ behaviors[i] = behavior;
2016
+ } else {
2017
+ let templateString = this.sourceTemplate.html;
2018
+ if (typeof templateString !== "string") {
2019
+ templateString = templateString.innerHTML;
2020
+ }
2021
+ throw new HydrationBindingError(`HydrationView was unable to successfully target bindings inside "${(_c = ((_b = this.firstChild) === null || _b === void 0 ? void 0 : _b.getRootNode()).host) === null || _c === void 0 ? void 0 : _c.nodeName}".`, factory, createRangeForNodes(this.firstChild, this.lastChild).cloneContents(), templateString);
2022
+ }
2023
+ }
2024
+ } else {
2025
+ if (this.source !== null) {
2026
+ this.evaluateUnbindables();
2027
+ }
2028
+ this.isBound = false;
2029
+ this.source = source;
2030
+ this.context = context;
2031
+ for (let i = 0, ii = behaviors.length; i < ii; ++i) {
2032
+ behaviors[i].bind(this);
2033
+ }
2034
+ }
2035
+ this.isBound = true;
2036
+ this._hydrationStage = HydrationStage.hydrated;
2037
+ }
2038
+ unbind() {
2039
+ if (!this.isBound || this.source === null) {
2040
+ return;
2041
+ }
2042
+ this.evaluateUnbindables();
2043
+ this.source = null;
2044
+ this.context = this;
2045
+ this.isBound = false;
2046
+ }
2047
+ /**
2048
+ * Removes the view and unbinds its behaviors, disposing of DOM nodes afterward.
2049
+ * Once a view has been disposed, it cannot be inserted or bound again.
2050
+ */
2051
+ dispose() {
2052
+ removeNodeSequence(this.firstChild, this.lastChild);
2053
+ this.unbind();
2054
+ }
2055
+ onUnbind(behavior) {
2056
+ this.unbindables.push(behavior);
2057
+ }
2058
+ evaluateUnbindables() {
2059
+ const unbindables = this.unbindables;
2060
+ for (let i = 0, ii = unbindables.length; i < ii; ++i) {
2061
+ unbindables[i].unbind(this);
2062
+ }
2063
+ unbindables.length = 0;
2064
+ }
2065
+ }
2066
+ _a = Hydratable;
2067
+ makeSerializationNoop(HydrationView);
2068
+
2069
+ function isContentTemplate(value) {
2070
+ return value.create !== undefined;
2071
+ }
2072
+ function updateContent(target, aspect, value, controller) {
2073
+ // If there's no actual value, then this equates to the
2074
+ // empty string for the purposes of content bindings.
2075
+ if (value === null || value === undefined) {
2076
+ value = "";
2077
+ }
2078
+ // If the value has a "create" method, then it's a ContentTemplate.
2079
+ if (isContentTemplate(value)) {
2080
+ target.textContent = "";
2081
+ let view = target.$fastView;
2082
+ // If there's no previous view that we might be able to
2083
+ // reuse then create a new view from the template.
2084
+ if (view === void 0) {
2085
+ if (isHydratable(controller) && isHydratable(value) && controller.bindingViewBoundaries[this.targetNodeId] !== undefined && controller.hydrationStage !== HydrationStage.hydrated) {
2086
+ const viewNodes = controller.bindingViewBoundaries[this.targetNodeId];
2087
+ view = value.hydrate(viewNodes.first, viewNodes.last);
2088
+ } else {
2089
+ view = value.create();
2090
+ }
2091
+ } else {
2092
+ // If there is a previous view, but it wasn't created
2093
+ // from the same template as the new value, then we
2094
+ // need to remove the old view if it's still in the DOM
2095
+ // and create a new view from the template.
2096
+ if (target.$fastTemplate !== value) {
2097
+ if (view.isComposed) {
2098
+ view.remove();
2099
+ view.unbind();
2100
+ }
2101
+ view = value.create();
2102
+ }
2103
+ }
2104
+ // It's possible that the value is the same as the previous template
2105
+ // and that there's actually no need to compose it.
2106
+ if (!view.isComposed) {
2107
+ view.isComposed = true;
2108
+ view.bind(controller.source, controller.context);
2109
+ view.insertBefore(target);
2110
+ target.$fastView = view;
2111
+ target.$fastTemplate = value;
2112
+ } else if (view.needsBindOnly) {
2113
+ view.needsBindOnly = false;
2114
+ view.bind(controller.source, controller.context);
2115
+ }
2116
+ } else {
2117
+ const view = target.$fastView;
2118
+ // If there is a view and it's currently composed into
2119
+ // the DOM, then we need to remove it.
2120
+ if (view !== void 0 && view.isComposed) {
2121
+ view.isComposed = false;
2122
+ view.remove();
2123
+ if (view.needsBindOnly) {
2124
+ view.needsBindOnly = false;
2125
+ } else {
2126
+ view.unbind();
2127
+ }
2128
+ }
2129
+ target.textContent = value;
2130
+ }
2131
+ }
2132
+ function updateTokenList(target, aspect, value) {
2133
+ var _a;
2134
+ const lookup = `${this.id}-t`;
2135
+ const state = (_a = target[lookup]) !== null && _a !== void 0 ? _a : target[lookup] = {
2136
+ v: 0,
2137
+ cv: Object.create(null)
2138
+ };
2139
+ const classVersions = state.cv;
2140
+ let version = state.v;
2141
+ const tokenList = target[aspect];
2142
+ // Add the classes, tracking the version at which they were added.
2143
+ if (value !== null && value !== undefined && value.length) {
2144
+ const names = value.split(/\s+/);
2145
+ for (let i = 0, ii = names.length; i < ii; ++i) {
2146
+ const currentName = names[i];
2147
+ if (currentName === "") {
2148
+ continue;
2149
+ }
2150
+ classVersions[currentName] = version;
2151
+ tokenList.add(currentName);
2152
+ }
2153
+ }
2154
+ state.v = version + 1;
2155
+ // If this is the first call to add classes, there's no need to remove old ones.
2156
+ if (version === 0) {
2157
+ return;
2158
+ }
2159
+ // Remove classes from the previous version.
2160
+ version -= 1;
2161
+ for (const name in classVersions) {
2162
+ if (classVersions[name] === version) {
2163
+ tokenList.remove(name);
2164
+ }
2165
+ }
2166
+ }
2167
+ const sinkLookup = {
2168
+ [DOMAspect.attribute]: DOM.setAttribute,
2169
+ [DOMAspect.booleanAttribute]: DOM.setBooleanAttribute,
2170
+ [DOMAspect.property]: (t, a, v) => t[a] = v,
2171
+ [DOMAspect.content]: updateContent,
2172
+ [DOMAspect.tokenList]: updateTokenList,
2173
+ [DOMAspect.event]: () => void 0
2174
+ };
2175
+ /**
2176
+ * A directive that applies bindings.
2177
+ * @public
2178
+ */
2179
+ class HTMLBindingDirective {
2180
+ /**
2181
+ * Creates an instance of HTMLBindingDirective.
2182
+ * @param dataBinding - The binding configuration to apply.
1671
2183
  */
1672
- remove() {
1673
- const fragment = this.fragment;
1674
- const end = this.lastChild;
1675
- let current = this.firstChild;
1676
- let next;
1677
- while (current !== end) {
1678
- next = current.nextSibling;
1679
- fragment.appendChild(current);
1680
- current = next;
1681
- }
1682
- fragment.appendChild(end);
2184
+ constructor(dataBinding) {
2185
+ this.dataBinding = dataBinding;
2186
+ this.updateTarget = null;
2187
+ /**
2188
+ * The type of aspect to target.
2189
+ */
2190
+ this.aspectType = DOMAspect.content;
1683
2191
  }
1684
2192
  /**
1685
- * Removes the view and unbinds its behaviors, disposing of DOM nodes afterward.
1686
- * Once a view has been disposed, it cannot be inserted or bound again.
2193
+ * Creates HTML to be used within a template.
2194
+ * @param add - Can be used to add behavior factories to a template.
1687
2195
  */
1688
- dispose() {
1689
- removeNodeSequence(this.firstChild, this.lastChild);
1690
- this.unbind();
1691
- }
1692
- onUnbind(behavior) {
1693
- this.unbindables.push(behavior);
2196
+ createHTML(add) {
2197
+ return Markup.interpolation(add(this));
1694
2198
  }
1695
2199
  /**
1696
- * Binds a view's behaviors to its binding source.
1697
- * @param source - The binding source for the view's binding behaviors.
1698
- * @param context - The execution context to run the behaviors within.
2200
+ * Creates a behavior.
1699
2201
  */
1700
- bind(source, context = this) {
1701
- if (this.source === source) {
1702
- return;
1703
- }
1704
- let behaviors = this.behaviors;
1705
- if (behaviors === null) {
1706
- this.source = source;
1707
- this.context = context;
1708
- this.behaviors = behaviors = new Array(this.factories.length);
1709
- const factories = this.factories;
1710
- for (let i = 0, ii = factories.length; i < ii; ++i) {
1711
- const behavior = factories[i].createBehavior();
1712
- behavior.bind(this);
1713
- behaviors[i] = behavior;
1714
- }
1715
- } else {
1716
- if (this.source !== null) {
1717
- this.evaluateUnbindables();
1718
- }
1719
- this.isBound = false;
1720
- this.source = source;
1721
- this.context = context;
1722
- for (let i = 0, ii = behaviors.length; i < ii; ++i) {
1723
- behaviors[i].bind(this);
2202
+ createBehavior() {
2203
+ var _a;
2204
+ if (this.updateTarget === null) {
2205
+ const sink = sinkLookup[this.aspectType];
2206
+ const policy = (_a = this.dataBinding.policy) !== null && _a !== void 0 ? _a : this.policy;
2207
+ if (!sink) {
2208
+ throw FAST.error(1205 /* Message.unsupportedBindingBehavior */);
1724
2209
  }
2210
+ this.data = `${this.id}-d`;
2211
+ this.updateTarget = policy.protect(this.targetTagName, this.aspectType, this.targetAspect, sink);
1725
2212
  }
1726
- this.isBound = true;
2213
+ return this;
1727
2214
  }
1728
- /**
1729
- * Unbinds a view's behaviors from its binding source.
1730
- */
1731
- unbind() {
1732
- if (!this.isBound || this.source === null) {
1733
- return;
2215
+ /** @internal */
2216
+ bind(controller) {
2217
+ var _a;
2218
+ const target = controller.targets[this.targetNodeId];
2219
+ const isHydrating = isHydratable(controller) && controller.hydrationStage && controller.hydrationStage !== HydrationStage.hydrated;
2220
+ switch (this.aspectType) {
2221
+ case DOMAspect.event:
2222
+ target[this.data] = controller;
2223
+ target.addEventListener(this.targetAspect, this, this.dataBinding.options);
2224
+ break;
2225
+ case DOMAspect.content:
2226
+ controller.onUnbind(this);
2227
+ // intentional fall through
2228
+ default:
2229
+ const observer = (_a = target[this.data]) !== null && _a !== void 0 ? _a : target[this.data] = this.dataBinding.createObserver(this, this);
2230
+ observer.target = target;
2231
+ observer.controller = controller;
2232
+ if (isHydrating && (this.aspectType === DOMAspect.attribute || this.aspectType === DOMAspect.booleanAttribute)) {
2233
+ observer.bind(controller);
2234
+ // Skip updating target during bind for attributes
2235
+ break;
2236
+ }
2237
+ this.updateTarget(target, this.targetAspect, observer.bind(controller), controller);
2238
+ break;
1734
2239
  }
1735
- this.evaluateUnbindables();
1736
- this.source = null;
1737
- this.context = this;
1738
- this.isBound = false;
1739
2240
  }
1740
- evaluateUnbindables() {
1741
- const unbindables = this.unbindables;
1742
- for (let i = 0, ii = unbindables.length; i < ii; ++i) {
1743
- unbindables[i].unbind(this);
2241
+ /** @internal */
2242
+ unbind(controller) {
2243
+ const target = controller.targets[this.targetNodeId];
2244
+ const view = target.$fastView;
2245
+ if (view !== void 0 && view.isComposed) {
2246
+ view.unbind();
2247
+ view.needsBindOnly = true;
1744
2248
  }
1745
- unbindables.length = 0;
1746
2249
  }
1747
- /**
1748
- * Efficiently disposes of a contiguous range of synthetic view instances.
1749
- * @param views - A contiguous range of views to be disposed.
1750
- */
1751
- static disposeContiguousBatch(views) {
1752
- if (views.length === 0) {
1753
- return;
1754
- }
1755
- removeNodeSequence(views[0].firstChild, views[views.length - 1].lastChild);
1756
- for (let i = 0, ii = views.length; i < ii; ++i) {
1757
- views[i].unbind();
2250
+ /** @internal */
2251
+ handleEvent(event) {
2252
+ const controller = event.currentTarget[this.data];
2253
+ if (controller.isBound) {
2254
+ ExecutionContext.setEvent(event);
2255
+ const result = this.dataBinding.evaluate(controller.source, controller.context);
2256
+ ExecutionContext.setEvent(null);
2257
+ if (result !== true) {
2258
+ event.preventDefault();
2259
+ }
1758
2260
  }
1759
2261
  }
2262
+ /** @internal */
2263
+ handleChange(binding, observer) {
2264
+ const target = observer.target;
2265
+ const controller = observer.controller;
2266
+ this.updateTarget(target, this.targetAspect, observer.bind(controller), controller);
2267
+ }
1760
2268
  }
1761
- makeSerializationNoop(HTMLView);
1762
- Observable.defineProperty(HTMLView.prototype, "index");
1763
- Observable.defineProperty(HTMLView.prototype, "length");
2269
+ HTMLDirective.define(HTMLBindingDirective, {
2270
+ aspected: true
2271
+ });
1764
2272
 
1765
2273
  const targetIdFrom = (parentId, nodeIndex) => `${parentId}.${nodeIndex}`;
1766
2274
  const descriptorCache = {};
@@ -2015,7 +2523,6 @@ const Compiler = {
2015
2523
  return parts[0];
2016
2524
  }
2017
2525
  let sourceAspect;
2018
- let binding;
2019
2526
  let isVolatile = false;
2020
2527
  let bindingPolicy = void 0;
2021
2528
  const partCount = parts.length;
@@ -2024,7 +2531,6 @@ const Compiler = {
2024
2531
  return () => x;
2025
2532
  }
2026
2533
  sourceAspect = x.sourceAspect || sourceAspect;
2027
- binding = x.dataBinding || binding;
2028
2534
  isVolatile = isVolatile || x.dataBinding.isVolatile;
2029
2535
  bindingPolicy = bindingPolicy || x.dataBinding.policy;
2030
2536
  return x.dataBinding.evaluate;
@@ -2036,17 +2542,14 @@ const Compiler = {
2036
2542
  }
2037
2543
  return output;
2038
2544
  };
2039
- binding.evaluate = expression;
2040
- binding.isVolatile = isVolatile;
2041
- binding.policy = bindingPolicy !== null && bindingPolicy !== void 0 ? bindingPolicy : policy;
2042
- const directive = new HTMLBindingDirective(binding);
2545
+ const directive = new HTMLBindingDirective(oneWay(expression, bindingPolicy !== null && bindingPolicy !== void 0 ? bindingPolicy : policy, isVolatile));
2043
2546
  HTMLDirective.assignAspect(directive, sourceAspect);
2044
2547
  return directive;
2045
2548
  }
2046
2549
  };
2047
2550
 
2048
2551
  // Much thanks to LitHTML for working this out!
2049
- const lastAttributeNameRegex = /* eslint-disable-next-line no-control-regex */
2552
+ const lastAttributeNameRegex = /* eslint-disable-next-line no-control-regex, max-len */
2050
2553
  /([ \x09\x0a\x0c\x0d])([^\0-\x1F\x7F-\x9F "'>=/]+)([ \x09\x0a\x0c\x0d]*=[ \x09\x0a\x0c\x0d]*(?:[^ \x09\x0a\x0c\x0d"'`<>=]*|"[^"]*|'[^']*))$/;
2051
2554
  const noFactories = Object.create(null);
2052
2555
  /**
@@ -2106,14 +2609,20 @@ class ViewTemplate {
2106
2609
  this.factories = factories;
2107
2610
  }
2108
2611
  /**
2109
- * Creates an HTMLView instance based on this template definition.
2110
- * @param hostBindingTarget - The element that host behaviors will be bound to.
2612
+ * @internal
2111
2613
  */
2112
- create(hostBindingTarget) {
2614
+ compile() {
2113
2615
  if (this.result === null) {
2114
2616
  this.result = Compiler.compile(this.html, this.factories, this.policy);
2115
2617
  }
2116
- return this.result.createView(hostBindingTarget);
2618
+ return this.result;
2619
+ }
2620
+ /**
2621
+ * Creates an HTMLView instance based on this template definition.
2622
+ * @param hostBindingTarget - The element that host behaviors will be bound to.
2623
+ */
2624
+ create(hostBindingTarget) {
2625
+ return this.compile().createView(hostBindingTarget);
2117
2626
  }
2118
2627
  /**
2119
2628
  * Returns a directive that can inline the template.
@@ -2718,6 +3227,100 @@ FASTElementDefinition.getByType = fastElementRegistry.getByType;
2718
3227
  */
2719
3228
  FASTElementDefinition.getForInstance = fastElementRegistry.getForInstance;
2720
3229
 
3230
+ /**
3231
+ * An extension of MutationObserver that supports unobserving nodes.
3232
+ * @internal
3233
+ */
3234
+ class UnobservableMutationObserver extends MutationObserver {
3235
+ /**
3236
+ * Creates an instance of UnobservableMutationObserver.
3237
+ * @param callback - The callback to invoke when observed nodes are changed.
3238
+ */
3239
+ constructor(callback) {
3240
+ function handler(mutations) {
3241
+ this.callback.call(null, mutations.filter(record => this.observedNodes.has(record.target)));
3242
+ }
3243
+ super(handler);
3244
+ this.callback = callback;
3245
+ this.observedNodes = new Set();
3246
+ }
3247
+ observe(target, options) {
3248
+ this.observedNodes.add(target);
3249
+ super.observe(target, options);
3250
+ }
3251
+ unobserve(target) {
3252
+ this.observedNodes.delete(target);
3253
+ if (this.observedNodes.size < 1) {
3254
+ this.disconnect();
3255
+ }
3256
+ }
3257
+ }
3258
+ /**
3259
+ * Bridges between ViewBehaviors and HostBehaviors, enabling a host to
3260
+ * control ViewBehaviors.
3261
+ * @public
3262
+ */
3263
+ Object.freeze({
3264
+ /**
3265
+ * Creates a ViewBehaviorOrchestrator.
3266
+ * @param source - The source to to associate behaviors with.
3267
+ * @returns A ViewBehaviorOrchestrator.
3268
+ */
3269
+ create(source) {
3270
+ const behaviors = [];
3271
+ const targets = {};
3272
+ let unbindables = null;
3273
+ let isConnected = false;
3274
+ return {
3275
+ source,
3276
+ context: ExecutionContext.default,
3277
+ targets,
3278
+ get isBound() {
3279
+ return isConnected;
3280
+ },
3281
+ addBehaviorFactory(factory, target) {
3282
+ var _a, _b, _c, _d;
3283
+ const compiled = factory;
3284
+ compiled.id = (_a = compiled.id) !== null && _a !== void 0 ? _a : nextId();
3285
+ compiled.targetNodeId = (_b = compiled.targetNodeId) !== null && _b !== void 0 ? _b : nextId();
3286
+ compiled.targetTagName = (_c = target.tagName) !== null && _c !== void 0 ? _c : null;
3287
+ compiled.policy = (_d = compiled.policy) !== null && _d !== void 0 ? _d : DOM.policy;
3288
+ this.addTarget(compiled.targetNodeId, target);
3289
+ this.addBehavior(compiled.createBehavior());
3290
+ },
3291
+ addTarget(nodeId, target) {
3292
+ targets[nodeId] = target;
3293
+ },
3294
+ addBehavior(behavior) {
3295
+ behaviors.push(behavior);
3296
+ if (isConnected) {
3297
+ behavior.bind(this);
3298
+ }
3299
+ },
3300
+ onUnbind(unbindable) {
3301
+ if (unbindables === null) {
3302
+ unbindables = [];
3303
+ }
3304
+ unbindables.push(unbindable);
3305
+ },
3306
+ connectedCallback(controller) {
3307
+ if (!isConnected) {
3308
+ isConnected = true;
3309
+ behaviors.forEach(x => x.bind(this));
3310
+ }
3311
+ },
3312
+ disconnectedCallback(controller) {
3313
+ if (isConnected) {
3314
+ isConnected = false;
3315
+ if (unbindables !== null) {
3316
+ unbindables.forEach(x => x.unbind(this));
3317
+ }
3318
+ }
3319
+ }
3320
+ };
3321
+ }
3322
+ });
3323
+
2721
3324
  const defaultEventOptions = {
2722
3325
  bubbles: true,
2723
3326
  composed: true,
@@ -2756,6 +3359,11 @@ class ElementController extends PropertyChangeNotifier {
2756
3359
  */
2757
3360
  this.guardBehaviorConnection = false;
2758
3361
  this.behaviors = null;
3362
+ /**
3363
+ * Tracks whether behaviors are connected so that
3364
+ * behaviors cant be connected multiple times
3365
+ */
3366
+ this.behaviorsConnected = false;
2759
3367
  this._mainStyles = null;
2760
3368
  /**
2761
3369
  * This allows Observable.getNotifier(...) to return the Controller
@@ -2996,7 +3604,19 @@ class ElementController extends PropertyChangeNotifier {
2996
3604
  return;
2997
3605
  }
2998
3606
  this.stage = 0 /* Stages.connecting */;
2999
- // If we have any observables that were bound, re-apply their values.
3607
+ this.bindObservables();
3608
+ this.connectBehaviors();
3609
+ if (this.needsInitialization) {
3610
+ this.renderTemplate(this.template);
3611
+ this.addStyles(this.mainStyles);
3612
+ this.needsInitialization = false;
3613
+ } else if (this.view !== null) {
3614
+ this.view.bind(this.source);
3615
+ }
3616
+ this.stage = 1 /* Stages.connected */;
3617
+ Observable.notify(this, isConnectedPropertyName);
3618
+ }
3619
+ bindObservables() {
3000
3620
  if (this.boundObservables !== null) {
3001
3621
  const element = this.source;
3002
3622
  const boundObservables = this.boundObservables;
@@ -3007,23 +3627,30 @@ class ElementController extends PropertyChangeNotifier {
3007
3627
  }
3008
3628
  this.boundObservables = null;
3009
3629
  }
3010
- const behaviors = this.behaviors;
3011
- if (behaviors !== null) {
3012
- this.guardBehaviorConnection = true;
3013
- for (const key of behaviors.keys()) {
3014
- key.connectedCallback && key.connectedCallback(this);
3630
+ }
3631
+ connectBehaviors() {
3632
+ if (this.behaviorsConnected === false) {
3633
+ const behaviors = this.behaviors;
3634
+ if (behaviors !== null) {
3635
+ this.guardBehaviorConnection = true;
3636
+ for (const key of behaviors.keys()) {
3637
+ key.connectedCallback && key.connectedCallback(this);
3638
+ }
3639
+ this.guardBehaviorConnection = false;
3015
3640
  }
3016
- this.guardBehaviorConnection = false;
3641
+ this.behaviorsConnected = true;
3017
3642
  }
3018
- if (this.needsInitialization) {
3019
- this.renderTemplate(this.template);
3020
- this.addStyles(this.mainStyles);
3021
- this.needsInitialization = false;
3022
- } else if (this.view !== null) {
3023
- this.view.bind(this.source);
3643
+ }
3644
+ disconnectBehaviors() {
3645
+ if (this.behaviorsConnected === true) {
3646
+ const behaviors = this.behaviors;
3647
+ if (behaviors !== null) {
3648
+ for (const key of behaviors.keys()) {
3649
+ key.disconnectedCallback && key.disconnectedCallback(this);
3650
+ }
3651
+ }
3652
+ this.behaviorsConnected = false;
3024
3653
  }
3025
- this.stage = 1 /* Stages.connected */;
3026
- Observable.notify(this, isConnectedPropertyName);
3027
3654
  }
3028
3655
  /**
3029
3656
  * Runs disconnected lifecycle behavior on the associated element.
@@ -3037,12 +3664,7 @@ class ElementController extends PropertyChangeNotifier {
3037
3664
  if (this.view !== null) {
3038
3665
  this.view.unbind();
3039
3666
  }
3040
- const behaviors = this.behaviors;
3041
- if (behaviors !== null) {
3042
- for (const key of behaviors.keys()) {
3043
- key.disconnectedCallback && key.disconnectedCallback(this);
3044
- }
3045
- }
3667
+ this.disconnectBehaviors();
3046
3668
  this.stage = 3 /* Stages.disconnected */;
3047
3669
  }
3048
3670
  /**
@@ -3242,6 +3864,87 @@ if (ElementStyles.supportsAdoptedStyleSheets) {
3242
3864
  } else {
3243
3865
  ElementStyles.setDefaultStrategy(StyleElementStrategy);
3244
3866
  }
3867
+ const deferHydrationAttribute = "defer-hydration";
3868
+ const needsHydrationAttribute = "needs-hydration";
3869
+ /**
3870
+ * An ElementController capable of hydrating FAST elements from
3871
+ * Declarative Shadow DOM.
3872
+ *
3873
+ * @beta
3874
+ */
3875
+ class HydratableElementController extends ElementController {
3876
+ static hydrationObserverHandler(records) {
3877
+ for (const record of records) {
3878
+ HydratableElementController.hydrationObserver.unobserve(record.target);
3879
+ record.target.$fastController.connect();
3880
+ }
3881
+ }
3882
+ connect() {
3883
+ var _a, _b;
3884
+ // Initialize needsHydration on first connect
3885
+ if (this.needsHydration === undefined) {
3886
+ this.needsHydration = this.source.getAttribute(needsHydrationAttribute) !== null;
3887
+ }
3888
+ // If the `defer-hydration` attribute exists on the source,
3889
+ // wait for it to be removed before continuing connection behavior.
3890
+ if (this.source.hasAttribute(deferHydrationAttribute)) {
3891
+ HydratableElementController.hydrationObserver.observe(this.source, {
3892
+ attributeFilter: [deferHydrationAttribute]
3893
+ });
3894
+ return;
3895
+ }
3896
+ // If the controller does not need to be hydrated, defer connection behavior
3897
+ // to the base-class. This case handles element re-connection and initial connection
3898
+ // of elements that did not get declarative shadow-dom emitted, as well as if an extending
3899
+ // class
3900
+ if (!this.needsHydration) {
3901
+ super.connect();
3902
+ return;
3903
+ }
3904
+ if (this.stage !== 3 /* Stages.disconnected */) {
3905
+ return;
3906
+ }
3907
+ this.stage = 0 /* Stages.connecting */;
3908
+ this.bindObservables();
3909
+ this.connectBehaviors();
3910
+ const element = this.source;
3911
+ const host = (_a = getShadowRoot(element)) !== null && _a !== void 0 ? _a : element;
3912
+ if (this.template) {
3913
+ if (isHydratable(this.template)) {
3914
+ let firstChild = host.firstChild;
3915
+ let lastChild = host.lastChild;
3916
+ if (element.shadowRoot === null) {
3917
+ // handle element boundary markers when shadowRoot is not present
3918
+ if (HydrationMarkup.isElementBoundaryStartMarker(firstChild)) {
3919
+ firstChild.data = "";
3920
+ firstChild = firstChild.nextSibling;
3921
+ }
3922
+ if (HydrationMarkup.isElementBoundaryEndMarker(lastChild)) {
3923
+ lastChild.data = "";
3924
+ lastChild = lastChild.previousSibling;
3925
+ }
3926
+ }
3927
+ this.view = this.template.hydrate(firstChild, lastChild, element);
3928
+ (_b = this.view) === null || _b === void 0 ? void 0 : _b.bind(this.source);
3929
+ } else {
3930
+ this.renderTemplate(this.template);
3931
+ }
3932
+ }
3933
+ this.addStyles(this.mainStyles);
3934
+ this.stage = 1 /* Stages.connected */;
3935
+ this.source.removeAttribute(needsHydrationAttribute);
3936
+ this.needsInitialization = this.needsHydration = false;
3937
+ Observable.notify(this, isConnectedPropertyName);
3938
+ }
3939
+ disconnect() {
3940
+ super.disconnect();
3941
+ HydratableElementController.hydrationObserver.unobserve(this.source);
3942
+ }
3943
+ static install() {
3944
+ ElementController.setStrategy(HydratableElementController);
3945
+ }
3946
+ }
3947
+ HydratableElementController.hydrationObserver = new UnobservableMutationObserver(HydratableElementController.hydrationObserverHandler);
3245
3948
 
3246
3949
  /* eslint-disable-next-line @typescript-eslint/explicit-function-return-type */
3247
3950
  function createFASTElement(BaseType) {