Licitator 1.0
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

8719 lines
382 KiB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
  1. (function (global, factory) {
  2. typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  3. typeof define === 'function' && define.amd ? define(factory) :
  4. (global = global || self, global.GSTC = factory());
  5. }(this, (function () { 'use strict';
  6. /**
  7. * @license
  8. * Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
  9. * This code may only be used under the BSD style license found at
  10. * http://polymer.github.io/LICENSE.txt
  11. * The complete set of authors may be found at
  12. * http://polymer.github.io/AUTHORS.txt
  13. * The complete set of contributors may be found at
  14. * http://polymer.github.io/CONTRIBUTORS.txt
  15. * Code distributed by Google as part of the polymer project is also
  16. * subject to an additional IP rights grant found at
  17. * http://polymer.github.io/PATENTS.txt
  18. */
  19. /**
  20. * Brands a function as a directive factory function so that lit-html will call
  21. * the function during template rendering, rather than passing as a value.
  22. *
  23. * A _directive_ is a function that takes a Part as an argument. It has the
  24. * signature: `(part: Part) => void`.
  25. *
  26. * A directive _factory_ is a function that takes arguments for data and
  27. * configuration and returns a directive. Users of directive usually refer to
  28. * the directive factory as the directive. For example, "The repeat directive".
  29. *
  30. * Usually a template author will invoke a directive factory in their template
  31. * with relevant arguments, which will then return a directive function.
  32. *
  33. * Here's an example of using the `repeat()` directive factory that takes an
  34. * array and a function to render an item:
  35. *
  36. * ```js
  37. * html`<ul><${repeat(items, (item) => html`<li>${item}</li>`)}</ul>`
  38. * ```
  39. *
  40. * When `repeat` is invoked, it returns a directive function that closes over
  41. * `items` and the template function. When the outer template is rendered, the
  42. * return directive function is called with the Part for the expression.
  43. * `repeat` then performs it's custom logic to render multiple items.
  44. *
  45. * @param f The directive factory function. Must be a function that returns a
  46. * function of the signature `(part: Part) => void`. The returned function will
  47. * be called with the part object.
  48. *
  49. * @example
  50. *
  51. * import {directive, html} from 'lit-html';
  52. *
  53. * const immutable = directive((v) => (part) => {
  54. * if (part.value !== v) {
  55. * part.setValue(v)
  56. * }
  57. * });
  58. */
  59. const directive = (f) => ((...args) => {
  60. const d = f(...args);
  61. // tslint:disable-next-line:no-any
  62. d.isDirective = true;
  63. return d;
  64. });
  65. class Directive {
  66. constructor() {
  67. this.isDirective = true;
  68. this.isClass = true;
  69. }
  70. body(_part) {
  71. // body of the directive
  72. }
  73. }
  74. const isDirective = (o) => {
  75. return o !== undefined && o !== null &&
  76. // tslint:disable-next-line:no-any
  77. typeof o.isDirective === 'boolean';
  78. };
  79. /**
  80. * @license
  81. * Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
  82. * This code may only be used under the BSD style license found at
  83. * http://polymer.github.io/LICENSE.txt
  84. * The complete set of authors may be found at
  85. * http://polymer.github.io/AUTHORS.txt
  86. * The complete set of contributors may be found at
  87. * http://polymer.github.io/CONTRIBUTORS.txt
  88. * Code distributed by Google as part of the polymer project is also
  89. * subject to an additional IP rights grant found at
  90. * http://polymer.github.io/PATENTS.txt
  91. */
  92. /**
  93. * True if the custom elements polyfill is in use.
  94. */
  95. const isCEPolyfill = typeof window !== 'undefined' ?
  96. window.customElements != null &&
  97. window.customElements
  98. .polyfillWrapFlushCallback !== undefined :
  99. false;
  100. /**
  101. * Reparents nodes, starting from `start` (inclusive) to `end` (exclusive),
  102. * into another container (could be the same container), before `before`. If
  103. * `before` is null, it appends the nodes to the container.
  104. */
  105. const reparentNodes = (container, start, end = null, before = null) => {
  106. while (start !== end) {
  107. const n = start.nextSibling;
  108. container.insertBefore(start, before);
  109. start = n;
  110. }
  111. };
  112. /**
  113. * Removes nodes, starting from `start` (inclusive) to `end` (exclusive), from
  114. * `container`.
  115. */
  116. const removeNodes = (container, start, end = null) => {
  117. while (start !== end) {
  118. const n = start.nextSibling;
  119. container.removeChild(start);
  120. start = n;
  121. }
  122. };
  123. /**
  124. * @license
  125. * Copyright (c) 2018 The Polymer Project Authors. All rights reserved.
  126. * This code may only be used under the BSD style license found at
  127. * http://polymer.github.io/LICENSE.txt
  128. * The complete set of authors may be found at
  129. * http://polymer.github.io/AUTHORS.txt
  130. * The complete set of contributors may be found at
  131. * http://polymer.github.io/CONTRIBUTORS.txt
  132. * Code distributed by Google as part of the polymer project is also
  133. * subject to an additional IP rights grant found at
  134. * http://polymer.github.io/PATENTS.txt
  135. */
  136. /**
  137. * A sentinel value that signals that a value was handled by a directive and
  138. * should not be written to the DOM.
  139. */
  140. const noChange = {};
  141. /**
  142. * A sentinel value that signals a NodePart to fully clear its content.
  143. */
  144. const nothing = {};
  145. /**
  146. * @license
  147. * Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
  148. * This code may only be used under the BSD style license found at
  149. * http://polymer.github.io/LICENSE.txt
  150. * The complete set of authors may be found at
  151. * http://polymer.github.io/AUTHORS.txt
  152. * The complete set of contributors may be found at
  153. * http://polymer.github.io/CONTRIBUTORS.txt
  154. * Code distributed by Google as part of the polymer project is also
  155. * subject to an additional IP rights grant found at
  156. * http://polymer.github.io/PATENTS.txt
  157. */
  158. /**
  159. * An expression marker with embedded unique key to avoid collision with
  160. * possible text in templates.
  161. */
  162. const marker = `{{lit-${String(Math.random()).slice(2)}}}`;
  163. /**
  164. * An expression marker used text-positions, multi-binding attributes, and
  165. * attributes with markup-like text values.
  166. */
  167. const nodeMarker = `<!--${marker}-->`;
  168. const markerRegex = new RegExp(`${marker}|${nodeMarker}`);
  169. /**
  170. * Suffix appended to all bound attribute names.
  171. */
  172. const boundAttributeSuffix = '$lit$';
  173. /**
  174. * An updatable Template that tracks the location of dynamic parts.
  175. */
  176. class Template {
  177. constructor(result, element) {
  178. this.parts = [];
  179. this.element = element;
  180. const nodesToRemove = [];
  181. const stack = [];
  182. // Edge needs all 4 parameters present; IE11 needs 3rd parameter to be null
  183. const walker = document.createTreeWalker(element.content, 133 /* NodeFilter.SHOW_{ELEMENT|COMMENT|TEXT} */, null, false);
  184. // Keeps track of the last index associated with a part. We try to delete
  185. // unnecessary nodes, but we never want to associate two different parts
  186. // to the same index. They must have a constant node between.
  187. let lastPartIndex = 0;
  188. let index = -1;
  189. let partIndex = 0;
  190. const { strings, values: { length } } = result;
  191. while (partIndex < length) {
  192. const node = walker.nextNode();
  193. if (node === null) {
  194. // We've exhausted the content inside a nested template element.
  195. // Because we still have parts (the outer for-loop), we know:
  196. // - There is a template in the stack
  197. // - The walker will find a nextNode outside the template
  198. walker.currentNode = stack.pop();
  199. continue;
  200. }
  201. index++;
  202. if (node.nodeType === 1 /* Node.ELEMENT_NODE */) {
  203. if (node.hasAttributes()) {
  204. const attributes = node.attributes;
  205. const { length } = attributes;
  206. // Per
  207. // https://developer.mozilla.org/en-US/docs/Web/API/NamedNodeMap,
  208. // attributes are not guaranteed to be returned in document order.
  209. // In particular, Edge/IE can return them out of order, so we cannot
  210. // assume a correspondence between part index and attribute index.
  211. let count = 0;
  212. for (let i = 0; i < length; i++) {
  213. if (endsWith(attributes[i].name, boundAttributeSuffix)) {
  214. count++;
  215. }
  216. }
  217. while (count-- > 0) {
  218. // Get the template literal section leading up to the first
  219. // expression in this attribute
  220. const stringForPart = strings[partIndex];
  221. // Find the attribute name
  222. const name = lastAttributeNameRegex.exec(stringForPart)[2];
  223. // Find the corresponding attribute
  224. // All bound attributes have had a suffix added in
  225. // TemplateResult#getHTML to opt out of special attribute
  226. // handling. To look up the attribute value we also need to add
  227. // the suffix.
  228. const attributeLookupName = name.toLowerCase() + boundAttributeSuffix;
  229. const attributeValue = node.getAttribute(attributeLookupName);
  230. node.removeAttribute(attributeLookupName);
  231. const statics = attributeValue.split(markerRegex);
  232. this.parts.push({
  233. type: 'attribute',
  234. index,
  235. name,
  236. strings: statics,
  237. sanitizer: undefined
  238. });
  239. partIndex += statics.length - 1;
  240. }
  241. }
  242. if (node.tagName === 'TEMPLATE') {
  243. stack.push(node);
  244. walker.currentNode = node.content;
  245. }
  246. }
  247. else if (node.nodeType === 3 /* Node.TEXT_NODE */) {
  248. const data = node.data;
  249. if (data.indexOf(marker) >= 0) {
  250. const parent = node.parentNode;
  251. const strings = data.split(markerRegex);
  252. const lastIndex = strings.length - 1;
  253. // Generate a new text node for each literal section
  254. // These nodes are also used as the markers for node parts
  255. for (let i = 0; i < lastIndex; i++) {
  256. let insert;
  257. let s = strings[i];
  258. if (s === '') {
  259. insert = createMarker();
  260. }
  261. else {
  262. const match = lastAttributeNameRegex.exec(s);
  263. if (match !== null && endsWith(match[2], boundAttributeSuffix)) {
  264. s = s.slice(0, match.index) + match[1] +
  265. match[2].slice(0, -boundAttributeSuffix.length) + match[3];
  266. }
  267. insert = document.createTextNode(s);
  268. }
  269. parent.insertBefore(insert, node);
  270. this.parts.push({ type: 'node', index: ++index });
  271. }
  272. // If there's no text, we must insert a comment to mark our place.
  273. // Else, we can trust it will stick around after cloning.
  274. if (strings[lastIndex] === '') {
  275. parent.insertBefore(createMarker(), node);
  276. nodesToRemove.push(node);
  277. }
  278. else {
  279. node.data = strings[lastIndex];
  280. }
  281. // We have a part for each match found
  282. partIndex += lastIndex;
  283. }
  284. }
  285. else if (node.nodeType === 8 /* Node.COMMENT_NODE */) {
  286. if (node.data === marker) {
  287. const parent = node.parentNode;
  288. // Add a new marker node to be the startNode of the Part if any of
  289. // the following are true:
  290. // * We don't have a previousSibling
  291. // * The previousSibling is already the start of a previous part
  292. if (node.previousSibling === null || index === lastPartIndex) {
  293. index++;
  294. parent.insertBefore(createMarker(), node);
  295. }
  296. lastPartIndex = index;
  297. this.parts.push({ type: 'node', index });
  298. // If we don't have a nextSibling, keep this node so we have an end.
  299. // Else, we can remove it to save future costs.
  300. if (node.nextSibling === null) {
  301. node.data = '';
  302. }
  303. else {
  304. nodesToRemove.push(node);
  305. index--;
  306. }
  307. partIndex++;
  308. }
  309. else {
  310. let i = -1;
  311. while ((i = node.data.indexOf(marker, i + 1)) !== -1) {
  312. // Comment node has a binding marker inside, make an inactive part
  313. // The binding won't work, but subsequent bindings will
  314. // TODO (justinfagnani): consider whether it's even worth it to
  315. // make bindings in comments work
  316. this.parts.push({ type: 'node', index: -1 });
  317. partIndex++;
  318. }
  319. }
  320. }
  321. }
  322. // Remove text binding nodes after the walk to not disturb the TreeWalker
  323. for (const n of nodesToRemove) {
  324. n.parentNode.removeChild(n);
  325. }
  326. }
  327. }
  328. const endsWith = (str, suffix) => {
  329. const index = str.length - suffix.length;
  330. return index >= 0 && str.slice(index) === suffix;
  331. };
  332. const isTemplatePartActive = (part) => part.index !== -1;
  333. /**
  334. * Used to clone existing node instead of each time creating new one which is
  335. * slower
  336. */
  337. const markerNode = document.createComment('');
  338. // Allows `document.createComment('')` to be renamed for a
  339. // small manual size-savings.
  340. const createMarker = () => markerNode.cloneNode();
  341. /**
  342. * This regex extracts the attribute name preceding an attribute-position
  343. * expression. It does this by matching the syntax allowed for attributes
  344. * against the string literal directly preceding the expression, assuming that
  345. * the expression is in an attribute-value position.
  346. *
  347. * See attributes in the HTML spec:
  348. * https://www.w3.org/TR/html5/syntax.html#elements-attributes
  349. *
  350. * " \x09\x0a\x0c\x0d" are HTML space characters:
  351. * https://www.w3.org/TR/html5/infrastructure.html#space-characters
  352. *
  353. * "\0-\x1F\x7F-\x9F" are Unicode control characters, which includes every
  354. * space character except " ".
  355. *
  356. * So an attribute is:
  357. * * The name: any character except a control character, space character, ('),
  358. * ("), ">", "=", or "/"
  359. * * Followed by zero or more space characters
  360. * * Followed by "="
  361. * * Followed by zero or more space characters
  362. * * Followed by:
  363. * * Any character except space, ('), ("), "<", ">", "=", (`), or
  364. * * (") then any non-("), or
  365. * * (') then any non-(')
  366. */
  367. const lastAttributeNameRegex =
  368. // eslint-disable-next-line no-control-regex
  369. /([ \x09\x0a\x0c\x0d])([^\0-\x1F\x7F-\x9F "'>=/]+)([ \x09\x0a\x0c\x0d]*=[ \x09\x0a\x0c\x0d]*(?:[^ \x09\x0a\x0c\x0d"'`<>=]*|"[^"]*|'[^']*))$/;
  370. /**
  371. * @license
  372. * Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
  373. * This code may only be used under the BSD style license found at
  374. * http://polymer.github.io/LICENSE.txt
  375. * The complete set of authors may be found at
  376. * http://polymer.github.io/AUTHORS.txt
  377. * The complete set of contributors may be found at
  378. * http://polymer.github.io/CONTRIBUTORS.txt
  379. * Code distributed by Google as part of the polymer project is also
  380. * subject to an additional IP rights grant found at
  381. * http://polymer.github.io/PATENTS.txt
  382. */
  383. /**
  384. * An instance of a `Template` that can be attached to the DOM and updated
  385. * with new values.
  386. */
  387. class TemplateInstance {
  388. constructor(template, processor, options) {
  389. this.__parts = [];
  390. this.template = template;
  391. this.processor = processor;
  392. this.options = options;
  393. }
  394. update(values) {
  395. let i = 0;
  396. for (const part of this.__parts) {
  397. if (part !== undefined) {
  398. part.setValue(values[i]);
  399. }
  400. i++;
  401. }
  402. for (const part of this.__parts) {
  403. if (part !== undefined) {
  404. part.commit();
  405. }
  406. }
  407. }
  408. _clone() {
  409. // There are a number of steps in the lifecycle of a template instance's
  410. // DOM fragment:
  411. // 1. Clone - create the instance fragment
  412. // 2. Adopt - adopt into the main document
  413. // 3. Process - find part markers and create parts
  414. // 4. Upgrade - upgrade custom elements
  415. // 5. Update - set node, attribute, property, etc., values
  416. // 6. Connect - connect to the document. Optional and outside of this
  417. // method.
  418. //
  419. // We have a few constraints on the ordering of these steps:
  420. // * We need to upgrade before updating, so that property values will pass
  421. // through any property setters.
  422. // * We would like to process before upgrading so that we're sure that the
  423. // cloned fragment is inert and not disturbed by self-modifying DOM.
  424. // * We want custom elements to upgrade even in disconnected fragments.
  425. //
  426. // Given these constraints, with full custom elements support we would
  427. // prefer the order: Clone, Process, Adopt, Upgrade, Update, Connect
  428. //
  429. // But Safari does not implement CustomElementRegistry#upgrade, so we
  430. // can not implement that order and still have upgrade-before-update and
  431. // upgrade disconnected fragments. So we instead sacrifice the
  432. // process-before-upgrade constraint, since in Custom Elements v1 elements
  433. // must not modify their light DOM in the constructor. We still have issues
  434. // when co-existing with CEv0 elements like Polymer 1, and with polyfills
  435. // that don't strictly adhere to the no-modification rule because shadow
  436. // DOM, which may be created in the constructor, is emulated by being placed
  437. // in the light DOM.
  438. //
  439. // The resulting order is on native is: Clone, Adopt, Upgrade, Process,
  440. // Update, Connect. document.importNode() performs Clone, Adopt, and Upgrade
  441. // in one step.
  442. //
  443. // The Custom Elements v1 polyfill supports upgrade(), so the order when
  444. // polyfilled is the more ideal: Clone, Process, Adopt, Upgrade, Update,
  445. // Connect.
  446. const fragment = isCEPolyfill ?
  447. this.template.element.content.cloneNode(true) :
  448. document.importNode(this.template.element.content, true);
  449. const stack = [];
  450. const parts = this.template.parts;
  451. // Edge needs all 4 parameters present; IE11 needs 3rd parameter to be null
  452. const walker = document.createTreeWalker(fragment, 133 /* NodeFilter.SHOW_{ELEMENT|COMMENT|TEXT} */, null, false);
  453. let partIndex = 0;
  454. let nodeIndex = 0;
  455. let part;
  456. let node = walker.nextNode();
  457. // Loop through all the nodes and parts of a template
  458. while (partIndex < parts.length) {
  459. part = parts[partIndex];
  460. if (!isTemplatePartActive(part)) {
  461. this.__parts.push(undefined);
  462. partIndex++;
  463. continue;
  464. }
  465. // Progress the tree walker until we find our next part's node.
  466. // Note that multiple parts may share the same node (attribute parts
  467. // on a single element), so this loop may not run at all.
  468. while (nodeIndex < part.index) {
  469. nodeIndex++;
  470. if (node.nodeName === 'TEMPLATE') {
  471. stack.push(node);
  472. walker.currentNode = node.content;
  473. }
  474. if ((node = walker.nextNode()) === null) {
  475. // We've exhausted the content inside a nested template element.
  476. // Because we still have parts (the outer for-loop), we know:
  477. // - There is a template in the stack
  478. // - The walker will find a nextNode outside the template
  479. walker.currentNode = stack.pop();
  480. node = walker.nextNode();
  481. }
  482. }
  483. // We've arrived at our part's node.
  484. if (part.type === 'node') {
  485. const textPart = this.processor.handleTextExpression(this.options, part);
  486. textPart.insertAfterNode(node.previousSibling);
  487. this.__parts.push(textPart);
  488. }
  489. else {
  490. this.__parts.push(...this.processor.handleAttributeExpressions(node, part.name, part.strings, this.options, part));
  491. }
  492. partIndex++;
  493. }
  494. if (isCEPolyfill) {
  495. document.adoptNode(fragment);
  496. customElements.upgrade(fragment);
  497. }
  498. return fragment;
  499. }
  500. }
  501. /**
  502. * @license
  503. * Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
  504. * This code may only be used under the BSD style license found at
  505. * http://polymer.github.io/LICENSE.txt
  506. * The complete set of authors may be found at
  507. * http://polymer.github.io/AUTHORS.txt
  508. * The complete set of contributors may be found at
  509. * http://polymer.github.io/CONTRIBUTORS.txt
  510. * Code distributed by Google as part of the polymer project is also
  511. * subject to an additional IP rights grant found at
  512. * http://polymer.github.io/PATENTS.txt
  513. */
  514. let policy;
  515. /**
  516. * Turns the value to trusted HTML. If the application uses Trusted Types the
  517. * value is transformed into TrustedHTML, which can be assigned to execution
  518. * sink. If the application doesn't use Trusted Types, the return value is the
  519. * same as the argument.
  520. */
  521. function convertConstantTemplateStringToTrustedHTML(value) {
  522. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  523. const w = window;
  524. // TrustedTypes have been renamed to trustedTypes
  525. // (https://github.com/WICG/trusted-types/issues/177)
  526. const trustedTypes = (w.trustedTypes || w.TrustedTypes);
  527. if (trustedTypes && !policy) {
  528. policy = trustedTypes.createPolicy('lit-html', { createHTML: (s) => s });
  529. }
  530. return policy ? policy.createHTML(value) : value;
  531. }
  532. const commentMarker = ` ${marker} `;
  533. /**
  534. * Used to clone existing node instead of each time creating new one which is
  535. * slower
  536. */
  537. const emptyTemplateNode = document.createElement('template');
  538. /**
  539. * The return type of `html`, which holds a Template and the values from
  540. * interpolated expressions.
  541. */
  542. class TemplateResult {
  543. constructor(strings, values, type, processor) {
  544. this.strings = strings;
  545. this.values = values;
  546. this.type = type;
  547. this.processor = processor;
  548. }
  549. /**
  550. * Returns a string of HTML used to create a `<template>` element.
  551. */
  552. getHTML() {
  553. const l = this.strings.length - 1;
  554. let html = '';
  555. let isCommentBinding = false;
  556. for (let i = 0; i < l; i++) {
  557. const s = this.strings[i];
  558. // For each binding we want to determine the kind of marker to insert
  559. // into the template source before it's parsed by the browser's HTML
  560. // parser. The marker type is based on whether the expression is in an
  561. // attribute, text, or comment position.
  562. // * For node-position bindings we insert a comment with the marker
  563. // sentinel as its text content, like <!--{{lit-guid}}-->.
  564. // * For attribute bindings we insert just the marker sentinel for the
  565. // first binding, so that we support unquoted attribute bindings.
  566. // Subsequent bindings can use a comment marker because multi-binding
  567. // attributes must be quoted.
  568. // * For comment bindings we insert just the marker sentinel so we don't
  569. // close the comment.
  570. //
  571. // The following code scans the template source, but is *not* an HTML
  572. // parser. We don't need to track the tree structure of the HTML, only
  573. // whether a binding is inside a comment, and if not, if it appears to be
  574. // the first binding in an attribute.
  575. const commentOpen = s.lastIndexOf('<!--');
  576. // We're in comment position if we have a comment open with no following
  577. // comment close. Because <-- can appear in an attribute value there can
  578. // be false positives.
  579. isCommentBinding = (commentOpen > -1 || isCommentBinding) &&
  580. s.indexOf('-->', commentOpen + 1) === -1;
  581. // Check to see if we have an attribute-like sequence preceding the
  582. // expression. This can match "name=value" like structures in text,
  583. // comments, and attribute values, so there can be false-positives.
  584. const attributeMatch = lastAttributeNameRegex.exec(s);
  585. if (attributeMatch === null) {
  586. // We're only in this branch if we don't have a attribute-like
  587. // preceding sequence. For comments, this guards against unusual
  588. // attribute values like <div foo="<!--${'bar'}">. Cases like
  589. // <!-- foo=${'bar'}--> are handled correctly in the attribute branch
  590. // below.
  591. html += s + (isCommentBinding ? commentMarker : nodeMarker);
  592. }
  593. else {
  594. // For attributes we use just a marker sentinel, and also append a
  595. // $lit$ suffix to the name to opt-out of attribute-specific parsing
  596. // that IE and Edge do for style and certain SVG attributes.
  597. html += s.substr(0, attributeMatch.index) + attributeMatch[1] +
  598. attributeMatch[2] + boundAttributeSuffix + attributeMatch[3] +
  599. marker;
  600. }
  601. }
  602. html += this.strings[l];
  603. return html;
  604. }
  605. getTemplateElement() {
  606. const template = emptyTemplateNode.cloneNode();
  607. // this is secure because `this.strings` is a TemplateStringsArray.
  608. // TODO: validate this when
  609. // https://github.com/tc39/proposal-array-is-template-object is implemented.
  610. template.innerHTML =
  611. convertConstantTemplateStringToTrustedHTML(this.getHTML());
  612. return template;
  613. }
  614. }
  615. /**
  616. * A TemplateResult for SVG fragments.
  617. *
  618. * This class wraps HTML in an `<svg>` tag in order to parse its contents in the
  619. * SVG namespace, then modifies the template to remove the `<svg>` tag so that
  620. * clones only container the original fragment.
  621. */
  622. class SVGTemplateResult extends TemplateResult {
  623. getHTML() {
  624. return `<svg>${super.getHTML()}</svg>`;
  625. }
  626. getTemplateElement() {
  627. const template = super.getTemplateElement();
  628. const content = template.content;
  629. const svgElement = content.firstChild;
  630. content.removeChild(svgElement);
  631. reparentNodes(content, svgElement.firstChild);
  632. return template;
  633. }
  634. }
  635. /**
  636. * @license
  637. * Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
  638. * This code may only be used under the BSD style license found at
  639. * http://polymer.github.io/LICENSE.txt
  640. * The complete set of authors may be found at
  641. * http://polymer.github.io/AUTHORS.txt
  642. * The complete set of contributors may be found at
  643. * http://polymer.github.io/CONTRIBUTORS.txt
  644. * Code distributed by Google as part of the polymer project is also
  645. * subject to an additional IP rights grant found at
  646. * http://polymer.github.io/PATENTS.txt
  647. */
  648. const isPrimitive = (value) => {
  649. return (value === null ||
  650. !(typeof value === 'object' || typeof value === 'function'));
  651. };
  652. const isIterable = (value) => {
  653. return Array.isArray(value) ||
  654. // tslint:disable-next-line: no-any
  655. !!(value && value[Symbol.iterator]);
  656. };
  657. const identityFunction = (value) => value;
  658. const noopSanitizer = (_node, _name, _type) => identityFunction;
  659. /**
  660. * A global callback used to get a sanitizer for a given field.
  661. */
  662. let sanitizerFactory = noopSanitizer;
  663. /** Sets the global sanitizer factory. */
  664. const setSanitizerFactory = (newSanitizer) => {
  665. if (sanitizerFactory !== noopSanitizer) {
  666. throw new Error(`Attempted to overwrite existing lit-html security policy.` +
  667. ` setSanitizeDOMValueFactory should be called at most once.`);
  668. }
  669. sanitizerFactory = newSanitizer;
  670. };
  671. /**
  672. * Used to clone text node instead of each time creating new one which is slower
  673. */
  674. const emptyTextNode = document.createTextNode('');
  675. /**
  676. * Writes attribute values to the DOM for a group of AttributeParts bound to a
  677. * single attribute. The value is only set once even if there are multiple parts
  678. * for an attribute.
  679. */
  680. class AttributeCommitter {
  681. constructor(element, name, strings,
  682. // Next breaking change, consider making this param required.
  683. templatePart, kind = 'attribute') {
  684. this.dirty = true;
  685. this.element = element;
  686. this.name = name;
  687. this.strings = strings;
  688. this.parts = [];
  689. let sanitizer = templatePart && templatePart.sanitizer;
  690. if (sanitizer === undefined) {
  691. sanitizer = sanitizerFactory(element, name, kind);
  692. if (templatePart !== undefined) {
  693. templatePart.sanitizer = sanitizer;
  694. }
  695. }
  696. this.sanitizer = sanitizer;
  697. for (let i = 0; i < strings.length - 1; i++) {
  698. this.parts[i] = this._createPart();
  699. }
  700. }
  701. /**
  702. * Creates a single part. Override this to create a differnt type of part.
  703. */
  704. _createPart() {
  705. return new AttributePart(this);
  706. }
  707. _getValue() {
  708. const strings = this.strings;
  709. const parts = this.parts;
  710. const l = strings.length - 1;
  711. // If we're assigning an attribute via syntax like:
  712. // attr="${foo}" or attr=${foo}
  713. // but not
  714. // attr="${foo} ${bar}" or attr="${foo} baz"
  715. // then we don't want to coerce the attribute value into one long
  716. // string. Instead we want to just return the value itself directly,
  717. // so that sanitizeDOMValue can get the actual value rather than
  718. // String(value)
  719. // The exception is if v is an array, in which case we do want to smash
  720. // it together into a string without calling String() on the array.
  721. //
  722. // This also allows trusted values (when using TrustedTypes) being
  723. // assigned to DOM sinks without being stringified in the process.
  724. if (l === 1 && strings[0] === '' && strings[1] === '' &&
  725. parts[0] !== undefined) {
  726. const v = parts[0].value;
  727. if (!isIterable(v)) {
  728. return v;
  729. }
  730. }
  731. let text = '';
  732. for (let i = 0; i < l; i++) {
  733. text += strings[i];
  734. const part = parts[i];
  735. if (part !== undefined) {
  736. const v = part.value;
  737. if (isPrimitive(v) || !isIterable(v)) {
  738. text += typeof v === 'string' ? v : String(v);
  739. }
  740. else {
  741. for (const t of v) {
  742. text += typeof t === 'string' ? t : String(t);
  743. }
  744. }
  745. }
  746. }
  747. text += strings[l];
  748. return text;
  749. }
  750. commit() {
  751. if (this.dirty) {
  752. this.dirty = false;
  753. let value = this._getValue();
  754. value = this.sanitizer(value);
  755. if (typeof value === 'symbol') {
  756. // Native Symbols throw if they're coerced to string.
  757. value = String(value);
  758. }
  759. this.element.setAttribute(this.name, value);
  760. }
  761. }
  762. }
  763. /**
  764. * A Part that controls all or part of an attribute value.
  765. */
  766. class AttributePart {
  767. constructor(committer) {
  768. this.value = undefined;
  769. this.committer = committer;
  770. }
  771. setValue(value) {
  772. if (value !== noChange && (!isPrimitive(value) || value !== this.value)) {
  773. this.value = value;
  774. // If the value is a not a directive, dirty the committer so that it'll
  775. // call setAttribute. If the value is a directive, it'll dirty the
  776. // committer if it calls setValue().
  777. if (!isDirective(value)) {
  778. this.committer.dirty = true;
  779. }
  780. }
  781. }
  782. commit() {
  783. while (isDirective(this.value)) {
  784. const directive = this.value;
  785. this.value = noChange;
  786. // tslint:disable-next-line: no-any
  787. if (directive.isClass) {
  788. // tslint:disable-next-line: no-any
  789. directive.body(this);
  790. }
  791. else {
  792. directive(this);
  793. }
  794. }
  795. if (this.value === noChange) {
  796. return;
  797. }
  798. this.committer.commit();
  799. }
  800. }
  801. /**
  802. * A Part that controls a location within a Node tree. Like a Range, NodePart
  803. * has start and end locations and can set and update the Nodes between those
  804. * locations.
  805. *
  806. * NodeParts support several value types: primitives, Nodes, TemplateResults,
  807. * as well as arrays and iterables of those types.
  808. */
  809. class NodePart {
  810. constructor(options, templatePart) {
  811. this.value = undefined;
  812. this.__pendingValue = undefined;
  813. /**
  814. * The sanitizer to use when writing text contents into this NodePart.
  815. *
  816. * We have to initialize this here rather than at the template literal level
  817. * because the security of text content depends on the context into which
  818. * it's written. e.g. the same text has different security requirements
  819. * when a child of a <script> vs a <style> vs a <div>.
  820. */
  821. this.textSanitizer = undefined;
  822. this.options = options;
  823. this.templatePart = templatePart;
  824. }
  825. /**
  826. * Appends this part into a container.
  827. *
  828. * This part must be empty, as its contents are not automatically moved.
  829. */
  830. appendInto(container) {
  831. this.startNode = container.appendChild(createMarker());
  832. this.endNode = container.appendChild(createMarker());
  833. }
  834. /**
  835. * Inserts this part after the `ref` node (between `ref` and `ref`'s next
  836. * sibling). Both `ref` and its next sibling must be static, unchanging nodes
  837. * such as those that appear in a literal section of a template.
  838. *
  839. * This part must be empty, as its contents are not automatically moved.
  840. */
  841. insertAfterNode(ref) {
  842. this.startNode = ref;
  843. this.endNode = ref.nextSibling;
  844. }
  845. /**
  846. * Appends this part into a parent part.
  847. *
  848. * This part must be empty, as its contents are not automatically moved.
  849. */
  850. appendIntoPart(part) {
  851. part.__insert(this.startNode = createMarker());
  852. part.__insert(this.endNode = createMarker());
  853. }
  854. /**
  855. * Inserts this part after the `ref` part.
  856. *
  857. * This part must be empty, as its contents are not automatically moved.
  858. */
  859. insertAfterPart(ref) {
  860. ref.__insert(this.startNode = createMarker());
  861. this.endNode = ref.endNode;
  862. ref.endNode = this.startNode;
  863. }
  864. setValue(value) {
  865. this.__pendingValue = value;
  866. }
  867. commit() {
  868. while (isDirective(this.__pendingValue)) {
  869. const directive = this.__pendingValue;
  870. this.__pendingValue = noChange;
  871. // tslint:disable-next-line: no-any
  872. if (directive.isClass) {
  873. // tslint:disable-next-line: no-any
  874. directive.body(this);
  875. }
  876. else {
  877. directive(this);
  878. }
  879. }
  880. const value = this.__pendingValue;
  881. if (value === noChange) {
  882. return;
  883. }
  884. if (isPrimitive(value)) {
  885. if (value !== this.value) {
  886. this.__commitText(value);
  887. }
  888. }
  889. else if (value instanceof TemplateResult) {
  890. this.__commitTemplateResult(value);
  891. }
  892. else if (value instanceof Node) {
  893. this.__commitNode(value);
  894. }
  895. else if (isIterable(value)) {
  896. this.__commitIterable(value);
  897. }
  898. else if (value === nothing) {
  899. this.value = nothing;
  900. this.clear();
  901. }
  902. else {
  903. // Fallback, will render the string representation
  904. this.__commitText(value);
  905. }
  906. }
  907. __insert(node) {
  908. this.endNode.parentNode.insertBefore(node, this.endNode);
  909. }
  910. __commitNode(value) {
  911. if (this.value === value) {
  912. return;
  913. }
  914. this.clear();
  915. this.__insert(value);
  916. this.value = value;
  917. }
  918. __commitText(value) {
  919. const node = this.startNode.nextSibling;
  920. value = value == null ? '' : value;
  921. if (node === this.endNode.previousSibling &&
  922. node.nodeType === 3 /* Node.TEXT_NODE */) {
  923. // If we only have a single text node between the markers, we can just
  924. // set its value, rather than replacing it.
  925. if (this.textSanitizer === undefined) {
  926. this.textSanitizer = sanitizerFactory(node, 'data', 'property');
  927. }
  928. const renderedValue = this.textSanitizer(value);
  929. node.data = typeof renderedValue === 'string' ?
  930. renderedValue :
  931. String(renderedValue);
  932. }
  933. else {
  934. // When setting text content, for security purposes it matters a lot what
  935. // the parent is. For example, <style> and <script> need to be handled
  936. // with care, while <span> does not. So first we need to put a text node
  937. // into the document, then we can sanitize its contentx.
  938. const textNode = emptyTextNode.cloneNode();
  939. this.__commitNode(textNode);
  940. if (this.textSanitizer === undefined) {
  941. this.textSanitizer = sanitizerFactory(textNode, 'data', 'property');
  942. }
  943. const renderedValue = this.textSanitizer(value);
  944. textNode.data = typeof renderedValue === 'string' ? renderedValue :
  945. String(renderedValue);
  946. }
  947. this.value = value;
  948. }
  949. __commitTemplateResult(value) {
  950. const template = this.options.templateFactory(value);
  951. if (this.value instanceof TemplateInstance &&
  952. this.value.template === template) {
  953. this.value.update(value.values);
  954. }
  955. else {
  956. // `value` is a template result that was constructed without knowledge of
  957. // the parent we're about to write it into. sanitizeDOMValue hasn't been
  958. // made aware of this relationship, and for scripts and style specifically
  959. // this is known to be unsafe. So in the case where the user is in
  960. // "secure mode" (i.e. when there's a sanitizeDOMValue set), we just want
  961. // to forbid this because it's not a use case we want to support.
  962. // We only apply this policy when sanitizerFactory has been set to
  963. // prevent this from being a breaking change to the library.
  964. const parent = this.endNode.parentNode;
  965. if (sanitizerFactory !== noopSanitizer && parent.nodeName === 'STYLE' ||
  966. parent.nodeName === 'SCRIPT') {
  967. this.__commitText('/* lit-html will not write ' +
  968. 'TemplateResults to scripts and styles */');
  969. return;
  970. }
  971. // Make sure we propagate the template processor from the TemplateResult
  972. // so that we use its syntax extension, etc. The template factory comes
  973. // from the render function options so that it can control template
  974. // caching and preprocessing.
  975. const instance = new TemplateInstance(template, value.processor, this.options);
  976. const fragment = instance._clone();
  977. instance.update(value.values);
  978. this.__commitNode(fragment);
  979. this.value = instance;
  980. }
  981. }
  982. __commitIterable(value) {
  983. // For an Iterable, we create a new InstancePart per item, then set its
  984. // value to the item. This is a little bit of overhead for every item in
  985. // an Iterable, but it lets us recurse easily and efficiently update Arrays
  986. // of TemplateResults that will be commonly returned from expressions like:
  987. // array.map((i) => html`${i}`), by reusing existing TemplateInstances.
  988. // If _value is an array, then the previous render was of an
  989. // iterable and _value will contain the NodeParts from the previous
  990. // render. If _value is not an array, clear this part and make a new
  991. // array for NodeParts.
  992. if (!Array.isArray(this.value)) {
  993. this.value = [];
  994. this.clear();
  995. }
  996. // Lets us keep track of how many items we stamped so we can clear leftover
  997. // items from a previous render
  998. const itemParts = this.value;
  999. let partIndex = 0;
  1000. let itemPart;
  1001. for (const item of value) {
  1002. // Try to reuse an existing part
  1003. itemPart = itemParts[partIndex];
  1004. // If no existing part, create a new one
  1005. if (itemPart === undefined) {
  1006. itemPart = new NodePart(this.options, this.templatePart);
  1007. itemParts.push(itemPart);
  1008. if (partIndex === 0) {
  1009. itemPart.appendIntoPart(this);
  1010. }
  1011. else {
  1012. itemPart.insertAfterPart(itemParts[partIndex - 1]);
  1013. }
  1014. }
  1015. itemPart.setValue(item);
  1016. itemPart.commit();
  1017. partIndex++;
  1018. }
  1019. if (partIndex < itemParts.length) {
  1020. // Truncate the parts array so _value reflects the current state
  1021. itemParts.length = partIndex;
  1022. this.clear(itemPart && itemPart.endNode);
  1023. }
  1024. }
  1025. clear(startNode = this.startNode) {
  1026. removeNodes(this.startNode.parentNode, startNode.nextSibling, this.endNode);
  1027. }
  1028. }
  1029. /**
  1030. * Implements a boolean attribute, roughly as defined in the HTML
  1031. * specification.
  1032. *
  1033. * If the value is truthy, then the attribute is present with a value of
  1034. * ''. If the value is falsey, the attribute is removed.
  1035. */
  1036. class BooleanAttributePart {
  1037. constructor(element, name, strings) {
  1038. this.value = undefined;
  1039. this.__pendingValue = undefined;
  1040. if (strings.length !== 2 || strings[0] !== '' || strings[1] !== '') {
  1041. throw new Error('Boolean attributes can only contain a single expression');
  1042. }
  1043. this.element = element;
  1044. this.name = name;
  1045. this.strings = strings;
  1046. }
  1047. setValue(value) {
  1048. this.__pendingValue = value;
  1049. }
  1050. commit() {
  1051. while (isDirective(this.__pendingValue)) {
  1052. const directive = this.__pendingValue;
  1053. this.__pendingValue = noChange;
  1054. // tslint:disable-next-line: no-any
  1055. if (directive.isClass) {
  1056. // tslint:disable-next-line: no-any
  1057. directive.body(this);
  1058. }
  1059. else {
  1060. directive(this);
  1061. }
  1062. }
  1063. if (this.__pendingValue === noChange) {
  1064. return;
  1065. }
  1066. const value = !!this.__pendingValue;
  1067. if (this.value !== value) {
  1068. if (value) {
  1069. this.element.setAttribute(this.name, '');
  1070. }
  1071. else {
  1072. this.element.removeAttribute(this.name);
  1073. }
  1074. this.value = value;
  1075. }
  1076. this.__pendingValue = noChange;
  1077. }
  1078. }
  1079. /**
  1080. * Sets attribute values for PropertyParts, so that the value is only set once
  1081. * even if there are multiple parts for a property.
  1082. *
  1083. * If an expression controls the whole property value, then the value is simply
  1084. * assigned to the property under control. If there are string literals or
  1085. * multiple expressions, then the strings are expressions are interpolated into
  1086. * a string first.
  1087. */
  1088. class PropertyCommitter extends AttributeCommitter {
  1089. constructor(element, name, strings,
  1090. // Next breaking change, consider making this param required.
  1091. templatePart) {
  1092. super(element, name, strings, templatePart, 'property');
  1093. this.single =
  1094. (strings.length === 2 && strings[0] === '' && strings[1] === '');
  1095. }
  1096. _createPart() {
  1097. return new PropertyPart(this);
  1098. }
  1099. _getValue() {
  1100. if (this.single) {
  1101. return this.parts[0].value;
  1102. }
  1103. return super._getValue();
  1104. }
  1105. commit() {
  1106. if (this.dirty) {
  1107. this.dirty = false;
  1108. let value = this._getValue();
  1109. value = this.sanitizer(value);
  1110. // tslint:disable-next-line: no-any
  1111. this.element[this.name] = value;
  1112. }
  1113. }
  1114. }
  1115. class PropertyPart extends AttributePart {
  1116. }
  1117. // Detect event listener options support. If the `capture` property is read
  1118. // from the options object, then options are supported. If not, then the third
  1119. // argument to add/removeEventListener is interpreted as the boolean capture
  1120. // value so we should only pass the `capture` property.
  1121. let eventOptionsSupported = false;
  1122. // Wrap into an IIFE because MS Edge <= v41 does not support having try/catch
  1123. // blocks right into the body of a module
  1124. (() => {
  1125. try {
  1126. const options = {
  1127. get capture() {
  1128. eventOptionsSupported = true;
  1129. return false;
  1130. }
  1131. };
  1132. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  1133. window.addEventListener('test', options, options);
  1134. // eslint-disable-next-line @typescript-eslint/no-explicit-any
  1135. window.removeEventListener('test', options, options);
  1136. }
  1137. catch (_e) {
  1138. // noop
  1139. }
  1140. })();
  1141. class EventPart {
  1142. constructor(element, eventName, eventContext) {
  1143. this.value = undefined;
  1144. this.__pendingValue = undefined;
  1145. this.element = element;
  1146. this.eventName = eventName;
  1147. this.eventContext = eventContext;
  1148. this.__boundHandleEvent = (e) => this.handleEvent(e);
  1149. }
  1150. setValue(value) {
  1151. this.__pendingValue = value;
  1152. }
  1153. commit() {
  1154. while (isDirective(this.__pendingValue)) {
  1155. const directive = this.__pendingValue;
  1156. this.__pendingValue = noChange;
  1157. // tslint:disable-next-line: no-any
  1158. if (directive.isClass) {
  1159. // tslint:disable-next-line: no-any
  1160. directive.body(this);
  1161. }
  1162. else {
  1163. directive(this);
  1164. }
  1165. }
  1166. if (this.__pendingValue === noChange) {
  1167. return;
  1168. }
  1169. const newListener = this.__pendingValue;
  1170. const oldListener = this.value;
  1171. const shouldRemoveListener = newListener == null ||
  1172. oldListener != null &&
  1173. (newListener.capture !== oldListener.capture ||
  1174. newListener.once !== oldListener.once ||
  1175. newListener.passive !== oldListener.passive);
  1176. const shouldAddListener = newListener != null && (oldListener == null || shouldRemoveListener);
  1177. if (shouldRemoveListener) {
  1178. this.element.removeEventListener(this.eventName, this.__boundHandleEvent, this.__options);
  1179. }
  1180. if (shouldAddListener) {
  1181. this.__options = getOptions(newListener);
  1182. this.element.addEventListener(this.eventName, this.__boundHandleEvent, this.__options);
  1183. }
  1184. this.value = newListener;
  1185. this.__pendingValue = noChange;
  1186. }
  1187. handleEvent(event) {
  1188. if (typeof this.value === 'function') {
  1189. this.value.call(this.eventContext || this.element, event);
  1190. }
  1191. else {
  1192. this.value.handleEvent(event);
  1193. }
  1194. }
  1195. }
  1196. // We copy options because of the inconsistent behavior of browsers when reading
  1197. // the third argument of add/removeEventListener. IE11 doesn't support options
  1198. // at all. Chrome 41 only reads `capture` if the argument is an object.
  1199. const getOptions = (o) => o &&
  1200. (eventOptionsSupported ?
  1201. { capture: o.capture, passive: o.passive, once: o.once } :
  1202. o.capture);
  1203. /**
  1204. * @license
  1205. * Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
  1206. * This code may only be used under the BSD style license found at
  1207. * http://polymer.github.io/LICENSE.txt
  1208. * The complete set of authors may be found at
  1209. * http://polymer.github.io/AUTHORS.txt
  1210. * The complete set of contributors may be found at
  1211. * http://polymer.github.io/CONTRIBUTORS.txt
  1212. * Code distributed by Google as part of the polymer project is also
  1213. * subject to an additional IP rights grant found at
  1214. * http://polymer.github.io/PATENTS.txt
  1215. */
  1216. /**
  1217. * Creates Parts when a template is instantiated.
  1218. */
  1219. class DefaultTemplateProcessor {
  1220. /**
  1221. * Create parts for an attribute-position binding, given the event, attribute
  1222. * name, and string literals.
  1223. *
  1224. * @param element The element containing the binding
  1225. * @param name The attribute name
  1226. * @param strings The string literals. There are always at least two strings,
  1227. * event for fully-controlled bindings with a single expression.
  1228. */
  1229. handleAttributeExpressions(element, name, strings, options, templatePart) {
  1230. const prefix = name[0];
  1231. if (prefix === '.') {
  1232. const committer = new PropertyCommitter(element, name.slice(1), strings, templatePart);
  1233. return committer.parts;
  1234. }
  1235. if (prefix === '@') {
  1236. return [new EventPart(element, name.slice(1), options.eventContext)];
  1237. }
  1238. if (prefix === '?') {
  1239. return [new BooleanAttributePart(element, name.slice(1), strings)];
  1240. }
  1241. const committer = new AttributeCommitter(element, name, strings, templatePart);
  1242. return committer.parts;
  1243. }
  1244. /**
  1245. * Create parts for a text-position binding.
  1246. * @param templateFactory
  1247. */
  1248. handleTextExpression(options, nodeTemplatePart) {
  1249. return new NodePart(options, nodeTemplatePart);
  1250. }
  1251. }
  1252. const defaultTemplateProcessor = new DefaultTemplateProcessor();
  1253. /**
  1254. * @license
  1255. * Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
  1256. * This code may only be used under the BSD style license found at
  1257. * http://polymer.github.io/LICENSE.txt
  1258. * The complete set of authors may be found at
  1259. * http://polymer.github.io/AUTHORS.txt
  1260. * The complete set of contributors may be found at
  1261. * http://polymer.github.io/CONTRIBUTORS.txt
  1262. * Code distributed by Google as part of the polymer project is also
  1263. * subject to an additional IP rights grant found at
  1264. * http://polymer.github.io/PATENTS.txt
  1265. */
  1266. /**
  1267. * The default TemplateFactory which caches Templates keyed on
  1268. * result.type and result.strings.
  1269. */
  1270. function templateFactory(result) {
  1271. let templateCache = templateCaches.get(result.type);
  1272. if (templateCache === undefined) {
  1273. templateCache = {
  1274. stringsArray: new WeakMap(),
  1275. keyString: new Map()
  1276. };
  1277. templateCaches.set(result.type, templateCache);
  1278. }
  1279. let template = templateCache.stringsArray.get(result.strings);
  1280. if (template !== undefined) {
  1281. return template;
  1282. }
  1283. // If the TemplateStringsArray is new, generate a key from the strings
  1284. // This key is shared between all templates with identical content
  1285. const key = result.strings.join(marker);
  1286. // Check if we already have a Template for this key
  1287. template = templateCache.keyString.get(key);
  1288. if (template === undefined) {
  1289. // If we have not seen this key before, create a new Template
  1290. template = new Template(result, result.getTemplateElement());
  1291. // Cache the Template for this key
  1292. templateCache.keyString.set(key, template);
  1293. }
  1294. // Cache all future queries for this TemplateStringsArray
  1295. templateCache.stringsArray.set(result.strings, template);
  1296. return template;
  1297. }
  1298. const templateCaches = new Map();
  1299. /**
  1300. * @license
  1301. * Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
  1302. * This code may only be used under the BSD style license found at
  1303. * http://polymer.github.io/LICENSE.txt
  1304. * The complete set of authors may be found at
  1305. * http://polymer.github.io/AUTHORS.txt
  1306. * The complete set of contributors may be found at
  1307. * http://polymer.github.io/CONTRIBUTORS.txt
  1308. * Code distributed by Google as part of the polymer project is also
  1309. * subject to an additional IP rights grant found at
  1310. * http://polymer.github.io/PATENTS.txt
  1311. */
  1312. const parts = new WeakMap();
  1313. /**
  1314. * Renders a template result or other value to a container.
  1315. *
  1316. * To update a container with new values, reevaluate the template literal and
  1317. * call `render` with the new result.
  1318. *
  1319. * @param result Any value renderable by NodePart - typically a TemplateResult
  1320. * created by evaluating a template tag like `html` or `svg`.
  1321. * @param container A DOM parent to render to. The entire contents are either
  1322. * replaced, or efficiently updated if the same result type was previous
  1323. * rendered there.
  1324. * @param options RenderOptions for the entire render tree rendered to this
  1325. * container. Render options must *not* change between renders to the same
  1326. * container, as those changes will not effect previously rendered DOM.
  1327. */
  1328. const render = (result, container, options) => {
  1329. let part = parts.get(container);
  1330. if (part === undefined) {
  1331. removeNodes(container, container.firstChild);
  1332. parts.set(container, part = new NodePart(Object.assign({ templateFactory }, options), undefined));
  1333. part.appendInto(container);
  1334. }
  1335. part.setValue(result);
  1336. part.commit();
  1337. };
  1338. /**
  1339. * @license
  1340. * Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
  1341. * This code may only be used under the BSD style license found at
  1342. * http://polymer.github.io/LICENSE.txt
  1343. * The complete set of authors may be found at
  1344. * http://polymer.github.io/AUTHORS.txt
  1345. * The complete set of contributors may be found at
  1346. * http://polymer.github.io/CONTRIBUTORS.txt
  1347. * Code distributed by Google as part of the polymer project is also
  1348. * subject to an additional IP rights grant found at
  1349. * http://polymer.github.io/PATENTS.txt
  1350. */
  1351. // IMPORTANT: do not change the property name or the assignment expression.
  1352. // This line will be used in regexes to search for lit-html usage.
  1353. // TODO(justinfagnani): inject version number at build time
  1354. const isBrowser = typeof window !== 'undefined';
  1355. if (isBrowser) {
  1356. // If we run in the browser set version
  1357. (window['litHtmlVersions'] || (window['litHtmlVersions'] = [])).push('1.1.7');
  1358. }
  1359. /**
  1360. * Interprets a template literal as an HTML template that can efficiently
  1361. * render to and update a container.
  1362. */
  1363. const html = (strings, ...values) => new TemplateResult(strings, values, 'html', defaultTemplateProcessor);
  1364. /**
  1365. * Interprets a template literal as an SVG template that can efficiently
  1366. * render to and update a container.
  1367. */
  1368. const svg = (strings, ...values) => new SVGTemplateResult(strings, values, 'svg', defaultTemplateProcessor);
  1369. var lithtml = /*#__PURE__*/Object.freeze({
  1370. __proto__: null,
  1371. html: html,
  1372. svg: svg,
  1373. DefaultTemplateProcessor: DefaultTemplateProcessor,
  1374. defaultTemplateProcessor: defaultTemplateProcessor,
  1375. directive: directive,
  1376. Directive: Directive,
  1377. isDirective: isDirective,
  1378. removeNodes: removeNodes,
  1379. reparentNodes: reparentNodes,
  1380. noChange: noChange,
  1381. nothing: nothing,
  1382. AttributeCommitter: AttributeCommitter,
  1383. AttributePart: AttributePart,
  1384. BooleanAttributePart: BooleanAttributePart,
  1385. EventPart: EventPart,
  1386. isIterable: isIterable,
  1387. isPrimitive: isPrimitive,
  1388. NodePart: NodePart,
  1389. PropertyCommitter: PropertyCommitter,
  1390. PropertyPart: PropertyPart,
  1391. get sanitizerFactory () { return sanitizerFactory; },
  1392. setSanitizerFactory: setSanitizerFactory,
  1393. parts: parts,
  1394. render: render,
  1395. templateCaches: templateCaches,
  1396. templateFactory: templateFactory,
  1397. TemplateInstance: TemplateInstance,
  1398. SVGTemplateResult: SVGTemplateResult,
  1399. TemplateResult: TemplateResult,
  1400. createMarker: createMarker,
  1401. isTemplatePartActive: isTemplatePartActive,
  1402. Template: Template
  1403. });
  1404. /**
  1405. * @license
  1406. * Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
  1407. * This code may only be used under the BSD style license found at
  1408. * http://polymer.github.io/LICENSE.txt
  1409. * The complete set of authors may be found at
  1410. * http://polymer.github.io/AUTHORS.txt
  1411. * The complete set of contributors may be found at
  1412. * http://polymer.github.io/CONTRIBUTORS.txt
  1413. * Code distributed by Google as part of the polymer project is also
  1414. * subject to an additional IP rights grant found at
  1415. * http://polymer.github.io/PATENTS.txt
  1416. */
  1417. var __asyncValues = function (o) {
  1418. if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
  1419. var m = o[Symbol.asyncIterator], i;
  1420. return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
  1421. function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
  1422. function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
  1423. };
  1424. /**
  1425. * A directive that renders the items of an async iterable[1], appending new
  1426. * values after previous values, similar to the built-in support for iterables.
  1427. *
  1428. * Async iterables are objects with a [Symbol.asyncIterator] method, which
  1429. * returns an iterator who's `next()` method returns a Promise. When a new
  1430. * value is available, the Promise resolves and the value is appended to the
  1431. * Part controlled by the directive. If another value other than this
  1432. * directive has been set on the Part, the iterable will no longer be listened
  1433. * to and new values won't be written to the Part.
  1434. *
  1435. * [1]: https://github.com/tc39/proposal-async-iteration
  1436. *
  1437. * @param value An async iterable
  1438. * @param mapper An optional function that maps from (value, index) to another
  1439. * value. Useful for generating templates for each item in the iterable.
  1440. */
  1441. const asyncAppend = directive((value, mapper) => async (part) => {
  1442. var e_1, _a;
  1443. if (!(part instanceof NodePart)) {
  1444. throw new Error('asyncAppend can only be used in text bindings');
  1445. }
  1446. // If we've already set up this particular iterable, we don't need
  1447. // to do anything.
  1448. if (value === part.value) {
  1449. return;
  1450. }
  1451. part.value = value;
  1452. // We keep track of item Parts across iterations, so that we can
  1453. // share marker nodes between consecutive Parts.
  1454. let itemPart;
  1455. let i = 0;
  1456. try {
  1457. for (var value_1 = __asyncValues(value), value_1_1; value_1_1 = await value_1.next(), !value_1_1.done;) {
  1458. let v = value_1_1.value;
  1459. // Check to make sure that value is the still the current value of
  1460. // the part, and if not bail because a new value owns this part
  1461. if (part.value !== value) {
  1462. break;
  1463. }
  1464. // When we get the first value, clear the part. This lets the
  1465. // previous value display until we can replace it.
  1466. if (i === 0) {
  1467. part.clear();
  1468. }
  1469. // As a convenience, because functional-programming-style
  1470. // transforms of iterables and async iterables requires a library,
  1471. // we accept a mapper function. This is especially convenient for
  1472. // rendering a template for each item.
  1473. if (mapper !== undefined) {
  1474. // This is safe because T must otherwise be treated as unknown by
  1475. // the rest of the system.
  1476. v = mapper(v, i);
  1477. }
  1478. // Like with sync iterables, each item induces a Part, so we need
  1479. // to keep track of start and end nodes for the Part.
  1480. // Note: Because these Parts are not updatable like with a sync
  1481. // iterable (if we render a new value, we always clear), it may
  1482. // be possible to optimize away the Parts and just re-use the
  1483. // Part.setValue() logic.
  1484. let itemStartNode = part.startNode;
  1485. // Check to see if we have a previous item and Part
  1486. if (itemPart !== undefined) {
  1487. // Create a new node to separate the previous and next Parts
  1488. itemStartNode = createMarker();
  1489. // itemPart is currently the Part for the previous item. Set
  1490. // it's endNode to the node we'll use for the next Part's
  1491. // startNode.
  1492. itemPart.endNode = itemStartNode;
  1493. part.endNode.parentNode.insertBefore(itemStartNode, part.endNode);
  1494. }
  1495. itemPart = new NodePart(part.options, part.templatePart);
  1496. itemPart.insertAfterNode(itemStartNode);
  1497. itemPart.setValue(v);
  1498. itemPart.commit();
  1499. i++;
  1500. }
  1501. }
  1502. catch (e_1_1) { e_1 = { error: e_1_1 }; }
  1503. finally {
  1504. try {
  1505. if (value_1_1 && !value_1_1.done && (_a = value_1.return)) await _a.call(value_1);
  1506. }
  1507. finally { if (e_1) throw e_1.error; }
  1508. }
  1509. });
  1510. /**
  1511. * @license
  1512. * Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
  1513. * This code may only be used under the BSD style license found at
  1514. * http://polymer.github.io/LICENSE.txt
  1515. * The complete set of authors may be found at
  1516. * http://polymer.github.io/AUTHORS.txt
  1517. * The complete set of contributors may be found at
  1518. * http://polymer.github.io/CONTRIBUTORS.txt
  1519. * Code distributed by Google as part of the polymer project is also
  1520. * subject to an additional IP rights grant found at
  1521. * http://polymer.github.io/PATENTS.txt
  1522. */
  1523. var __asyncValues$1 = function (o) {
  1524. if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
  1525. var m = o[Symbol.asyncIterator], i;
  1526. return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
  1527. function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
  1528. function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
  1529. };
  1530. /**
  1531. * A directive that renders the items of an async iterable[1], replacing
  1532. * previous values with new values, so that only one value is ever rendered
  1533. * at a time.
  1534. *
  1535. * Async iterables are objects with a [Symbol.asyncIterator] method, which
  1536. * returns an iterator who's `next()` method returns a Promise. When a new
  1537. * value is available, the Promise resolves and the value is rendered to the
  1538. * Part controlled by the directive. If another value other than this
  1539. * directive has been set on the Part, the iterable will no longer be listened
  1540. * to and new values won't be written to the Part.
  1541. *
  1542. * [1]: https://github.com/tc39/proposal-async-iteration
  1543. *
  1544. * @param value An async iterable
  1545. * @param mapper An optional function that maps from (value, index) to another
  1546. * value. Useful for generating templates for each item in the iterable.
  1547. */
  1548. const asyncReplace = directive((value, mapper) => async (part) => {
  1549. var e_1, _a;
  1550. if (!(part instanceof NodePart)) {
  1551. throw new Error('asyncReplace can only be used in text bindings');
  1552. }
  1553. // If we've already set up this particular iterable, we don't need
  1554. // to do anything.
  1555. if (value === part.value) {
  1556. return;
  1557. }
  1558. // We nest a new part to keep track of previous item values separately
  1559. // of the iterable as a value itself.
  1560. const itemPart = new NodePart(part.options, part.templatePart);
  1561. part.value = value;
  1562. let i = 0;
  1563. try {
  1564. for (var value_1 = __asyncValues$1(value), value_1_1; value_1_1 = await value_1.next(), !value_1_1.done;) {
  1565. let v = value_1_1.value;
  1566. // Check to make sure that value is the still the current value of
  1567. // the part, and if not bail because a new value owns this part
  1568. if (part.value !== value) {
  1569. break;
  1570. }
  1571. // When we get the first value, clear the part. This let's the
  1572. // previous value display until we can replace it.
  1573. if (i === 0) {
  1574. part.clear();
  1575. itemPart.appendIntoPart(part);
  1576. }
  1577. // As a convenience, because functional-programming-style
  1578. // transforms of iterables and async iterables requires a library,
  1579. // we accept a mapper function. This is especially convenient for
  1580. // rendering a template for each item.
  1581. if (mapper !== undefined) {
  1582. // This is safe because T must otherwise be treated as unknown by
  1583. // the rest of the system.
  1584. v = mapper(v, i);
  1585. }
  1586. itemPart.setValue(v);
  1587. itemPart.commit();
  1588. i++;
  1589. }
  1590. }
  1591. catch (e_1_1) { e_1 = { error: e_1_1 }; }
  1592. finally {
  1593. try {
  1594. if (value_1_1 && !value_1_1.done && (_a = value_1.return)) await _a.call(value_1);
  1595. }
  1596. finally { if (e_1) throw e_1.error; }
  1597. }
  1598. });
  1599. /**
  1600. * @license
  1601. * Copyright (c) 2018 The Polymer Project Authors. All rights reserved.
  1602. * This code may only be used under the BSD style license found at
  1603. * http://polymer.github.io/LICENSE.txt
  1604. * The complete set of authors may be found at
  1605. * http://polymer.github.io/AUTHORS.txt
  1606. * The complete set of contributors may be found at
  1607. * http://polymer.github.io/CONTRIBUTORS.txt
  1608. * Code distributed by Google as part of the polymer project is also
  1609. * subject to an additional IP rights grant found at
  1610. * http://polymer.github.io/PATENTS.txt
  1611. */
  1612. const templateCaches$1 = new WeakMap();
  1613. /**
  1614. * Enables fast switching between multiple templates by caching the DOM nodes
  1615. * and TemplateInstances produced by the templates.
  1616. *
  1617. * Example:
  1618. *
  1619. * ```
  1620. * let checked = false;
  1621. *
  1622. * html`
  1623. * ${cache(checked ? html`input is checked` : html`input is not checked`)}
  1624. * `
  1625. * ```
  1626. */
  1627. const cache = directive((value) => (part) => {
  1628. if (!(part instanceof NodePart)) {
  1629. throw new Error('cache can only be used in text bindings');
  1630. }
  1631. let templateCache = templateCaches$1.get(part);
  1632. if (templateCache === undefined) {
  1633. templateCache = new WeakMap();
  1634. templateCaches$1.set(part, templateCache);
  1635. }
  1636. const previousValue = part.value;
  1637. // First, can we update the current TemplateInstance, or do we need to move
  1638. // the current nodes into the cache?
  1639. if (previousValue instanceof TemplateInstance) {
  1640. if (value instanceof TemplateResult &&
  1641. previousValue.template === part.options.templateFactory(value)) {
  1642. // Same Template, just trigger an update of the TemplateInstance
  1643. part.setValue(value);
  1644. return;
  1645. }
  1646. else {
  1647. // Not the same Template, move the nodes from the DOM into the cache.
  1648. let cachedTemplate = templateCache.get(previousValue.template);
  1649. if (cachedTemplate === undefined) {
  1650. cachedTemplate = {
  1651. instance: previousValue,
  1652. nodes: document.createDocumentFragment(),
  1653. };
  1654. templateCache.set(previousValue.template, cachedTemplate);
  1655. }
  1656. reparentNodes(cachedTemplate.nodes, part.startNode.nextSibling, part.endNode);
  1657. }
  1658. }
  1659. // Next, can we reuse nodes from the cache?
  1660. if (value instanceof TemplateResult) {
  1661. const template = part.options.templateFactory(value);
  1662. const cachedTemplate = templateCache.get(template);
  1663. if (cachedTemplate !== undefined) {
  1664. // Move nodes out of cache
  1665. part.setValue(cachedTemplate.nodes);
  1666. part.commit();
  1667. // Set the Part value to the TemplateInstance so it'll update it.
  1668. part.value = cachedTemplate.instance;
  1669. }
  1670. }
  1671. part.setValue(value);
  1672. });
  1673. /**
  1674. * @license
  1675. * Copyright (c) 2018 The Polymer Project Authors. All rights reserved.
  1676. * This code may only be used under the BSD style license found at
  1677. * http://polymer.github.io/LICENSE.txt
  1678. * The complete set of authors may be found at
  1679. * http://polymer.github.io/AUTHORS.txt
  1680. * The complete set of contributors may be found at
  1681. * http://polymer.github.io/CONTRIBUTORS.txt
  1682. * Code distributed by Google as part of the polymer project is also
  1683. * subject to an additional IP rights grant found at
  1684. * http://polymer.github.io/PATENTS.txt
  1685. */
  1686. /**
  1687. * Stores the ClassInfo object applied to a given AttributePart.
  1688. * Used to unset existing values when a new ClassInfo object is applied.
  1689. */
  1690. const previousClassesCache = new WeakMap();
  1691. /**
  1692. * A directive that applies CSS classes. This must be used in the `class`
  1693. * attribute and must be the only part used in the attribute. It takes each
  1694. * property in the `classInfo` argument and adds the property name to the
  1695. * element's `classList` if the property value is truthy; if the property value
  1696. * is falsey, the property name is removed from the element's `classList`. For
  1697. * example
  1698. * `{foo: bar}` applies the class `foo` if the value of `bar` is truthy.
  1699. * @param classInfo {ClassInfo}
  1700. */
  1701. const classMap = directive((classInfo) => (part) => {
  1702. if (!(part instanceof AttributePart) || (part instanceof PropertyPart) ||
  1703. part.committer.name !== 'class' || part.committer.parts.length > 1) {
  1704. throw new Error('The `classMap` directive must be used in the `class` attribute ' +
  1705. 'and must be the only part in the attribute.');
  1706. }
  1707. const { committer } = part;
  1708. const { element } = committer;
  1709. let previousClasses = previousClassesCache.get(part);
  1710. if (previousClasses === undefined) {
  1711. // Write static classes once
  1712. element.className = committer.strings.join(' ');
  1713. previousClassesCache.set(part, previousClasses = new Set());
  1714. }
  1715. const { classList } = element;
  1716. // Remove old classes that no longer apply
  1717. // We use forEach() instead of for-of so that re don't require down-level
  1718. // iteration.
  1719. previousClasses.forEach((name) => {
  1720. if (!(name in classInfo)) {
  1721. classList.remove(name);
  1722. previousClasses.delete(name);
  1723. }
  1724. });
  1725. // Add or remove classes based on their classMap value
  1726. for (const name in classInfo) {
  1727. const value = classInfo[name];
  1728. // We explicitly want a loose truthy check of `value` because it seems more
  1729. // convenient that '' and 0 are skipped.
  1730. if (value != previousClasses.has(name)) {
  1731. if (value) {
  1732. classList.add(name);
  1733. previousClasses.add(name);
  1734. }
  1735. else {
  1736. classList.remove(name);
  1737. previousClasses.delete(name);
  1738. }
  1739. }
  1740. }
  1741. });
  1742. /**
  1743. * @license
  1744. * Copyright (c) 2018 The Polymer Project Authors. All rights reserved.
  1745. * This code may only be used under the BSD style license found at
  1746. * http://polymer.github.io/LICENSE.txt
  1747. * The complete set of authors may be found at
  1748. * http://polymer.github.io/AUTHORS.txt
  1749. * The complete set of contributors may be found at
  1750. * http://polymer.github.io/CONTRIBUTORS.txt
  1751. * Code distributed by Google as part of the polymer project is also
  1752. * subject to an additional IP rights grant found at
  1753. * http://polymer.github.io/PATENTS.txt
  1754. */
  1755. const previousValues = new WeakMap();
  1756. /**
  1757. * Prevents re-render of a template function until a single value or an array of
  1758. * values changes.
  1759. *
  1760. * Example:
  1761. *
  1762. * ```js
  1763. * html`
  1764. * <div>
  1765. * ${guard([user.id, company.id], () => html`...`)}
  1766. * </div>
  1767. * ```
  1768. *
  1769. * In this case, the template only renders if either `user.id` or `company.id`
  1770. * changes.
  1771. *
  1772. * guard() is useful with immutable data patterns, by preventing expensive work
  1773. * until data updates.
  1774. *
  1775. * Example:
  1776. *
  1777. * ```js
  1778. * html`
  1779. * <div>
  1780. * ${guard([immutableItems], () => immutableItems.map(i => html`${i}`))}
  1781. * </div>
  1782. * ```
  1783. *
  1784. * In this case, items are mapped over only when the array reference changes.
  1785. *
  1786. * @param value the value to check before re-rendering
  1787. * @param f the template function
  1788. */
  1789. const guard = directive((value, f) => (part) => {
  1790. const previousValue = previousValues.get(part);
  1791. if (Array.isArray(value)) {
  1792. // Dirty-check arrays by item
  1793. if (Array.isArray(previousValue) &&
  1794. previousValue.length === value.length &&
  1795. value.every((v, i) => v === previousValue[i])) {
  1796. return;
  1797. }
  1798. }
  1799. else if (previousValue === value &&
  1800. (value !== undefined || previousValues.has(part))) {
  1801. // Dirty-check non-arrays by identity
  1802. return;
  1803. }
  1804. part.setValue(f());
  1805. // Copy the value if it's an array so that if it's mutated we don't forget
  1806. // what the previous values were.
  1807. previousValues.set(part, Array.isArray(value) ? Array.from(value) : value);
  1808. });
  1809. /**
  1810. * @license
  1811. * Copyright (c) 2018 The Polymer Project Authors. All rights reserved.
  1812. * This code may only be used under the BSD style license found at
  1813. * http://polymer.github.io/LICENSE.txt
  1814. * The complete set of authors may be found at
  1815. * http://polymer.github.io/AUTHORS.txt
  1816. * The complete set of contributors may be found at
  1817. * http://polymer.github.io/CONTRIBUTORS.txt
  1818. * Code distributed by Google as part of the polymer project is also
  1819. * subject to an additional IP rights grant found at
  1820. * http://polymer.github.io/PATENTS.txt
  1821. */
  1822. /**
  1823. * For AttributeParts, sets the attribute if the value is defined and removes
  1824. * the attribute if the value is undefined.
  1825. *
  1826. * For other part types, this directive is a no-op.
  1827. */
  1828. const ifDefined = directive((value) => (part) => {
  1829. if (value === undefined && part instanceof AttributePart) {
  1830. if (value !== part.value) {
  1831. const name = part.committer.name;
  1832. part.committer.element.removeAttribute(name);
  1833. }
  1834. }
  1835. else {
  1836. part.setValue(value);
  1837. }
  1838. });
  1839. /**
  1840. * @license
  1841. * Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
  1842. * This code may only be used under the BSD style license found at
  1843. * http://polymer.github.io/LICENSE.txt
  1844. * The complete set of authors may be found at
  1845. * http://polymer.github.io/AUTHORS.txt
  1846. * The complete set of contributors may be found at
  1847. * http://polymer.github.io/CONTRIBUTORS.txt
  1848. * Code distributed by Google as part of the polymer project is also
  1849. * subject to an additional IP rights grant found at
  1850. * http://polymer.github.io/PATENTS.txt
  1851. */
  1852. // Helper functions for manipulating parts
  1853. // TODO(kschaaf): Refactor into Part API?
  1854. const createAndInsertPart = (containerPart, beforePart) => {
  1855. const container = containerPart.startNode.parentNode;
  1856. const beforeNode = beforePart == null ? containerPart.endNode : beforePart.startNode;
  1857. const startNode = container.insertBefore(createMarker(), beforeNode);
  1858. container.insertBefore(createMarker(), beforeNode);
  1859. const newPart = new NodePart(containerPart.options, undefined);
  1860. newPart.insertAfterNode(startNode);
  1861. return newPart;
  1862. };
  1863. const updatePart = (part, value) => {
  1864. part.setValue(value);
  1865. part.commit();
  1866. return part;
  1867. };
  1868. const insertPartBefore = (containerPart, part, ref) => {
  1869. const container = containerPart.startNode.parentNode;
  1870. const beforeNode = ref ? ref.startNode : containerPart.endNode;
  1871. const endNode = part.endNode.nextSibling;
  1872. if (endNode !== beforeNode) {
  1873. reparentNodes(container, part.startNode, endNode, beforeNode);
  1874. }
  1875. };
  1876. const removePart = (part) => {
  1877. removeNodes(part.startNode.parentNode, part.startNode, part.endNode.nextSibling);
  1878. };
  1879. // Helper for generating a map of array item to its index over a subset
  1880. // of an array (used to lazily generate `newKeyToIndexMap` and
  1881. // `oldKeyToIndexMap`)
  1882. const generateMap = (list, start, end) => {
  1883. const map = new Map();
  1884. for (let i = start; i <= end; i++) {
  1885. map.set(list[i], i);
  1886. }
  1887. return map;
  1888. };
  1889. // Stores previous ordered list of parts and map of key to index
  1890. const partListCache = new WeakMap();
  1891. const keyListCache = new WeakMap();
  1892. /**
  1893. * A directive that repeats a series of values (usually `TemplateResults`)
  1894. * generated from an iterable, and updates those items efficiently when the
  1895. * iterable changes based on user-provided `keys` associated with each item.
  1896. *
  1897. * Note that if a `keyFn` is provided, strict key-to-DOM mapping is maintained,
  1898. * meaning previous DOM for a given key is moved into the new position if
  1899. * needed, and DOM will never be reused with values for different keys (new DOM
  1900. * will always be created for new keys). This is generally the most efficient
  1901. * way to use `repeat` since it performs minimum unnecessary work for insertions
  1902. * and removals.
  1903. *
  1904. * IMPORTANT: If providing a `keyFn`, keys *must* be unique for all items in a
  1905. * given call to `repeat`. The behavior when two or more items have the same key
  1906. * is undefined.
  1907. *
  1908. * If no `keyFn` is provided, this directive will perform similar to mapping
  1909. * items to values, and DOM will be reused against potentially different items.
  1910. */
  1911. const repeat = directive((items, keyFnOrTemplate, template) => {
  1912. let keyFn;
  1913. if (template === undefined) {
  1914. template = keyFnOrTemplate;
  1915. }
  1916. else if (keyFnOrTemplate !== undefined) {
  1917. keyFn = keyFnOrTemplate;
  1918. }
  1919. return (containerPart) => {
  1920. if (!(containerPart instanceof NodePart)) {
  1921. throw new Error('repeat can only be used in text bindings');
  1922. }
  1923. // Old part & key lists are retrieved from the last update
  1924. // (associated with the part for this instance of the directive)
  1925. const oldParts = partListCache.get(containerPart) || [];
  1926. const oldKeys = keyListCache.get(containerPart) || [];
  1927. // New part list will be built up as we go (either reused from
  1928. // old parts or created for new keys in this update). This is
  1929. // saved in the above cache at the end of the update.
  1930. const newParts = [];
  1931. // New value list is eagerly generated from items along with a
  1932. // parallel array indicating its key.
  1933. const newValues = [];
  1934. const newKeys = [];
  1935. let index = 0;
  1936. for (const item of items) {
  1937. newKeys[index] = keyFn ? keyFn(item, index) : index;
  1938. newValues[index] = template(item, index);
  1939. index++;
  1940. }
  1941. // Maps from key to index for current and previous update; these
  1942. // are generated lazily only when needed as a performance
  1943. // optimization, since they are only required for multiple
  1944. // non-contiguous changes in the list, which are less common.
  1945. let newKeyToIndexMap;
  1946. let oldKeyToIndexMap;
  1947. // Head and tail pointers to old parts and new values
  1948. let oldHead = 0;
  1949. let oldTail = oldParts.length - 1;
  1950. let newHead = 0;
  1951. let newTail = newValues.length - 1;
  1952. // Overview of O(n) reconciliation algorithm (general approach
  1953. // based on ideas found in ivi, vue, snabbdom, etc.):
  1954. //
  1955. // * We start with the list of old parts and new values (and
  1956. // arrays of their respective keys), head/tail pointers into
  1957. // each, and we build up the new list of parts by updating
  1958. // (and when needed, moving) old parts or creating new ones.
  1959. // The initial scenario might look like this (for brevity of
  1960. // the diagrams, the numbers in the array reflect keys
  1961. // associated with the old parts or new values, although keys
  1962. // and parts/values are actually stored in parallel arrays
  1963. // indexed using the same head/tail pointers):
  1964. //
  1965. // oldHead v v oldTail
  1966. // oldKeys: [0, 1, 2, 3, 4, 5, 6]
  1967. // newParts: [ , , , , , , ]
  1968. // newKeys: [0, 2, 1, 4, 3, 7, 6] <- reflects the user's new
  1969. // item order
  1970. // newHead ^ ^ newTail
  1971. //
  1972. // * Iterate old & new lists from both sides, updating,
  1973. // swapping, or removing parts at the head/tail locations
  1974. // until neither head nor tail can move.
  1975. //
  1976. // * Example below: keys at head pointers match, so update old
  1977. // part 0 in-place (no need to move it) and record part 0 in
  1978. // the `newParts` list. The last thing we do is advance the
  1979. // `oldHead` and `newHead` pointers (will be reflected in the
  1980. // next diagram).
  1981. //
  1982. // oldHead v v oldTail
  1983. // oldKeys: [0, 1, 2, 3, 4, 5, 6]
  1984. // newParts: [0, , , , , , ] <- heads matched: update 0
  1985. // newKeys: [0, 2, 1, 4, 3, 7, 6] and advance both oldHead
  1986. // & newHead
  1987. // newHead ^ ^ newTail
  1988. //
  1989. // * Example below: head pointers don't match, but tail
  1990. // pointers do, so update part 6 in place (no need to move
  1991. // it), and record part 6 in the `newParts` list. Last,
  1992. // advance the `oldTail` and `oldHead` pointers.
  1993. //
  1994. // oldHead v v oldTail
  1995. // oldKeys: [0, 1, 2, 3, 4, 5, 6]
  1996. // newParts: [0, , , , , , 6] <- tails matched: update 6
  1997. // newKeys: [0, 2, 1, 4, 3, 7, 6] and advance both oldTail
  1998. // & newTail
  1999. // newHead ^ ^ newTail
  2000. //
  2001. // * If neither head nor tail match; next check if one of the
  2002. // old head/tail items was removed. We first need to generate
  2003. // the reverse map of new keys to index (`newKeyToIndexMap`),
  2004. // which is done once lazily as a performance optimization,
  2005. // since we only hit this case if multiple non-contiguous
  2006. // changes were made. Note that for contiguous removal
  2007. // anywhere in the list, the head and tails would advance
  2008. // from either end and pass each other before we get to this
  2009. // case and removals would be handled in the final while loop
  2010. // without needing to generate the map.
  2011. //
  2012. // * Example below: The key at `oldTail` was removed (no longer
  2013. // in the `newKeyToIndexMap`), so remove that part from the
  2014. // DOM and advance just the `oldTail` pointer.
  2015. //
  2016. // oldHead v v oldTail
  2017. // oldKeys: [0, 1, 2, 3, 4, 5, 6]
  2018. // newParts: [0, , , , , , 6] <- 5 not in new map: remove
  2019. // newKeys: [0, 2, 1, 4, 3, 7, 6] 5 and advance oldTail
  2020. // newHead ^ ^ newTail
  2021. //
  2022. // * Once head and tail cannot move, any mismatches are due to
  2023. // either new or moved items; if a new key is in the previous
  2024. // "old key to old index" map, move the old part to the new
  2025. // location, otherwise create and insert a new part. Note
  2026. // that when moving an old part we null its position in the
  2027. // oldParts array if it lies between the head and tail so we
  2028. // know to skip it when the pointers get there.
  2029. //
  2030. // * Example below: neither head nor tail match, and neither
  2031. // were removed; so find the `newHead` key in the
  2032. // `oldKeyToIndexMap`, and move that old part's DOM into the
  2033. // next head position (before `oldParts[oldHead]`). Last,
  2034. // null the part in the `oldPart` array since it was
  2035. // somewhere in the remaining oldParts still to be scanned
  2036. // (between the head and tail pointers) so that we know to
  2037. // skip that old part on future iterations.
  2038. //
  2039. // oldHead v v oldTail
  2040. // oldKeys: [0, 1, -, 3, 4, 5, 6]
  2041. // newParts: [0, 2, , , , , 6] <- stuck: update & move 2
  2042. // newKeys: [0, 2, 1, 4, 3, 7, 6] into place and advance
  2043. // newHead
  2044. // newHead ^ ^ newTail
  2045. //
  2046. // * Note that for moves/insertions like the one above, a part
  2047. // inserted at the head pointer is inserted before the
  2048. // current `oldParts[oldHead]`, and a part inserted at the
  2049. // tail pointer is inserted before `newParts[newTail+1]`. The
  2050. // seeming asymmetry lies in the fact that new parts are
  2051. // moved into place outside in, so to the right of the head
  2052. // pointer are old parts, and to the right of the tail
  2053. // pointer are new parts.
  2054. //
  2055. // * We always restart back from the top of the algorithm,
  2056. // allowing matching and simple updates in place to
  2057. // continue...
  2058. //
  2059. // * Example below: the head pointers once again match, so
  2060. // simply update part 1 and record it in the `newParts`
  2061. // array. Last, advance both head pointers.
  2062. //
  2063. // oldHead v v oldTail
  2064. // oldKeys: [0, 1, -, 3, 4, 5, 6]
  2065. // newParts: [0, 2, 1, , , , 6] <- heads matched: update 1
  2066. // newKeys: [0, 2, 1, 4, 3, 7, 6] and advance both oldHead
  2067. // & newHead
  2068. // newHead ^ ^ newTail
  2069. //
  2070. // * As mentioned above, items that were moved as a result of
  2071. // being stuck (the final else clause in the code below) are
  2072. // marked with null, so we always advance old pointers over
  2073. // these so we're comparing the next actual old value on
  2074. // either end.
  2075. //
  2076. // * Example below: `oldHead` is null (already placed in
  2077. // newParts), so advance `oldHead`.
  2078. //
  2079. // oldHead v v oldTail
  2080. // oldKeys: [0, 1, -, 3, 4, 5, 6] <- old head already used:
  2081. // newParts: [0, 2, 1, , , , 6] advance oldHead
  2082. // newKeys: [0, 2, 1, 4, 3, 7, 6]
  2083. // newHead ^ ^ newTail
  2084. //
  2085. // * Note it's not critical to mark old parts as null when they
  2086. // are moved from head to tail or tail to head, since they
  2087. // will be outside the pointer range and never visited again.
  2088. //
  2089. // * Example below: Here the old tail key matches the new head
  2090. // key, so the part at the `oldTail` position and move its
  2091. // DOM to the new head position (before `oldParts[oldHead]`).
  2092. // Last, advance `oldTail` and `newHead` pointers.
  2093. //
  2094. // oldHead v v oldTail
  2095. // oldKeys: [0, 1, -, 3, 4, 5, 6]
  2096. // newParts: [0, 2, 1, 4, , , 6] <- old tail matches new
  2097. // newKeys: [0, 2, 1, 4, 3, 7, 6] head: update & move 4,
  2098. // advance oldTail & newHead
  2099. // newHead ^ ^ newTail
  2100. //
  2101. // * Example below: Old and new head keys match, so update the
  2102. // old head part in place, and advance the `oldHead` and
  2103. // `newHead` pointers.
  2104. //
  2105. // oldHead v oldTail
  2106. // oldKeys: [0, 1, -, 3, 4, 5, 6]
  2107. // newParts: [0, 2, 1, 4, 3, ,6] <- heads match: update 3
  2108. // newKeys: [0, 2, 1, 4, 3, 7, 6] and advance oldHead &
  2109. // newHead
  2110. // newHead ^ ^ newTail
  2111. //
  2112. // * Once the new or old pointers move past each other then all
  2113. // we have left is additions (if old list exhausted) or
  2114. // removals (if new list exhausted). Those are handled in the
  2115. // final while loops at the end.
  2116. //
  2117. // * Example below: `oldHead` exceeded `oldTail`, so we're done
  2118. // with the main loop. Create the remaining part and insert
  2119. // it at the new head position, and the update is complete.
  2120. //
  2121. // (oldHead > oldTail)
  2122. // oldKeys: [0, 1, -, 3, 4, 5, 6]
  2123. // newParts: [0, 2, 1, 4, 3, 7 ,6] <- create and insert 7
  2124. // newKeys: [0, 2, 1, 4, 3, 7, 6]
  2125. // newHead ^ newTail
  2126. //
  2127. // * Note that the order of the if/else clauses is not
  2128. // important to the algorithm, as long as the null checks
  2129. // come first (to ensure we're always working on valid old
  2130. // parts) and that the final else clause comes last (since
  2131. // that's where the expensive moves occur). The order of
  2132. // remaining clauses is is just a simple guess at which cases
  2133. // will be most common.
  2134. //
  2135. // * TODO(kschaaf) Note, we could calculate the longest
  2136. // increasing subsequence (LIS) of old items in new position,
  2137. // and only move those not in the LIS set. However that costs
  2138. // O(nlogn) time and adds a bit more code, and only helps
  2139. // make rare types of mutations require fewer moves. The
  2140. // above handles removes, adds, reversal, swaps, and single
  2141. // moves of contiguous items in linear time, in the minimum
  2142. // number of moves. As the number of multiple moves where LIS
  2143. // might help approaches a random shuffle, the LIS
  2144. // optimization becomes less helpful, so it seems not worth
  2145. // the code at this point. Could reconsider if a compelling
  2146. // case arises.
  2147. while (oldHead <= oldTail && newHead <= newTail) {
  2148. if (oldParts[oldHead] === null) {
  2149. // `null` means old part at head has already been used
  2150. // below; skip
  2151. oldHead++;
  2152. }
  2153. else if (oldParts[oldTail] === null) {
  2154. // `null` means old part at tail has already been used
  2155. // below; skip
  2156. oldTail--;
  2157. }
  2158. else if (oldKeys[oldHead] === newKeys[newHead]) {
  2159. // Old head matches new head; update in place
  2160. newParts[newHead] =
  2161. updatePart(oldParts[oldHead], newValues[newHead]);
  2162. oldHead++;
  2163. newHead++;
  2164. }
  2165. else if (oldKeys[oldTail] === newKeys[newTail]) {
  2166. // Old tail matches new tail; update in place
  2167. newParts[newTail] =
  2168. updatePart(oldParts[oldTail], newValues[newTail]);
  2169. oldTail--;
  2170. newTail--;
  2171. }
  2172. else if (oldKeys[oldHead] === newKeys[newTail]) {
  2173. // Old head matches new tail; update and move to new tail
  2174. newParts[newTail] =
  2175. updatePart(oldParts[oldHead], newValues[newTail]);
  2176. insertPartBefore(containerPart, oldParts[oldHead], newParts[newTail + 1]);
  2177. oldHead++;
  2178. newTail--;
  2179. }
  2180. else if (oldKeys[oldTail] === newKeys[newHead]) {
  2181. // Old tail matches new head; update and move to new head
  2182. newParts[newHead] =
  2183. updatePart(oldParts[oldTail], newValues[newHead]);
  2184. insertPartBefore(containerPart, oldParts[oldTail], oldParts[oldHead]);
  2185. oldTail--;
  2186. newHead++;
  2187. }
  2188. else {
  2189. if (newKeyToIndexMap === undefined) {
  2190. // Lazily generate key-to-index maps, used for removals &
  2191. // moves below
  2192. newKeyToIndexMap = generateMap(newKeys, newHead, newTail);
  2193. oldKeyToIndexMap = generateMap(oldKeys, oldHead, oldTail);
  2194. }
  2195. if (!newKeyToIndexMap.has(oldKeys[oldHead])) {
  2196. // Old head is no longer in new list; remove
  2197. removePart(oldParts[oldHead]);
  2198. oldHead++;
  2199. }
  2200. else if (!newKeyToIndexMap.has(oldKeys[oldTail])) {
  2201. // Old tail is no longer in new list; remove
  2202. removePart(oldParts[oldTail]);
  2203. oldTail--;
  2204. }
  2205. else {
  2206. // Any mismatches at this point are due to additions or
  2207. // moves; see if we have an old part we can reuse and move
  2208. // into place
  2209. const oldIndex = oldKeyToIndexMap.get(newKeys[newHead]);
  2210. const oldPart = oldIndex !== undefined ? oldParts[oldIndex] : null;
  2211. if (oldPart === null) {
  2212. // No old part for this value; create a new one and
  2213. // insert it
  2214. const newPart = createAndInsertPart(containerPart, oldParts[oldHead]);
  2215. updatePart(newPart, newValues[newHead]);
  2216. newParts[newHead] = newPart;
  2217. }
  2218. else {
  2219. // Reuse old part
  2220. newParts[newHead] =
  2221. updatePart(oldPart, newValues[newHead]);
  2222. insertPartBefore(containerPart, oldPart, oldParts[oldHead]);
  2223. // This marks the old part as having been used, so that
  2224. // it will be skipped in the first two checks above
  2225. oldParts[oldIndex] = null;
  2226. }
  2227. newHead++;
  2228. }
  2229. }
  2230. }
  2231. // Add parts for any remaining new values
  2232. while (newHead <= newTail) {
  2233. // For all remaining additions, we insert before last new
  2234. // tail, since old pointers are no longer valid
  2235. const newPart = createAndInsertPart(containerPart, newParts[newTail + 1]);
  2236. updatePart(newPart, newValues[newHead]);
  2237. newParts[newHead++] = newPart;
  2238. }
  2239. // Remove any remaining unused old parts
  2240. while (oldHead <= oldTail) {
  2241. const oldPart = oldParts[oldHead++];
  2242. if (oldPart !== null) {
  2243. removePart(oldPart);
  2244. }
  2245. }
  2246. // Save order of new parts for next round
  2247. partListCache.set(containerPart, newParts);
  2248. keyListCache.set(containerPart, newKeys);
  2249. };
  2250. });
  2251. /**
  2252. * @license
  2253. * Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
  2254. * This code may only be used under the BSD style license found at
  2255. * http://polymer.github.io/LICENSE.txt
  2256. * The complete set of authors may be found at
  2257. * http://polymer.github.io/AUTHORS.txt
  2258. * The complete set of contributors may be found at
  2259. * http://polymer.github.io/CONTRIBUTORS.txt
  2260. * Code distributed by Google as part of the polymer project is also
  2261. * subject to an additional IP rights grant found at
  2262. * http://polymer.github.io/PATENTS.txt
  2263. */
  2264. // For each part, remember the value that was last rendered to the part by the
  2265. // unsafeHTML directive, and the DocumentFragment that was last set as a value.
  2266. // The DocumentFragment is used as a unique key to check if the last value
  2267. // rendered to the part was with unsafeHTML. If not, we'll always re-render the
  2268. // value passed to unsafeHTML.
  2269. const previousValues$1 = new WeakMap();
  2270. /**
  2271. * Used to clone existing node instead of each time creating new one which is
  2272. * slower
  2273. */
  2274. const emptyTemplateNode$1 = document.createElement('template');
  2275. /**
  2276. * Renders the result as HTML, rather than text.
  2277. *
  2278. * Note, this is unsafe to use with any user-provided input that hasn't been
  2279. * sanitized or escaped, as it may lead to cross-site-scripting
  2280. * vulnerabilities.
  2281. */
  2282. const unsafeHTML = directive((value) => (part) => {
  2283. if (!(part instanceof NodePart)) {
  2284. throw new Error('unsafeHTML can only be used in text bindings');
  2285. }
  2286. const previousValue = previousValues$1.get(part);
  2287. if (previousValue !== undefined && isPrimitive(value) &&
  2288. value === previousValue.value && part.value === previousValue.fragment) {
  2289. return;
  2290. }
  2291. const template = emptyTemplateNode$1.cloneNode();
  2292. template.innerHTML = value; // innerHTML casts to string internally
  2293. const fragment = document.importNode(template.content, true);
  2294. part.setValue(fragment);
  2295. previousValues$1.set(part, { value, fragment });
  2296. });
  2297. /**
  2298. * @license
  2299. * Copyright (c) 2017 The Polymer Project Authors. All rights reserved.
  2300. * This code may only be used under the BSD style license found at
  2301. * http://polymer.github.io/LICENSE.txt
  2302. * The complete set of authors may be found at
  2303. * http://polymer.github.io/AUTHORS.txt
  2304. * The complete set of contributors may be found at
  2305. * http://polymer.github.io/CONTRIBUTORS.txt
  2306. * Code distributed by Google as part of the polymer project is also
  2307. * subject to an additional IP rights grant found at
  2308. * http://polymer.github.io/PATENTS.txt
  2309. */
  2310. const _state = new WeakMap();
  2311. // Effectively infinity, but a SMI.
  2312. const _infinity = 0x7fffffff;
  2313. /**
  2314. * Renders one of a series of values, including Promises, to a Part.
  2315. *
  2316. * Values are rendered in priority order, with the first argument having the
  2317. * highest priority and the last argument having the lowest priority. If a
  2318. * value is a Promise, low-priority values will be rendered until it resolves.
  2319. *
  2320. * The priority of values can be used to create placeholder content for async
  2321. * data. For example, a Promise with pending content can be the first,
  2322. * highest-priority, argument, and a non_promise loading indicator template can
  2323. * be used as the second, lower-priority, argument. The loading indicator will
  2324. * render immediately, and the primary content will render when the Promise
  2325. * resolves.
  2326. *
  2327. * Example:
  2328. *
  2329. * const content = fetch('./content.txt').then(r => r.text());
  2330. * html`${until(content, html`<span>Loading...</span>`)}`
  2331. */
  2332. const until = directive((...args) => (part) => {
  2333. let state = _state.get(part);
  2334. if (state === undefined) {
  2335. state = {
  2336. lastRenderedIndex: _infinity,
  2337. values: [],
  2338. };
  2339. _state.set(part, state);
  2340. }
  2341. const previousValues = state.values;
  2342. let previousLength = previousValues.length;
  2343. state.values = args;
  2344. for (let i = 0; i < args.length; i++) {
  2345. // If we've rendered a higher-priority value already, stop.
  2346. if (i > state.lastRenderedIndex) {
  2347. break;
  2348. }
  2349. const value = args[i];
  2350. // Render non-Promise values immediately
  2351. if (isPrimitive(value) ||
  2352. typeof value.then !== 'function') {
  2353. part.setValue(value);
  2354. state.lastRenderedIndex = i;
  2355. // Since a lower-priority value will never overwrite a higher-priority
  2356. // synchronous value, we can stop processing now.
  2357. break;
  2358. }
  2359. // If this is a Promise we've already handled, skip it.
  2360. if (i < previousLength && value === previousValues[i]) {
  2361. continue;
  2362. }
  2363. // We have a Promise that we haven't seen before, so priorities may have
  2364. // changed. Forget what we rendered before.
  2365. state.lastRenderedIndex = _infinity;
  2366. previousLength = 0;
  2367. Promise.resolve(value).then((resolvedValue) => {
  2368. const index = state.values.indexOf(value);
  2369. // If state.values doesn't contain the value, we've re-rendered without
  2370. // the value, so don't render it. Then, only render if the value is
  2371. // higher-priority than what's already been rendered.
  2372. if (index > -1 && index < state.lastRenderedIndex) {
  2373. state.lastRenderedIndex = index;
  2374. part.setValue(resolvedValue);
  2375. part.commit();
  2376. }
  2377. });
  2378. }
  2379. });
  2380. const detached = new WeakMap();
  2381. class Detach extends Directive {
  2382. constructor(ifFn) {
  2383. super();
  2384. this.ifFn = ifFn;
  2385. }
  2386. body(part) {
  2387. const detach = this.ifFn();
  2388. const element = part.committer.element;
  2389. if (detach) {
  2390. if (!detached.has(part)) {
  2391. const nextSibling = element.nextSibling;
  2392. detached.set(part, { element, nextSibling });
  2393. }
  2394. element.remove();
  2395. }
  2396. else {
  2397. const data = detached.get(part);
  2398. if (typeof data !== 'undefined' && data !== null) {
  2399. data.nextSibling.parentNode.insertBefore(data.element, data.nextSibling);
  2400. detached.delete(part);
  2401. }
  2402. }
  2403. }
  2404. }
  2405. const toRemove = [], toUpdate = [];
  2406. class StyleMap extends Directive {
  2407. constructor(styleInfo, detach = false) {
  2408. super();
  2409. this.previous = {};
  2410. this.style = styleInfo;
  2411. this.detach = detach;
  2412. }
  2413. setStyle(styleInfo) {
  2414. this.style = styleInfo;
  2415. }
  2416. setDetach(detach) {
  2417. this.detach = detach;
  2418. }
  2419. body(part) {
  2420. toRemove.length = 0;
  2421. toUpdate.length = 0;
  2422. // @ts-ignore
  2423. const element = part.committer.element;
  2424. const style = element.style;
  2425. let previous = this.previous;
  2426. for (const name in previous) {
  2427. if (this.style[name] === undefined) {
  2428. toRemove.push(name);
  2429. }
  2430. }
  2431. for (const name in this.style) {
  2432. const value = this.style[name];
  2433. const prev = previous[name];
  2434. if (prev !== undefined && prev === value) {
  2435. continue;
  2436. }
  2437. toUpdate.push(name);
  2438. }
  2439. if (toRemove.length || toUpdate.length) {
  2440. let parent, nextSibling;
  2441. if (this.detach) {
  2442. parent = element.parentNode;
  2443. if (parent) {
  2444. nextSibling = element.nextSibling;
  2445. element.remove();
  2446. }
  2447. }
  2448. for (const name of toRemove) {
  2449. style.removeProperty(name);
  2450. }
  2451. for (const name of toUpdate) {
  2452. const value = this.style[name];
  2453. if (!name.includes('-')) {
  2454. style[name] = value;
  2455. }
  2456. else {
  2457. style.setProperty(name, value);
  2458. }
  2459. }
  2460. if (this.detach && parent) {
  2461. parent.insertBefore(element, nextSibling);
  2462. }
  2463. this.previous = Object.assign({}, this.style);
  2464. }
  2465. }
  2466. }
  2467. class Action {
  2468. constructor() {
  2469. this.isAction = true;
  2470. }
  2471. }
  2472. Action.prototype.isAction = true;
  2473. const defaultOptions = {
  2474. element: document.createTextNode(''),
  2475. axis: 'xy',
  2476. threshold: 10,
  2477. onDown(data) { },
  2478. onMove(data) { },
  2479. onUp(data) { },
  2480. onWheel(data) { }
  2481. };
  2482. const pointerEventsExists = typeof PointerEvent !== 'undefined';
  2483. let id = 0;
  2484. class PointerAction extends Action {
  2485. constructor(element, data) {
  2486. super();
  2487. this.moving = '';
  2488. this.initialX = 0;
  2489. this.initialY = 0;
  2490. this.lastY = 0;
  2491. this.lastX = 0;
  2492. this.onPointerDown = this.onPointerDown.bind(this);
  2493. this.onPointerMove = this.onPointerMove.bind(this);
  2494. this.onPointerUp = this.onPointerUp.bind(this);
  2495. this.onWheel = this.onWheel.bind(this);
  2496. this.element = element;
  2497. this.id = ++id;
  2498. this.options = Object.assign(Object.assign({}, defaultOptions), data.pointerOptions);
  2499. if (pointerEventsExists) {
  2500. element.addEventListener('pointerdown', this.onPointerDown);
  2501. document.addEventListener('pointermove', this.onPointerMove);
  2502. document.addEventListener('pointerup', this.onPointerUp);
  2503. }
  2504. else {
  2505. element.addEventListener('touchstart', this.onPointerDown);
  2506. document.addEventListener('touchmove', this.onPointerMove, { passive: false });
  2507. document.addEventListener('touchend', this.onPointerUp);
  2508. document.addEventListener('touchcancel', this.onPointerUp);
  2509. element.addEventListener('mousedown', this.onPointerDown);
  2510. document.addEventListener('mousemove', this.onPointerMove, { passive: false });
  2511. document.addEventListener('mouseup', this.onPointerUp);
  2512. }
  2513. }
  2514. normalizeMouseWheelEvent(event) {
  2515. // @ts-ignore
  2516. let x = event.deltaX || 0;
  2517. // @ts-ignore
  2518. let y = event.deltaY || 0;
  2519. // @ts-ignore
  2520. let z = event.deltaZ || 0;
  2521. // @ts-ignore
  2522. const mode = event.deltaMode;
  2523. // @ts-ignore
  2524. const lineHeight = parseInt(getComputedStyle(event.target).getPropertyValue('line-height'));
  2525. let scale = 1;
  2526. switch (mode) {
  2527. case 1:
  2528. scale = lineHeight;
  2529. break;
  2530. case 2:
  2531. // @ts-ignore
  2532. scale = window.height;
  2533. break;
  2534. }
  2535. x *= scale;
  2536. y *= scale;
  2537. z *= scale;
  2538. return { x, y, z, event };
  2539. }
  2540. onWheel(event) {
  2541. const normalized = this.normalizeMouseWheelEvent(event);
  2542. this.options.onWheel(normalized);
  2543. }
  2544. normalizePointerEvent(event) {
  2545. let result = { x: 0, y: 0, pageX: 0, pageY: 0, clientX: 0, clientY: 0, screenX: 0, screenY: 0, event };
  2546. switch (event.type) {
  2547. case 'wheel':
  2548. const wheel = this.normalizeMouseWheelEvent(event);
  2549. result.x = wheel.x;
  2550. result.y = wheel.y;
  2551. result.pageX = result.x;
  2552. result.pageY = result.y;
  2553. result.screenX = result.x;
  2554. result.screenY = result.y;
  2555. result.clientX = result.x;
  2556. result.clientY = result.y;
  2557. break;
  2558. case 'touchstart':
  2559. case 'touchmove':
  2560. case 'touchend':
  2561. case 'touchcancel':
  2562. result.x = event.changedTouches[0].screenX;
  2563. result.y = event.changedTouches[0].screenY;
  2564. result.pageX = event.changedTouches[0].pageX;
  2565. result.pageY = event.changedTouches[0].pageY;
  2566. result.screenX = event.changedTouches[0].screenX;
  2567. result.screenY = event.changedTouches[0].screenY;
  2568. result.clientX = event.changedTouches[0].clientX;
  2569. result.clientY = event.changedTouches[0].clientY;
  2570. break;
  2571. default:
  2572. result.x = event.x;
  2573. result.y = event.y;
  2574. result.pageX = event.pageX;
  2575. result.pageY = event.pageY;
  2576. result.screenX = event.screenX;
  2577. result.screenY = event.screenY;
  2578. result.clientX = event.clientX;
  2579. result.clientY = event.clientY;
  2580. break;
  2581. }
  2582. return result;
  2583. }
  2584. onPointerDown(event) {
  2585. if (event.type === 'mousedown' && event.button !== 0)
  2586. return;
  2587. this.moving = 'xy';
  2588. const normalized = this.normalizePointerEvent(event);
  2589. this.lastX = normalized.x;
  2590. this.lastY = normalized.y;
  2591. this.initialX = normalized.x;
  2592. this.initialY = normalized.y;
  2593. this.options.onDown(normalized);
  2594. }
  2595. handleX(normalized) {
  2596. let movementX = normalized.x - this.lastX;
  2597. this.lastY = normalized.y;
  2598. this.lastX = normalized.x;
  2599. return movementX;
  2600. }
  2601. handleY(normalized) {
  2602. let movementY = normalized.y - this.lastY;
  2603. this.lastY = normalized.y;
  2604. this.lastX = normalized.x;
  2605. return movementY;
  2606. }
  2607. onPointerMove(event) {
  2608. if (this.moving === '' || (event.type === 'mousemove' && event.button !== 0))
  2609. return;
  2610. const normalized = this.normalizePointerEvent(event);
  2611. if (this.options.axis === 'x|y') {
  2612. let movementX = 0, movementY = 0;
  2613. if (this.moving === 'x' ||
  2614. (this.moving === 'xy' && Math.abs(normalized.x - this.initialX) > this.options.threshold)) {
  2615. this.moving = 'x';
  2616. movementX = this.handleX(normalized);
  2617. }
  2618. if (this.moving === 'y' ||
  2619. (this.moving === 'xy' && Math.abs(normalized.y - this.initialY) > this.options.threshold)) {
  2620. this.moving = 'y';
  2621. movementY = this.handleY(normalized);
  2622. }
  2623. this.options.onMove({
  2624. movementX,
  2625. movementY,
  2626. x: normalized.x,
  2627. y: normalized.y,
  2628. initialX: this.initialX,
  2629. initialY: this.initialY,
  2630. lastX: this.lastX,
  2631. lastY: this.lastY,
  2632. event
  2633. });
  2634. }
  2635. else if (this.options.axis === 'xy') {
  2636. let movementX = 0, movementY = 0;
  2637. if (Math.abs(normalized.x - this.initialX) > this.options.threshold) {
  2638. movementX = this.handleX(normalized);
  2639. }
  2640. if (Math.abs(normalized.y - this.initialY) > this.options.threshold) {
  2641. movementY = this.handleY(normalized);
  2642. }
  2643. this.options.onMove({
  2644. movementX,
  2645. movementY,
  2646. x: normalized.x,
  2647. y: normalized.y,
  2648. initialX: this.initialX,
  2649. initialY: this.initialY,
  2650. lastX: this.lastX,
  2651. lastY: this.lastY,
  2652. event
  2653. });
  2654. }
  2655. else if (this.options.axis === 'x') {
  2656. if (this.moving === 'x' ||
  2657. (this.moving === 'xy' && Math.abs(normalized.x - this.initialX) > this.options.threshold)) {
  2658. this.moving = 'x';
  2659. this.options.onMove({
  2660. movementX: this.handleX(normalized),
  2661. movementY: 0,
  2662. initialX: this.initialX,
  2663. initialY: this.initialY,
  2664. lastX: this.lastX,
  2665. lastY: this.lastY,
  2666. event
  2667. });
  2668. }
  2669. }
  2670. else if (this.options.axis === 'y') {
  2671. let movementY = 0;
  2672. if (this.moving === 'y' ||
  2673. (this.moving === 'xy' && Math.abs(normalized.y - this.initialY) > this.options.threshold)) {
  2674. this.moving = 'y';
  2675. movementY = this.handleY(normalized);
  2676. }
  2677. this.options.onMove({
  2678. movementX: 0,
  2679. movementY,
  2680. x: normalized.x,
  2681. y: normalized.y,
  2682. initialX: this.initialX,
  2683. initialY: this.initialY,
  2684. lastX: this.lastX,
  2685. lastY: this.lastY,
  2686. event
  2687. });
  2688. }
  2689. }
  2690. onPointerUp(event) {
  2691. this.moving = '';
  2692. const normalized = this.normalizePointerEvent(event);
  2693. this.options.onUp({
  2694. movementX: 0,
  2695. movementY: 0,
  2696. x: normalized.x,
  2697. y: normalized.y,
  2698. initialX: this.initialX,
  2699. initialY: this.initialY,
  2700. lastX: this.lastX,
  2701. lastY: this.lastY,
  2702. event
  2703. });
  2704. this.lastY = 0;
  2705. this.lastX = 0;
  2706. }
  2707. destroy(element) {
  2708. if (pointerEventsExists) {
  2709. element.removeEventListener('pointerdown', this.onPointerDown);
  2710. document.removeEventListener('pointermove', this.onPointerMove);
  2711. document.removeEventListener('pointerup', this.onPointerUp);
  2712. }
  2713. else {
  2714. element.removeEventListener('mousedown', this.onPointerDown);
  2715. document.removeEventListener('mousemove', this.onPointerMove);
  2716. document.removeEventListener('mouseup', this.onPointerUp);
  2717. element.removeEventListener('touchstart', this.onPointerDown);
  2718. document.removeEventListener('touchmove', this.onPointerMove);
  2719. document.removeEventListener('touchend', this.onPointerUp);
  2720. document.removeEventListener('touchcancel', this.onPointerUp);
  2721. }
  2722. }
  2723. }
  2724. function getPublicComponentMethods(components, actionsByInstance, clone) {
  2725. return class PublicComponentMethods {
  2726. constructor(instance, vidoInstance, props = {}) {
  2727. this.instance = instance;
  2728. this.name = vidoInstance.name;
  2729. this.vidoInstance = vidoInstance;
  2730. this.props = props;
  2731. this.destroy = this.destroy.bind(this);
  2732. this.update = this.update.bind(this);
  2733. this.change = this.change.bind(this);
  2734. this.html = this.html.bind(this);
  2735. }
  2736. /**
  2737. * Destroy component
  2738. */
  2739. destroy() {
  2740. if (this.vidoInstance.debug) {
  2741. console.groupCollapsed(`destroying component ${this.instance}`);
  2742. console.log(clone({ components: components.keys(), actionsByInstance }));
  2743. console.trace();
  2744. console.groupEnd();
  2745. }
  2746. return this.vidoInstance.destroyComponent(this.instance, this.vidoInstance);
  2747. }
  2748. /**
  2749. * Update template - trigger rendering process
  2750. */
  2751. update() {
  2752. if (this.vidoInstance.debug) {
  2753. console.groupCollapsed(`updating component ${this.instance}`);
  2754. console.log(clone({ components: components.keys(), actionsByInstance }));
  2755. console.trace();
  2756. console.groupEnd();
  2757. }
  2758. return this.vidoInstance.updateTemplate(this.vidoInstance);
  2759. }
  2760. /**
  2761. * Change component input properties
  2762. * @param {any} newProps
  2763. */
  2764. change(newProps, options) {
  2765. if (this.vidoInstance.debug) {
  2766. console.groupCollapsed(`changing component ${this.instance}`);
  2767. console.log(clone({ props: this.props, newProps: newProps, components: components.keys(), actionsByInstance }));
  2768. console.trace();
  2769. console.groupEnd();
  2770. }
  2771. components.get(this.instance).change(newProps, options);
  2772. }
  2773. /**
  2774. * Get component lit-html template
  2775. * @param {} templateProps
  2776. */
  2777. html(templateProps = {}) {
  2778. const component = components.get(this.instance);
  2779. if (component) {
  2780. return component.update(templateProps, this.vidoInstance);
  2781. }
  2782. return undefined;
  2783. }
  2784. _getComponents() {
  2785. return components;
  2786. }
  2787. _getActions() {
  2788. return actionsByInstance;
  2789. }
  2790. };
  2791. }
  2792. function getActionsCollector(actionsByInstance) {
  2793. return class ActionsCollector extends Directive {
  2794. constructor(instance) {
  2795. super();
  2796. this.instance = instance;
  2797. }
  2798. set(actions, props) {
  2799. this.actions = actions;
  2800. this.props = props;
  2801. // props must be mutable! (do not do this -> {...props})
  2802. // because we will modify action props with onChange and can reuse existin instance
  2803. return this;
  2804. }
  2805. body(part) {
  2806. const element = part.committer.element;
  2807. for (const create of this.actions) {
  2808. if (typeof create !== 'undefined') {
  2809. let exists;
  2810. if (actionsByInstance.has(this.instance)) {
  2811. for (const action of actionsByInstance.get(this.instance)) {
  2812. if (action.componentAction.create === create && action.element === element) {
  2813. exists = action;
  2814. break;
  2815. }
  2816. }
  2817. }
  2818. if (!exists) {
  2819. // @ts-ignore
  2820. if (typeof element.vido !== 'undefined')
  2821. delete element.vido;
  2822. const componentAction = {
  2823. create,
  2824. update() { },
  2825. destroy() { }
  2826. };
  2827. const action = { instance: this.instance, componentAction, element, props: this.props };
  2828. let byInstance = [];
  2829. if (actionsByInstance.has(this.instance)) {
  2830. byInstance = actionsByInstance.get(this.instance);
  2831. }
  2832. byInstance.push(action);
  2833. actionsByInstance.set(this.instance, byInstance);
  2834. }
  2835. else {
  2836. exists.props = this.props;
  2837. }
  2838. }
  2839. }
  2840. }
  2841. };
  2842. }
  2843. function getInternalComponentMethods(components, actionsByInstance, clone) {
  2844. return class InternalComponentMethods {
  2845. constructor(instance, vidoInstance, renderFunction, content) {
  2846. this.instance = instance;
  2847. this.vidoInstance = vidoInstance;
  2848. this.renderFunction = renderFunction;
  2849. this.content = content;
  2850. }
  2851. destroy() {
  2852. var _a;
  2853. if (this.vidoInstance.debug) {
  2854. console.groupCollapsed(`component destroy method fired ${this.instance}`);
  2855. console.log(clone({
  2856. props: this.vidoInstance.props,
  2857. components: components.keys(),
  2858. destroyable: this.vidoInstance.destroyable,
  2859. actionsByInstance
  2860. }));
  2861. console.trace();
  2862. console.groupEnd();
  2863. }
  2864. if (typeof ((_a = this.content) === null || _a === void 0 ? void 0 : _a.destroy) === 'function') {
  2865. this.content.destroy();
  2866. }
  2867. for (const d of this.vidoInstance.destroyable) {
  2868. d();
  2869. }
  2870. this.vidoInstance.onChangeFunctions = [];
  2871. this.vidoInstance.destroyable = [];
  2872. this.vidoInstance.update();
  2873. }
  2874. update(props = {}) {
  2875. if (this.vidoInstance.debug) {
  2876. console.groupCollapsed(`component update method fired ${this.instance}`);
  2877. console.log(clone({ components: components.keys(), actionsByInstance }));
  2878. console.trace();
  2879. console.groupEnd();
  2880. }
  2881. return this.renderFunction(props);
  2882. }
  2883. change(changedProps, options = { leave: false }) {
  2884. const props = changedProps;
  2885. if (this.vidoInstance.debug) {
  2886. console.groupCollapsed(`component change method fired ${this.instance}`);
  2887. console.log(clone({
  2888. props,
  2889. components: components.keys(),
  2890. onChangeFunctions: this.vidoInstance.onChangeFunctions,
  2891. changedProps,
  2892. actionsByInstance
  2893. }));
  2894. console.trace();
  2895. console.groupEnd();
  2896. }
  2897. for (const fn of this.vidoInstance.onChangeFunctions) {
  2898. fn(changedProps, options);
  2899. }
  2900. }
  2901. };
  2902. }
  2903. /**
  2904. * Schedule - a throttle function that uses requestAnimationFrame to limit the rate at which a function is called.
  2905. *
  2906. * @param {function} fn
  2907. * @returns {function}
  2908. */
  2909. function schedule(fn) {
  2910. let frameId = 0;
  2911. function wrapperFn(argument) {
  2912. if (frameId) {
  2913. return;
  2914. }
  2915. function executeFrame() {
  2916. frameId = 0;
  2917. fn.apply(undefined, [argument]);
  2918. }
  2919. frameId = requestAnimationFrame(executeFrame);
  2920. }
  2921. return wrapperFn;
  2922. }
  2923. /**
  2924. * Is object - helper function to determine if specified variable is an object
  2925. *
  2926. * @param {any} item
  2927. * @returns {boolean}
  2928. */
  2929. function isObject(item) {
  2930. return item && typeof item === 'object' && !Array.isArray(item);
  2931. }
  2932. /**
  2933. * Merge deep - helper function which will merge objects recursively - creating brand new one - like clone
  2934. *
  2935. * @param {object} target
  2936. * @params {object} sources
  2937. * @returns {object}
  2938. */
  2939. function mergeDeep(target, ...sources) {
  2940. const source = sources.shift();
  2941. if (isObject(target) && isObject(source)) {
  2942. for (const key in source) {
  2943. if (isObject(source[key])) {
  2944. if (typeof target[key] === 'undefined') {
  2945. target[key] = {};
  2946. }
  2947. target[key] = mergeDeep(target[key], source[key]);
  2948. }
  2949. else if (Array.isArray(source[key])) {
  2950. target[key] = [];
  2951. for (let item of source[key]) {
  2952. if (isObject(item)) {
  2953. target[key].push(mergeDeep({}, item));
  2954. continue;
  2955. }
  2956. target[key].push(item);
  2957. }
  2958. }
  2959. else {
  2960. target[key] = source[key];
  2961. }
  2962. }
  2963. }
  2964. if (!sources.length) {
  2965. return target;
  2966. }
  2967. return mergeDeep(target, ...sources);
  2968. }
  2969. /**
  2970. * Clone helper function
  2971. *
  2972. * @param source
  2973. * @returns {object} cloned source
  2974. */
  2975. function clone(source) {
  2976. if (typeof source.actions !== 'undefined') {
  2977. const actns = source.actions.map((action) => {
  2978. const result = Object.assign({}, action);
  2979. const props = Object.assign({}, result.props);
  2980. delete props.state;
  2981. delete props.api;
  2982. delete result.element;
  2983. result.props = props;
  2984. return result;
  2985. });
  2986. source.actions = actns;
  2987. }
  2988. return mergeDeep({}, source);
  2989. }
  2990. /* dev imports
  2991. import { render, html, directive, svg, Part } from '../lit-html';
  2992. import { asyncAppend } from '../lit-html/directives/async-append';
  2993. import { asyncReplace } from '../lit-html/directives/async-replace';
  2994. import { cache } from '../lit-html/directives/cache';
  2995. import { classMap } from '../lit-html/directives/class-map';
  2996. import { guard } from '../lit-html/directives/guard';
  2997. import { ifDefined } from '../lit-html/directives/if-defined';
  2998. import { repeat } from '../lit-html/directives/repeat';
  2999. import { unsafeHTML } from '../lit-html/directives/unsafe-html';
  3000. import { until } from '../lit-html/directives/until';
  3001. import { Directive } from '../lit-html/lib/directive';
  3002. */
  3003. /**
  3004. * Vido library
  3005. *
  3006. * @param {any} state - state management for the view (can be anything)
  3007. * @param {any} api - some api's or other globally available services
  3008. * @returns {object} vido instance
  3009. */
  3010. function Vido(state, api) {
  3011. let componentId = 0;
  3012. const components = new Map();
  3013. let actionsByInstance = new Map();
  3014. let app, element;
  3015. let shouldUpdateCount = 0;
  3016. const resolved = Promise.resolve();
  3017. const additionalMethods = {};
  3018. const ActionsCollector = getActionsCollector(actionsByInstance);
  3019. class InstanceActionsCollector {
  3020. constructor(instance) {
  3021. this.instance = instance;
  3022. }
  3023. create(actions, props) {
  3024. const actionsInstance = new ActionsCollector(this.instance);
  3025. actionsInstance.set(actions, props);
  3026. return actionsInstance;
  3027. }
  3028. }
  3029. const PublicComponentMethods = getPublicComponentMethods(components, actionsByInstance, clone);
  3030. const InternalComponentMethods = getInternalComponentMethods(components, actionsByInstance, clone);
  3031. class vido {
  3032. constructor() {
  3033. this.destroyable = [];
  3034. this.onChangeFunctions = [];
  3035. this.debug = false;
  3036. this.state = state;
  3037. this.api = api;
  3038. this.lastProps = {};
  3039. this.html = html;
  3040. this.svg = svg;
  3041. this.directive = directive;
  3042. this.asyncAppend = asyncAppend;
  3043. this.asyncReplace = asyncReplace;
  3044. this.cache = cache;
  3045. this.classMap = classMap;
  3046. this.guard = guard;
  3047. this.ifDefined = ifDefined;
  3048. this.repeat = repeat;
  3049. this.unsafeHTML = unsafeHTML;
  3050. this.until = until;
  3051. this.schedule = schedule;
  3052. this.actionsByInstance = (componentActions, props) => { };
  3053. this.StyleMap = StyleMap;
  3054. this.Detach = Detach;
  3055. this.PointerAction = PointerAction;
  3056. this.Action = Action;
  3057. this._components = components;
  3058. this._actions = actionsByInstance;
  3059. this.reuseComponents = this.reuseComponents.bind(this);
  3060. this.onDestroy = this.onDestroy.bind(this);
  3061. this.onChange = this.onChange.bind(this);
  3062. this.update = this.update.bind(this);
  3063. for (const name in additionalMethods) {
  3064. this[name] = additionalMethods[name];
  3065. }
  3066. }
  3067. addMethod(name, body) {
  3068. additionalMethods[name] = body;
  3069. }
  3070. onDestroy(fn) {
  3071. this.destroyable.push(fn);
  3072. }
  3073. onChange(fn) {
  3074. this.onChangeFunctions.push(fn);
  3075. }
  3076. update(callback) {
  3077. return this.updateTemplate(callback);
  3078. }
  3079. /**
  3080. * Reuse existing components when your data was changed
  3081. *
  3082. * @param {array} currentComponents - array of components
  3083. * @param {array} dataArray - any data as array for each component
  3084. * @param {function} getProps - you can pass params to component from array item ( example: item=>({id:item.id}) )
  3085. * @param {function} component - what kind of components do you want to create?
  3086. * @param {boolean} leaveTail - leave last elements and do not destroy corresponding components
  3087. * @returns {array} of components (with updated/destroyed/created ones)
  3088. */
  3089. reuseComponents(currentComponents, dataArray, getProps, component, leaveTail = true) {
  3090. const modified = [];
  3091. const currentLen = currentComponents.length;
  3092. const dataLen = dataArray.length;
  3093. let leave = false;
  3094. if (leaveTail && (dataArray === undefined || dataArray.length === 0)) {
  3095. leave = true;
  3096. }
  3097. let leaveStartingAt = 0;
  3098. if (currentLen < dataLen) {
  3099. let diff = dataLen - currentLen;
  3100. while (diff) {
  3101. const item = dataArray[dataLen - diff];
  3102. const newComponent = this.createComponent(component, getProps(item));
  3103. currentComponents.push(newComponent);
  3104. modified.push(newComponent.instance);
  3105. diff--;
  3106. }
  3107. }
  3108. else if (currentLen > dataLen) {
  3109. let diff = currentLen - dataLen;
  3110. if (leaveTail) {
  3111. leave = true;
  3112. leaveStartingAt = currentLen - diff;
  3113. }
  3114. while (diff) {
  3115. const index = currentLen - diff;
  3116. if (!leaveTail) {
  3117. modified.push(currentComponents[index].instance);
  3118. currentComponents[index].destroy();
  3119. }
  3120. diff--;
  3121. }
  3122. if (!leaveTail) {
  3123. currentComponents.length = dataLen;
  3124. }
  3125. }
  3126. let index = 0;
  3127. for (const component of currentComponents) {
  3128. const item = dataArray[index];
  3129. if (!modified.includes(component.instance)) {
  3130. component.change(getProps(item), { leave: leave && index >= leaveStartingAt });
  3131. }
  3132. index++;
  3133. }
  3134. }
  3135. createComponent(component, props = {}, content = null) {
  3136. const instance = component.name + ':' + componentId++;
  3137. let vidoInstance;
  3138. vidoInstance = new vido();
  3139. vidoInstance.instance = instance;
  3140. vidoInstance.name = component.name;
  3141. vidoInstance.Actions = new InstanceActionsCollector(instance);
  3142. const publicMethods = new PublicComponentMethods(instance, vidoInstance, props);
  3143. const internalMethods = new InternalComponentMethods(instance, vidoInstance, component(vidoInstance, props, content), content);
  3144. components.set(instance, internalMethods);
  3145. components.get(instance).change(props);
  3146. if (vidoInstance.debug) {
  3147. console.groupCollapsed(`component created ${instance}`);
  3148. console.log(clone({ props, components: components.keys(), actionsByInstance }));
  3149. console.trace();
  3150. console.groupEnd();
  3151. }
  3152. return publicMethods;
  3153. }
  3154. destroyComponent(instance, vidoInstance) {
  3155. if (vidoInstance.debug) {
  3156. console.groupCollapsed(`destroying component ${instance}...`);
  3157. console.log(clone({ components: components.keys(), actionsByInstance }));
  3158. console.trace();
  3159. console.groupEnd();
  3160. }
  3161. if (actionsByInstance.has(instance)) {
  3162. for (const action of actionsByInstance.get(instance)) {
  3163. if (typeof action.componentAction.destroy === 'function') {
  3164. action.componentAction.destroy(action.element, action.props);
  3165. }
  3166. }
  3167. }
  3168. actionsByInstance.delete(instance);
  3169. const component = components.get(instance);
  3170. component.update();
  3171. component.destroy();
  3172. components.delete(instance);
  3173. if (vidoInstance.debug) {
  3174. console.groupCollapsed(`component destroyed ${instance}`);
  3175. console.log(clone({ components: components.keys(), actionsByInstance }));
  3176. console.trace();
  3177. console.groupEnd();
  3178. }
  3179. }
  3180. executeActions() {
  3181. var _a, _b, _c;
  3182. for (const actions of actionsByInstance.values()) {
  3183. for (const action of actions) {
  3184. if (action.element.vido === undefined) {
  3185. const componentAction = action.componentAction;
  3186. const create = componentAction.create;
  3187. if (typeof create !== 'undefined') {
  3188. let result;
  3189. if (((_a = create.prototype) === null || _a === void 0 ? void 0 : _a.isAction) !== true &&
  3190. create.isAction === undefined &&
  3191. ((_b = create.prototype) === null || _b === void 0 ? void 0 : _b.update) === undefined &&
  3192. ((_c = create.prototype) === null || _c === void 0 ? void 0 : _c.destroy) === undefined) {
  3193. result = create(action.element, action.props);
  3194. }
  3195. else {
  3196. result = new create(action.element, action.props);
  3197. }
  3198. if (result !== undefined) {
  3199. if (typeof result === 'function') {
  3200. componentAction.destroy = result;
  3201. }
  3202. else {
  3203. if (typeof result.update === 'function') {
  3204. componentAction.update = result.update.bind(result);
  3205. }
  3206. if (typeof result.destroy === 'function') {
  3207. componentAction.destroy = result.destroy.bind(result);
  3208. }
  3209. }
  3210. }
  3211. }
  3212. }
  3213. else {
  3214. action.element.vido = action.props;
  3215. if (typeof action.componentAction.update === 'function') {
  3216. action.componentAction.update(action.element, action.props);
  3217. }
  3218. }
  3219. }
  3220. for (const action of actions) {
  3221. action.element.vido = action.props;
  3222. }
  3223. }
  3224. }
  3225. updateTemplate(callback) {
  3226. return new Promise((resolve) => {
  3227. const currentShouldUpdateCount = ++shouldUpdateCount;
  3228. const self = this;
  3229. function flush() {
  3230. if (currentShouldUpdateCount === shouldUpdateCount) {
  3231. shouldUpdateCount = 0;
  3232. self.render();
  3233. if (typeof callback === 'function')
  3234. callback();
  3235. resolve();
  3236. }
  3237. }
  3238. resolved.then(flush);
  3239. });
  3240. }
  3241. createApp(config) {
  3242. element = config.element;
  3243. const App = this.createComponent(config.component, config.props);
  3244. app = App.instance;
  3245. this.render();
  3246. return App;
  3247. }
  3248. render() {
  3249. const appComponent = components.get(app);
  3250. if (appComponent) {
  3251. render(appComponent.update(), element);
  3252. this.executeActions();
  3253. }
  3254. else if (element) {
  3255. element.remove();
  3256. }
  3257. }
  3258. }
  3259. return new vido();
  3260. }
  3261. Vido.prototype.lithtml = lithtml;
  3262. Vido.prototype.Action = Action;
  3263. Vido.prototype.Directive = Directive;
  3264. Vido.prototype.schedule = schedule;
  3265. Vido.prototype.Detach = Detach;
  3266. Vido.prototype.StyleMap = StyleMap;
  3267. Vido.prototype.PointerAction = PointerAction;
  3268. Vido.prototype.asyncAppend = asyncAppend;
  3269. Vido.prototype.asyncReplace = asyncReplace;
  3270. Vido.prototype.cache = cache;
  3271. Vido.prototype.classMap = classMap;
  3272. Vido.prototype.guard = guard;
  3273. Vido.prototype.ifDefined = ifDefined;
  3274. Vido.prototype.repeat = repeat;
  3275. Vido.prototype.unsafeHTML = unsafeHTML;
  3276. Vido.prototype.unti = until;
  3277. /**
  3278. * A collection of shims that provide minimal functionality of the ES6 collections.
  3279. *
  3280. * These implementations are not meant to be used outside of the ResizeObserver
  3281. * modules as they cover only a limited range of use cases.
  3282. */
  3283. /* eslint-disable require-jsdoc, valid-jsdoc */
  3284. var MapShim = (function () {
  3285. if (typeof Map !== 'undefined') {
  3286. return Map;
  3287. }
  3288. /**
  3289. * Returns index in provided array that matches the specified key.
  3290. *
  3291. * @param {Array<Array>} arr
  3292. * @param {*} key
  3293. * @returns {number}
  3294. */
  3295. function getIndex(arr, key) {
  3296. var result = -1;
  3297. arr.some(function (entry, index) {
  3298. if (entry[0] === key) {
  3299. result = index;
  3300. return true;
  3301. }
  3302. return false;
  3303. });
  3304. return result;
  3305. }
  3306. return /** @class */ (function () {
  3307. function class_1() {
  3308. this.__entries__ = [];
  3309. }
  3310. Object.defineProperty(class_1.prototype, "size", {
  3311. /**
  3312. * @returns {boolean}
  3313. */
  3314. get: function () {
  3315. return this.__entries__.length;
  3316. },
  3317. enumerable: true,
  3318. configurable: true
  3319. });
  3320. /**
  3321. * @param {*} key
  3322. * @returns {*}
  3323. */
  3324. class_1.prototype.get = function (key) {
  3325. var index = getIndex(this.__entries__, key);
  3326. var entry = this.__entries__[index];
  3327. return entry && entry[1];
  3328. };
  3329. /**
  3330. * @param {*} key
  3331. * @param {*} value
  3332. * @returns {void}
  3333. */
  3334. class_1.prototype.set = function (key, value) {
  3335. var index = getIndex(this.__entries__, key);
  3336. if (~index) {
  3337. this.__entries__[index][1] = value;
  3338. }
  3339. else {
  3340. this.__entries__.push([key, value]);
  3341. }
  3342. };
  3343. /**
  3344. * @param {*} key
  3345. * @returns {void}
  3346. */
  3347. class_1.prototype.delete = function (key) {
  3348. var entries = this.__entries__;
  3349. var index = getIndex(entries, key);
  3350. if (~index) {
  3351. entries.splice(index, 1);
  3352. }
  3353. };
  3354. /**
  3355. * @param {*} key
  3356. * @returns {void}
  3357. */
  3358. class_1.prototype.has = function (key) {
  3359. return !!~getIndex(this.__entries__, key);
  3360. };
  3361. /**
  3362. * @returns {void}
  3363. */
  3364. class_1.prototype.clear = function () {
  3365. this.__entries__.splice(0);
  3366. };
  3367. /**
  3368. * @param {Function} callback
  3369. * @param {*} [ctx=null]
  3370. * @returns {void}
  3371. */
  3372. class_1.prototype.forEach = function (callback, ctx) {
  3373. if (ctx === void 0) { ctx = null; }
  3374. for (var _i = 0, _a = this.__entries__; _i < _a.length; _i++) {
  3375. var entry = _a[_i];
  3376. callback.call(ctx, entry[1], entry[0]);
  3377. }
  3378. };
  3379. return class_1;
  3380. }());
  3381. })();
  3382. /**
  3383. * Detects whether window and document objects are available in current environment.
  3384. */
  3385. var isBrowser$1 = typeof window !== 'undefined' && typeof document !== 'undefined' && window.document === document;
  3386. // Returns global object of a current environment.
  3387. var global$1 = (function () {
  3388. if (typeof global !== 'undefined' && global.Math === Math) {
  3389. return global;
  3390. }
  3391. if (typeof self !== 'undefined' && self.Math === Math) {
  3392. return self;
  3393. }
  3394. if (typeof window !== 'undefined' && window.Math === Math) {
  3395. return window;
  3396. }
  3397. // eslint-disable-next-line no-new-func
  3398. return Function('return this')();
  3399. })();
  3400. /**
  3401. * A shim for the requestAnimationFrame which falls back to the setTimeout if
  3402. * first one is not supported.
  3403. *
  3404. * @returns {number} Requests' identifier.
  3405. */
  3406. var requestAnimationFrame$1 = (function () {
  3407. if (typeof requestAnimationFrame === 'function') {
  3408. // It's required to use a bounded function because IE sometimes throws
  3409. // an "Invalid calling object" error if rAF is invoked without the global
  3410. // object on the left hand side.
  3411. return requestAnimationFrame.bind(global$1);
  3412. }
  3413. return function (callback) { return setTimeout(function () { return callback(Date.now()); }, 1000 / 60); };
  3414. })();
  3415. // Defines minimum timeout before adding a trailing call.
  3416. var trailingTimeout = 2;
  3417. /**
  3418. * Creates a wrapper function which ensures that provided callback will be
  3419. * invoked only once during the specified delay period.
  3420. *
  3421. * @param {Function} callback - Function to be invoked after the delay period.
  3422. * @param {number} delay - Delay after which to invoke callback.
  3423. * @returns {Function}
  3424. */
  3425. function throttle (callback, delay) {
  3426. var leadingCall = false, trailingCall = false, lastCallTime = 0;
  3427. /**
  3428. * Invokes the original callback function and schedules new invocation if
  3429. * the "proxy" was called during current request.
  3430. *
  3431. * @returns {void}
  3432. */
  3433. function resolvePending() {
  3434. if (leadingCall) {
  3435. leadingCall = false;
  3436. callback();
  3437. }
  3438. if (trailingCall) {
  3439. proxy();
  3440. }
  3441. }
  3442. /**
  3443. * Callback invoked after the specified delay. It will further postpone
  3444. * invocation of the original function delegating it to the
  3445. * requestAnimationFrame.
  3446. *
  3447. * @returns {void}
  3448. */
  3449. function timeoutCallback() {
  3450. requestAnimationFrame$1(resolvePending);
  3451. }
  3452. /**
  3453. * Schedules invocation of the original function.
  3454. *
  3455. * @returns {void}
  3456. */
  3457. function proxy() {
  3458. var timeStamp = Date.now();
  3459. if (leadingCall) {
  3460. // Reject immediately following calls.
  3461. if (timeStamp - lastCallTime < trailingTimeout) {
  3462. return;
  3463. }
  3464. // Schedule new call to be in invoked when the pending one is resolved.
  3465. // This is important for "transitions" which never actually start
  3466. // immediately so there is a chance that we might miss one if change
  3467. // happens amids the pending invocation.
  3468. trailingCall = true;
  3469. }
  3470. else {
  3471. leadingCall = true;
  3472. trailingCall = false;
  3473. setTimeout(timeoutCallback, delay);
  3474. }
  3475. lastCallTime = timeStamp;
  3476. }
  3477. return proxy;
  3478. }
  3479. // Minimum delay before invoking the update of observers.
  3480. var REFRESH_DELAY = 20;
  3481. // A list of substrings of CSS properties used to find transition events that
  3482. // might affect dimensions of observed elements.
  3483. var transitionKeys = ['top', 'right', 'bottom', 'left', 'width', 'height', 'size', 'weight'];
  3484. // Check if MutationObserver is available.
  3485. var mutationObserverSupported = typeof MutationObserver !== 'undefined';
  3486. /**
  3487. * Singleton controller class which handles updates of ResizeObserver instances.
  3488. */
  3489. var ResizeObserverController = /** @class */ (function () {
  3490. /**
  3491. * Creates a new instance of ResizeObserverController.
  3492. *
  3493. * @private
  3494. */
  3495. function ResizeObserverController() {
  3496. /**
  3497. * Indicates whether DOM listeners have been added.
  3498. *
  3499. * @private {boolean}
  3500. */
  3501. this.connected_ = false;
  3502. /**
  3503. * Tells that controller has subscribed for Mutation Events.
  3504. *
  3505. * @private {boolean}
  3506. */
  3507. this.mutationEventsAdded_ = false;
  3508. /**
  3509. * Keeps reference to the instance of MutationObserver.
  3510. *
  3511. * @private {MutationObserver}
  3512. */
  3513. this.mutationsObserver_ = null;
  3514. /**
  3515. * A list of connected observers.
  3516. *
  3517. * @private {Array<ResizeObserverSPI>}
  3518. */
  3519. this.observers_ = [];
  3520. this.onTransitionEnd_ = this.onTransitionEnd_.bind(this);
  3521. this.refresh = throttle(this.refresh.bind(this), REFRESH_DELAY);
  3522. }
  3523. /**
  3524. * Adds observer to observers list.
  3525. *
  3526. * @param {ResizeObserverSPI} observer - Observer to be added.
  3527. * @returns {void}
  3528. */
  3529. ResizeObserverController.prototype.addObserver = function (observer) {
  3530. if (!~this.observers_.indexOf(observer)) {
  3531. this.observers_.push(observer);
  3532. }
  3533. // Add listeners if they haven't been added yet.
  3534. if (!this.connected_) {
  3535. this.connect_();
  3536. }
  3537. };
  3538. /**
  3539. * Removes observer from observers list.
  3540. *
  3541. * @param {ResizeObserverSPI} observer - Observer to be removed.
  3542. * @returns {void}
  3543. */
  3544. ResizeObserverController.prototype.removeObserver = function (observer) {
  3545. var observers = this.observers_;
  3546. var index = observers.indexOf(observer);
  3547. // Remove observer if it's present in registry.
  3548. if (~index) {
  3549. observers.splice(index, 1);
  3550. }
  3551. // Remove listeners if controller has no connected observers.
  3552. if (!observers.length && this.connected_) {
  3553. this.disconnect_();
  3554. }
  3555. };
  3556. /**
  3557. * Invokes the update of observers. It will continue running updates insofar
  3558. * it detects changes.
  3559. *
  3560. * @returns {void}
  3561. */
  3562. ResizeObserverController.prototype.refresh = function () {
  3563. var changesDetected = this.updateObservers_();
  3564. // Continue running updates if changes have been detected as there might
  3565. // be future ones caused by CSS transitions.
  3566. if (changesDetected) {
  3567. this.refresh();
  3568. }
  3569. };
  3570. /**
  3571. * Updates every observer from observers list and notifies them of queued
  3572. * entries.
  3573. *
  3574. * @private
  3575. * @returns {boolean} Returns "true" if any observer has detected changes in
  3576. * dimensions of it's elements.
  3577. */
  3578. ResizeObserverController.prototype.updateObservers_ = function () {
  3579. // Collect observers that have active observations.
  3580. var activeObservers = this.observers_.filter(function (observer) {
  3581. return observer.gatherActive(), observer.hasActive();
  3582. });
  3583. // Deliver notifications in a separate cycle in order to avoid any
  3584. // collisions between observers, e.g. when multiple instances of
  3585. // ResizeObserver are tracking the same element and the callback of one
  3586. // of them changes content dimensions of the observed target. Sometimes
  3587. // this may result in notifications being blocked for the rest of observers.
  3588. activeObservers.forEach(function (observer) { return observer.broadcastActive(); });
  3589. return activeObservers.length > 0;
  3590. };
  3591. /**
  3592. * Initializes DOM listeners.
  3593. *
  3594. * @private
  3595. * @returns {void}
  3596. */
  3597. ResizeObserverController.prototype.connect_ = function () {
  3598. // Do nothing if running in a non-browser environment or if listeners
  3599. // have been already added.
  3600. if (!isBrowser$1 || this.connected_) {
  3601. return;
  3602. }
  3603. // Subscription to the "Transitionend" event is used as a workaround for
  3604. // delayed transitions. This way it's possible to capture at least the
  3605. // final state of an element.
  3606. document.addEventListener('transitionend', this.onTransitionEnd_);
  3607. window.addEventListener('resize', this.refresh);
  3608. if (mutationObserverSupported) {
  3609. this.mutationsObserver_ = new MutationObserver(this.refresh);
  3610. this.mutationsObserver_.observe(document, {
  3611. attributes: true,
  3612. childList: true,
  3613. characterData: true,
  3614. subtree: true
  3615. });
  3616. }
  3617. else {
  3618. document.addEventListener('DOMSubtreeModified', this.refresh);
  3619. this.mutationEventsAdded_ = true;
  3620. }
  3621. this.connected_ = true;
  3622. };
  3623. /**
  3624. * Removes DOM listeners.
  3625. *
  3626. * @private
  3627. * @returns {void}
  3628. */
  3629. ResizeObserverController.prototype.disconnect_ = function () {
  3630. // Do nothing if running in a non-browser environment or if listeners
  3631. // have been already removed.
  3632. if (!isBrowser$1 || !this.connected_) {
  3633. return;
  3634. }
  3635. document.removeEventListener('transitionend', this.onTransitionEnd_);
  3636. window.removeEventListener('resize', this.refresh);
  3637. if (this.mutationsObserver_) {
  3638. this.mutationsObserver_.disconnect();
  3639. }
  3640. if (this.mutationEventsAdded_) {
  3641. document.removeEventListener('DOMSubtreeModified', this.refresh);
  3642. }
  3643. this.mutationsObserver_ = null;
  3644. this.mutationEventsAdded_ = false;
  3645. this.connected_ = false;
  3646. };
  3647. /**
  3648. * "Transitionend" event handler.
  3649. *
  3650. * @private
  3651. * @param {TransitionEvent} event
  3652. * @returns {void}
  3653. */
  3654. ResizeObserverController.prototype.onTransitionEnd_ = function (_a) {
  3655. var _b = _a.propertyName, propertyName = _b === void 0 ? '' : _b;
  3656. // Detect whether transition may affect dimensions of an element.
  3657. var isReflowProperty = transitionKeys.some(function (key) {
  3658. return !!~propertyName.indexOf(key);
  3659. });
  3660. if (isReflowProperty) {
  3661. this.refresh();
  3662. }
  3663. };
  3664. /**
  3665. * Returns instance of the ResizeObserverController.
  3666. *
  3667. * @returns {ResizeObserverController}
  3668. */
  3669. ResizeObserverController.getInstance = function () {
  3670. if (!this.instance_) {
  3671. this.instance_ = new ResizeObserverController();
  3672. }
  3673. return this.instance_;
  3674. };
  3675. /**
  3676. * Holds reference to the controller's instance.
  3677. *
  3678. * @private {ResizeObserverController}
  3679. */
  3680. ResizeObserverController.instance_ = null;
  3681. return ResizeObserverController;
  3682. }());
  3683. /**
  3684. * Defines non-writable/enumerable properties of the provided target object.
  3685. *
  3686. * @param {Object} target - Object for which to define properties.
  3687. * @param {Object} props - Properties to be defined.
  3688. * @returns {Object} Target object.
  3689. */
  3690. var defineConfigurable = (function (target, props) {
  3691. for (var _i = 0, _a = Object.keys(props); _i < _a.length; _i++) {
  3692. var key = _a[_i];
  3693. Object.defineProperty(target, key, {
  3694. value: props[key],
  3695. enumerable: false,
  3696. writable: false,
  3697. configurable: true
  3698. });
  3699. }
  3700. return target;
  3701. });
  3702. /**
  3703. * Returns the global object associated with provided element.
  3704. *
  3705. * @param {Object} target
  3706. * @returns {Object}
  3707. */
  3708. var getWindowOf = (function (target) {
  3709. // Assume that the element is an instance of Node, which means that it
  3710. // has the "ownerDocument" property from which we can retrieve a
  3711. // corresponding global object.
  3712. var ownerGlobal = target && target.ownerDocument && target.ownerDocument.defaultView;
  3713. // Return the local global object if it's not possible extract one from
  3714. // provided element.
  3715. return ownerGlobal || global$1;
  3716. });
  3717. // Placeholder of an empty content rectangle.
  3718. var emptyRect = createRectInit(0, 0, 0, 0);
  3719. /**
  3720. * Converts provided string to a number.
  3721. *
  3722. * @param {number|string} value
  3723. * @returns {number}
  3724. */
  3725. function toFloat(value) {
  3726. return parseFloat(value) || 0;
  3727. }
  3728. /**
  3729. * Extracts borders size from provided styles.
  3730. *
  3731. * @param {CSSStyleDeclaration} styles
  3732. * @param {...string} positions - Borders positions (top, right, ...)
  3733. * @returns {number}
  3734. */
  3735. function getBordersSize(styles) {
  3736. var positions = [];
  3737. for (var _i = 1; _i < arguments.length; _i++) {
  3738. positions[_i - 1] = arguments[_i];
  3739. }
  3740. return positions.reduce(function (size, position) {
  3741. var value = styles['border-' + position + '-width'];
  3742. return size + toFloat(value);
  3743. }, 0);
  3744. }
  3745. /**
  3746. * Extracts paddings sizes from provided styles.
  3747. *
  3748. * @param {CSSStyleDeclaration} styles
  3749. * @returns {Object} Paddings box.
  3750. */
  3751. function getPaddings(styles) {
  3752. var positions = ['top', 'right', 'bottom', 'left'];
  3753. var paddings = {};
  3754. for (var _i = 0, positions_1 = positions; _i < positions_1.length; _i++) {
  3755. var position = positions_1[_i];
  3756. var value = styles['padding-' + position];
  3757. paddings[position] = toFloat(value);
  3758. }
  3759. return paddings;
  3760. }
  3761. /**
  3762. * Calculates content rectangle of provided SVG element.
  3763. *
  3764. * @param {SVGGraphicsElement} target - Element content rectangle of which needs
  3765. * to be calculated.
  3766. * @returns {DOMRectInit}
  3767. */
  3768. function getSVGContentRect(target) {
  3769. var bbox = target.getBBox();
  3770. return createRectInit(0, 0, bbox.width, bbox.height);
  3771. }
  3772. /**
  3773. * Calculates content rectangle of provided HTMLElement.
  3774. *
  3775. * @param {HTMLElement} target - Element for which to calculate the content rectangle.
  3776. * @returns {DOMRectInit}
  3777. */
  3778. function getHTMLElementContentRect(target) {
  3779. // Client width & height properties can't be
  3780. // used exclusively as they provide rounded values.
  3781. var clientWidth = target.clientWidth, clientHeight = target.clientHeight;
  3782. // By this condition we can catch all non-replaced inline, hidden and
  3783. // detached elements. Though elements with width & height properties less
  3784. // than 0.5 will be discarded as well.
  3785. //
  3786. // Without it we would need to implement separate methods for each of
  3787. // those cases and it's not possible to perform a precise and performance
  3788. // effective test for hidden elements. E.g. even jQuery's ':visible' filter
  3789. // gives wrong results for elements with width & height less than 0.5.
  3790. if (!clientWidth && !clientHeight) {
  3791. return emptyRect;
  3792. }
  3793. var styles = getWindowOf(target).getComputedStyle(target);
  3794. var paddings = getPaddings(styles);
  3795. var horizPad = paddings.left + paddings.right;
  3796. var vertPad = paddings.top + paddings.bottom;
  3797. // Computed styles of width & height are being used because they are the
  3798. // only dimensions available to JS that contain non-rounded values. It could
  3799. // be possible to utilize the getBoundingClientRect if only it's data wasn't
  3800. // affected by CSS transformations let alone paddings, borders and scroll bars.
  3801. var width = toFloat(styles.width), height = toFloat(styles.height);
  3802. // Width & height include paddings and borders when the 'border-box' box
  3803. // model is applied (except for IE).
  3804. if (styles.boxSizing === 'border-box') {
  3805. // Following conditions are required to handle Internet Explorer which
  3806. // doesn't include paddings and borders to computed CSS dimensions.
  3807. //
  3808. // We can say that if CSS dimensions + paddings are equal to the "client"
  3809. // properties then it's either IE, and thus we don't need to subtract
  3810. // anything, or an element merely doesn't have paddings/borders styles.
  3811. if (Math.round(width + horizPad) !== clientWidth) {
  3812. width -= getBordersSize(styles, 'left', 'right') + horizPad;
  3813. }
  3814. if (Math.round(height + vertPad) !== clientHeight) {
  3815. height -= getBordersSize(styles, 'top', 'bottom') + vertPad;
  3816. }
  3817. }
  3818. // Following steps can't be applied to the document's root element as its
  3819. // client[Width/Height] properties represent viewport area of the window.
  3820. // Besides, it's as well not necessary as the <html> itself neither has
  3821. // rendered scroll bars nor it can be clipped.
  3822. if (!isDocumentElement(target)) {
  3823. // In some browsers (only in Firefox, actually) CSS width & height
  3824. // include scroll bars size which can be removed at this step as scroll
  3825. // bars are the only difference between rounded dimensions + paddings
  3826. // and "client" properties, though that is not always true in Chrome.
  3827. var vertScrollbar = Math.round(width + horizPad) - clientWidth;
  3828. var horizScrollbar = Math.round(height + vertPad) - clientHeight;
  3829. // Chrome has a rather weird rounding of "client" properties.
  3830. // E.g. for an element with content width of 314.2px it sometimes gives
  3831. // the client width of 315px and for the width of 314.7px it may give
  3832. // 314px. And it doesn't happen all the time. So just ignore this delta
  3833. // as a non-relevant.
  3834. if (Math.abs(vertScrollbar) !== 1) {
  3835. width -= vertScrollbar;
  3836. }
  3837. if (Math.abs(horizScrollbar) !== 1) {
  3838. height -= horizScrollbar;
  3839. }
  3840. }
  3841. return createRectInit(paddings.left, paddings.top, width, height);
  3842. }
  3843. /**
  3844. * Checks whether provided element is an instance of the SVGGraphicsElement.
  3845. *
  3846. * @param {Element} target - Element to be checked.
  3847. * @returns {boolean}
  3848. */
  3849. var isSVGGraphicsElement = (function () {
  3850. // Some browsers, namely IE and Edge, don't have the SVGGraphicsElement
  3851. // interface.
  3852. if (typeof SVGGraphicsElement !== 'undefined') {
  3853. return function (target) { return target instanceof getWindowOf(target).SVGGraphicsElement; };
  3854. }
  3855. // If it's so, then check that element is at least an instance of the
  3856. // SVGElement and that it has the "getBBox" method.
  3857. // eslint-disable-next-line no-extra-parens
  3858. return function (target) { return (target instanceof getWindowOf(target).SVGElement &&
  3859. typeof target.getBBox === 'function'); };
  3860. })();
  3861. /**
  3862. * Checks whether provided element is a document element (<html>).
  3863. *
  3864. * @param {Element} target - Element to be checked.
  3865. * @returns {boolean}
  3866. */
  3867. function isDocumentElement(target) {
  3868. return target === getWindowOf(target).document.documentElement;
  3869. }
  3870. /**
  3871. * Calculates an appropriate content rectangle for provided html or svg element.
  3872. *
  3873. * @param {Element} target - Element content rectangle of which needs to be calculated.
  3874. * @returns {DOMRectInit}
  3875. */
  3876. function getContentRect(target) {
  3877. if (!isBrowser$1) {
  3878. return emptyRect;
  3879. }
  3880. if (isSVGGraphicsElement(target)) {
  3881. return getSVGContentRect(target);
  3882. }
  3883. return getHTMLElementContentRect(target);
  3884. }
  3885. /**
  3886. * Creates rectangle with an interface of the DOMRectReadOnly.
  3887. * Spec: https://drafts.fxtf.org/geometry/#domrectreadonly
  3888. *
  3889. * @param {DOMRectInit} rectInit - Object with rectangle's x/y coordinates and dimensions.
  3890. * @returns {DOMRectReadOnly}
  3891. */
  3892. function createReadOnlyRect(_a) {
  3893. var x = _a.x, y = _a.y, width = _a.width, height = _a.height;
  3894. // If DOMRectReadOnly is available use it as a prototype for the rectangle.
  3895. var Constr = typeof DOMRectReadOnly !== 'undefined' ? DOMRectReadOnly : Object;
  3896. var rect = Object.create(Constr.prototype);
  3897. // Rectangle's properties are not writable and non-enumerable.
  3898. defineConfigurable(rect, {
  3899. x: x, y: y, width: width, height: height,
  3900. top: y,
  3901. right: x + width,
  3902. bottom: height + y,
  3903. left: x
  3904. });
  3905. return rect;
  3906. }
  3907. /**
  3908. * Creates DOMRectInit object based on the provided dimensions and the x/y coordinates.
  3909. * Spec: https://drafts.fxtf.org/geometry/#dictdef-domrectinit
  3910. *
  3911. * @param {number} x - X coordinate.
  3912. * @param {number} y - Y coordinate.
  3913. * @param {number} width - Rectangle's width.
  3914. * @param {number} height - Rectangle's height.
  3915. * @returns {DOMRectInit}
  3916. */
  3917. function createRectInit(x, y, width, height) {
  3918. return { x: x, y: y, width: width, height: height };
  3919. }
  3920. /**
  3921. * Class that is responsible for computations of the content rectangle of
  3922. * provided DOM element and for keeping track of it's changes.
  3923. */
  3924. var ResizeObservation = /** @class */ (function () {
  3925. /**
  3926. * Creates an instance of ResizeObservation.
  3927. *
  3928. * @param {Element} target - Element to be observed.
  3929. */
  3930. function ResizeObservation(target) {
  3931. /**
  3932. * Broadcasted width of content rectangle.
  3933. *
  3934. * @type {number}
  3935. */
  3936. this.broadcastWidth = 0;
  3937. /**
  3938. * Broadcasted height of content rectangle.
  3939. *
  3940. * @type {number}
  3941. */
  3942. this.broadcastHeight = 0;
  3943. /**
  3944. * Reference to the last observed content rectangle.
  3945. *
  3946. * @private {DOMRectInit}
  3947. */
  3948. this.contentRect_ = createRectInit(0, 0, 0, 0);
  3949. this.target = target;
  3950. }
  3951. /**
  3952. * Updates content rectangle and tells whether it's width or height properties
  3953. * have changed since the last broadcast.
  3954. *
  3955. * @returns {boolean}
  3956. */
  3957. ResizeObservation.prototype.isActive = function () {
  3958. var rect = getContentRect(this.target);
  3959. this.contentRect_ = rect;
  3960. return (rect.width !== this.broadcastWidth ||
  3961. rect.height !== this.broadcastHeight);
  3962. };
  3963. /**
  3964. * Updates 'broadcastWidth' and 'broadcastHeight' properties with a data
  3965. * from the corresponding properties of the last observed content rectangle.
  3966. *
  3967. * @returns {DOMRectInit} Last observed content rectangle.
  3968. */
  3969. ResizeObservation.prototype.broadcastRect = function () {
  3970. var rect = this.contentRect_;
  3971. this.broadcastWidth = rect.width;
  3972. this.broadcastHeight = rect.height;
  3973. return rect;
  3974. };
  3975. return ResizeObservation;
  3976. }());
  3977. var ResizeObserverEntry = /** @class */ (function () {
  3978. /**
  3979. * Creates an instance of ResizeObserverEntry.
  3980. *
  3981. * @param {Element} target - Element that is being observed.
  3982. * @param {DOMRectInit} rectInit - Data of the element's content rectangle.
  3983. */
  3984. function ResizeObserverEntry(target, rectInit) {
  3985. var contentRect = createReadOnlyRect(rectInit);
  3986. // According to the specification following properties are not writable
  3987. // and are also not enumerable in the native implementation.
  3988. //
  3989. // Property accessors are not being used as they'd require to define a
  3990. // private WeakMap storage which may cause memory leaks in browsers that
  3991. // don't support this type of collections.
  3992. defineConfigurable(this, { target: target, contentRect: contentRect });
  3993. }
  3994. return ResizeObserverEntry;
  3995. }());
  3996. var ResizeObserverSPI = /** @class */ (function () {
  3997. /**
  3998. * Creates a new instance of ResizeObserver.
  3999. *
  4000. * @param {ResizeObserverCallback} callback - Callback function that is invoked
  4001. * when one of the observed elements changes it's content dimensions.
  4002. * @param {ResizeObserverController} controller - Controller instance which
  4003. * is responsible for the updates of observer.
  4004. * @param {ResizeObserver} callbackCtx - Reference to the public
  4005. * ResizeObserver instance which will be passed to callback function.
  4006. */
  4007. function ResizeObserverSPI(callback, controller, callbackCtx) {
  4008. /**
  4009. * Collection of resize observations that have detected changes in dimensions
  4010. * of elements.
  4011. *
  4012. * @private {Array<ResizeObservation>}
  4013. */
  4014. this.activeObservations_ = [];
  4015. /**
  4016. * Registry of the ResizeObservation instances.
  4017. *
  4018. * @private {Map<Element, ResizeObservation>}
  4019. */
  4020. this.observations_ = new MapShim();
  4021. if (typeof callback !== 'function') {
  4022. throw new TypeError('The callback provided as parameter 1 is not a function.');
  4023. }
  4024. this.callback_ = callback;
  4025. this.controller_ = controller;
  4026. this.callbackCtx_ = callbackCtx;
  4027. }
  4028. /**
  4029. * Starts observing provided element.
  4030. *
  4031. * @param {Element} target - Element to be observed.
  4032. * @returns {void}
  4033. */
  4034. ResizeObserverSPI.prototype.observe = function (target) {
  4035. if (!arguments.length) {
  4036. throw new TypeError('1 argument required, but only 0 present.');
  4037. }
  4038. // Do nothing if current environment doesn't have the Element interface.
  4039. if (typeof Element === 'undefined' || !(Element instanceof Object)) {
  4040. return;
  4041. }
  4042. if (!(target instanceof getWindowOf(target).Element)) {
  4043. throw new TypeError('parameter 1 is not of type "Element".');
  4044. }
  4045. var observations = this.observations_;
  4046. // Do nothing if element is already being observed.
  4047. if (observations.has(target)) {
  4048. return;
  4049. }
  4050. observations.set(target, new ResizeObservation(target));
  4051. this.controller_.addObserver(this);
  4052. // Force the update of observations.
  4053. this.controller_.refresh();
  4054. };
  4055. /**
  4056. * Stops observing provided element.
  4057. *
  4058. * @param {Element} target - Element to stop observing.
  4059. * @returns {void}
  4060. */
  4061. ResizeObserverSPI.prototype.unobserve = function (target) {
  4062. if (!arguments.length) {
  4063. throw new TypeError('1 argument required, but only 0 present.');
  4064. }
  4065. // Do nothing if current environment doesn't have the Element interface.
  4066. if (typeof Element === 'undefined' || !(Element instanceof Object)) {
  4067. return;
  4068. }
  4069. if (!(target instanceof getWindowOf(target).Element)) {
  4070. throw new TypeError('parameter 1 is not of type "Element".');
  4071. }
  4072. var observations = this.observations_;
  4073. // Do nothing if element is not being observed.
  4074. if (!observations.has(target)) {
  4075. return;
  4076. }
  4077. observations.delete(target);
  4078. if (!observations.size) {
  4079. this.controller_.removeObserver(this);
  4080. }
  4081. };
  4082. /**
  4083. * Stops observing all elements.
  4084. *
  4085. * @returns {void}
  4086. */
  4087. ResizeObserverSPI.prototype.disconnect = function () {
  4088. this.clearActive();
  4089. this.observations_.clear();
  4090. this.controller_.removeObserver(this);
  4091. };
  4092. /**
  4093. * Collects observation instances the associated element of which has changed
  4094. * it's content rectangle.
  4095. *
  4096. * @returns {void}
  4097. */
  4098. ResizeObserverSPI.prototype.gatherActive = function () {
  4099. var _this = this;
  4100. this.clearActive();
  4101. this.observations_.forEach(function (observation) {
  4102. if (observation.isActive()) {
  4103. _this.activeObservations_.push(observation);
  4104. }
  4105. });
  4106. };
  4107. /**
  4108. * Invokes initial callback function with a list of ResizeObserverEntry
  4109. * instances collected from active resize observations.
  4110. *
  4111. * @returns {void}
  4112. */
  4113. ResizeObserverSPI.prototype.broadcastActive = function () {
  4114. // Do nothing if observer doesn't have active observations.
  4115. if (!this.hasActive()) {
  4116. return;
  4117. }
  4118. var ctx = this.callbackCtx_;
  4119. // Create ResizeObserverEntry instance for every active observation.
  4120. var entries = this.activeObservations_.map(function (observation) {
  4121. return new ResizeObserverEntry(observation.target, observation.broadcastRect());
  4122. });
  4123. this.callback_.call(ctx, entries, ctx);
  4124. this.clearActive();
  4125. };
  4126. /**
  4127. * Clears the collection of active observations.
  4128. *
  4129. * @returns {void}
  4130. */
  4131. ResizeObserverSPI.prototype.clearActive = function () {
  4132. this.activeObservations_.splice(0);
  4133. };
  4134. /**
  4135. * Tells whether observer has active observations.
  4136. *
  4137. * @returns {boolean}
  4138. */
  4139. ResizeObserverSPI.prototype.hasActive = function () {
  4140. return this.activeObservations_.length > 0;
  4141. };
  4142. return ResizeObserverSPI;
  4143. }());
  4144. // Registry of internal observers. If WeakMap is not available use current shim
  4145. // for the Map collection as it has all required methods and because WeakMap
  4146. // can't be fully polyfilled anyway.
  4147. var observers = typeof WeakMap !== 'undefined' ? new WeakMap() : new MapShim();
  4148. /**
  4149. * ResizeObserver API. Encapsulates the ResizeObserver SPI implementation
  4150. * exposing only those methods and properties that are defined in the spec.
  4151. */
  4152. var ResizeObserver = /** @class */ (function () {
  4153. /**
  4154. * Creates a new instance of ResizeObserver.
  4155. *
  4156. * @param {ResizeObserverCallback} callback - Callback that is invoked when
  4157. * dimensions of the observed elements change.
  4158. */
  4159. function ResizeObserver(callback) {
  4160. if (!(this instanceof ResizeObserver)) {
  4161. throw new TypeError('Cannot call a class as a function.');
  4162. }
  4163. if (!arguments.length) {
  4164. throw new TypeError('1 argument required, but only 0 present.');
  4165. }
  4166. var controller = ResizeObserverController.getInstance();
  4167. var observer = new ResizeObserverSPI(callback, controller, this);
  4168. observers.set(this, observer);
  4169. }
  4170. return ResizeObserver;
  4171. }());
  4172. // Expose public methods of ResizeObserver.
  4173. [
  4174. 'observe',
  4175. 'unobserve',
  4176. 'disconnect'
  4177. ].forEach(function (method) {
  4178. ResizeObserver.prototype[method] = function () {
  4179. var _a;
  4180. return (_a = observers.get(this))[method].apply(_a, arguments);
  4181. };
  4182. });
  4183. var index = (function () {
  4184. // Export existing implementation if available.
  4185. if (typeof global$1.ResizeObserver !== 'undefined') {
  4186. return global$1.ResizeObserver;
  4187. }
  4188. return ResizeObserver;
  4189. })();
  4190. /**
  4191. * Main component
  4192. *
  4193. * @copyright Rafal Pospiech <https://neuronet.io>
  4194. * @author Rafal Pospiech <neuronet.io@gmail.com>
  4195. * @package gantt-schedule-timeline-calendar
  4196. * @license AGPL-3.0 (https://github.com/neuronetio/gantt-schedule-timeline-calendar/blob/master/LICENSE)
  4197. * @link https://github.com/neuronetio/gantt-schedule-timeline-calendar
  4198. */
  4199. function Main(vido, props = {}) {
  4200. const { api, state, onDestroy, Actions, update, createComponent, html, StyleMap, schedule } = vido;
  4201. const componentName = api.name;
  4202. // Initialize plugins
  4203. onDestroy(state.subscribe('config.plugins', plugins => {
  4204. if (typeof plugins !== 'undefined' && Array.isArray(plugins)) {
  4205. for (const initializePlugin of plugins) {
  4206. const destroyPlugin = initializePlugin(vido);
  4207. if (typeof destroyPlugin === 'function') {
  4208. onDestroy(destroyPlugin);
  4209. }
  4210. else if (destroyPlugin && destroyPlugin.hasOwnProperty('destroy')) {
  4211. destroyPlugin.destroy();
  4212. }
  4213. }
  4214. }
  4215. }));
  4216. const componentSubs = [];
  4217. let ListComponent;
  4218. componentSubs.push(state.subscribe('config.components.List', value => (ListComponent = value)));
  4219. let ChartComponent;
  4220. componentSubs.push(state.subscribe('config.components.Chart', value => (ChartComponent = value)));
  4221. const List = createComponent(ListComponent);
  4222. onDestroy(List.destroy);
  4223. const Chart = createComponent(ChartComponent);
  4224. onDestroy(Chart.destroy);
  4225. onDestroy(() => {
  4226. componentSubs.forEach(unsub => unsub());
  4227. });
  4228. let wrapper;
  4229. onDestroy(state.subscribe('config.wrappers.Main', value => (wrapper = value)));
  4230. const componentActions = api.getActions('main');
  4231. let className, classNameVerticalScroll;
  4232. const styleMap = new StyleMap({}), verticalScrollStyleMap = new StyleMap({}), verticalScrollAreaStyleMap = new StyleMap({});
  4233. let verticalScrollBarElement;
  4234. let rowsHeight = 0;
  4235. let resizerActive = false;
  4236. /**
  4237. * Update class names
  4238. * @param {object} classNames
  4239. */
  4240. const updateClassNames = classNames => {
  4241. const config = state.get('config');
  4242. className = api.getClass(componentName, { config });
  4243. if (resizerActive) {
  4244. className += ` ${componentName}__list-column-header-resizer--active`;
  4245. }
  4246. classNameVerticalScroll = api.getClass('vertical-scroll', { config });
  4247. update();
  4248. };
  4249. onDestroy(state.subscribe('config.classNames', updateClassNames));
  4250. /**
  4251. * Height change
  4252. */
  4253. function heightChange() {
  4254. const config = state.get('config');
  4255. const scrollBarHeight = state.get('_internal.scrollBarHeight');
  4256. const height = config.height - config.headerHeight - scrollBarHeight;
  4257. state.update('_internal.height', height);
  4258. styleMap.style['--height'] = config.height + 'px';
  4259. verticalScrollStyleMap.style.height = height + 'px';
  4260. verticalScrollStyleMap.style.width = scrollBarHeight + 'px';
  4261. verticalScrollStyleMap.style['margin-top'] = config.headerHeight + 'px';
  4262. update();
  4263. }
  4264. onDestroy(state.subscribeAll(['config.height', 'config.headerHeight', '_internal.scrollBarHeight'], heightChange));
  4265. /**
  4266. * Resizer active change
  4267. * @param {boolean} active
  4268. */
  4269. function resizerActiveChange(active) {
  4270. resizerActive = active;
  4271. className = api.getClass(api.name);
  4272. if (resizerActive) {
  4273. className += ` ${api.name}__list-column-header-resizer--active`;
  4274. }
  4275. update();
  4276. }
  4277. onDestroy(state.subscribe('_internal.list.columns.resizer.active', resizerActiveChange));
  4278. /**
  4279. * Generate tree
  4280. * @param {object} bulk
  4281. * @param {object} eventInfo
  4282. */
  4283. function generateTree(bulk, eventInfo) {
  4284. if (state.get('_internal.flatTreeMap').length && eventInfo.type === 'subscribe') {
  4285. return;
  4286. }
  4287. const configRows = state.get('config.list.rows');
  4288. const rows = [];
  4289. for (const rowId in configRows) {
  4290. rows.push(configRows[rowId]);
  4291. }
  4292. api.fillEmptyRowValues(rows);
  4293. const configItems = state.get('config.chart.items');
  4294. const items = [];
  4295. for (const itemId in configItems) {
  4296. items.push(configItems[itemId]);
  4297. }
  4298. api.prepareItems(items);
  4299. const treeMap = api.makeTreeMap(rows, items);
  4300. state.update('_internal.treeMap', treeMap);
  4301. const flatTreeMapById = api.getFlatTreeMapById(treeMap);
  4302. state.update('_internal.flatTreeMapById', flatTreeMapById);
  4303. const flatTreeMap = api.flattenTreeMap(treeMap);
  4304. state.update('_internal.flatTreeMap', flatTreeMap);
  4305. update();
  4306. }
  4307. onDestroy(state.subscribeAll(['config.list.rows;', 'config.chart.items;'], generateTree));
  4308. onDestroy(state.subscribeAll(['config.list.rows.*.parentId', 'config.chart.items.*.rowId'], generateTree, { bulk: true }));
  4309. function prepareExpanded() {
  4310. const configRows = state.get('config.list.rows');
  4311. const rowsWithParentsExpanded = api.getRowsFromIds(api.getRowsWithParentsExpanded(state.get('_internal.flatTreeMap'), state.get('_internal.flatTreeMapById'), configRows), configRows);
  4312. rowsHeight = api.getRowsHeight(rowsWithParentsExpanded);
  4313. state.update('_internal.list.rowsHeight', rowsHeight);
  4314. state.update('_internal.list.rowsWithParentsExpanded', rowsWithParentsExpanded);
  4315. update();
  4316. }
  4317. onDestroy(state.subscribeAll(['config.list.rows.*.expanded', '_internal.treeMap;', 'config.list.rows.*.height'], prepareExpanded, { bulk: true }));
  4318. /**
  4319. * Generate visible rows
  4320. */
  4321. function generateVisibleRowsAndItems() {
  4322. const { visibleRows, compensation } = api.getVisibleRowsAndCompensation(state.get('_internal.list.rowsWithParentsExpanded'));
  4323. const smoothScroll = state.get('config.scroll.smooth');
  4324. const currentVisibleRows = state.get('_internal.list.visibleRows');
  4325. let shouldUpdate = true;
  4326. state.update('config.scroll.compensation.y', smoothScroll ? -compensation : 0);
  4327. if (visibleRows.length !== currentVisibleRows.length) {
  4328. shouldUpdate = true;
  4329. }
  4330. else if (visibleRows.length) {
  4331. shouldUpdate = visibleRows.some((row, index) => {
  4332. if (typeof currentVisibleRows[index] === 'undefined') {
  4333. return true;
  4334. }
  4335. return row.id !== currentVisibleRows[index].id;
  4336. });
  4337. }
  4338. if (shouldUpdate) {
  4339. state.update('_internal.list.visibleRows', visibleRows);
  4340. }
  4341. const visibleItems = [];
  4342. for (const row of visibleRows) {
  4343. for (const item of row._internal.items) {
  4344. visibleItems.push(item);
  4345. }
  4346. }
  4347. state.update('_internal.chart.visibleItems', visibleItems);
  4348. update();
  4349. }
  4350. onDestroy(state.subscribeAll(['_internal.list.rowsWithParentsExpanded;', 'config.scroll.top', 'config.chart.items'], generateVisibleRowsAndItems, { bulk: true }));
  4351. let elementScrollTop = 0;
  4352. function onVisibleRowsChange() {
  4353. const top = state.get('config.scroll.top');
  4354. verticalScrollAreaStyleMap.style.width = '1px';
  4355. verticalScrollAreaStyleMap.style.height = rowsHeight + 'px';
  4356. if (elementScrollTop !== top && verticalScrollBarElement) {
  4357. elementScrollTop = top;
  4358. verticalScrollBarElement.scrollTop = top;
  4359. }
  4360. update();
  4361. }
  4362. onDestroy(state.subscribe('_internal.list.visibleRows;', onVisibleRowsChange));
  4363. /**
  4364. * Generate and add period dates
  4365. * @param {string} period
  4366. * @param {object} internalTime
  4367. */
  4368. const generatePeriodDates = (period, internalTime) => {
  4369. const dates = [];
  4370. let leftGlobal = internalTime.leftGlobal;
  4371. const timePerPixel = internalTime.timePerPixel;
  4372. let startOfLeft = api.time
  4373. .date(leftGlobal)
  4374. .startOf(period)
  4375. .valueOf();
  4376. if (startOfLeft < leftGlobal)
  4377. startOfLeft = leftGlobal;
  4378. let sub = leftGlobal - startOfLeft;
  4379. let subPx = sub / timePerPixel;
  4380. let leftPx = 0;
  4381. const diff = Math.ceil(api.time
  4382. .date(internalTime.rightGlobal)
  4383. .endOf(period)
  4384. .diff(api.time.date(leftGlobal).startOf(period), period, true));
  4385. for (let i = 0; i < diff; i++) {
  4386. const date = {
  4387. sub,
  4388. subPx,
  4389. leftGlobal,
  4390. rightGlobal: api.time
  4391. .date(leftGlobal)
  4392. .endOf(period)
  4393. .valueOf(),
  4394. width: 0,
  4395. leftPx: 0,
  4396. rightPx: 0,
  4397. period
  4398. };
  4399. date.width = (date.rightGlobal - date.leftGlobal + sub) / timePerPixel;
  4400. date.leftPx = leftPx;
  4401. leftPx += date.width;
  4402. date.rightPx = leftPx;
  4403. dates.push(date);
  4404. leftGlobal = date.rightGlobal + 1;
  4405. sub = 0;
  4406. subPx = 0;
  4407. }
  4408. return dates;
  4409. };
  4410. function triggerLoadedEvent() {
  4411. if (state.get('_internal.loadedEventTriggered'))
  4412. return;
  4413. Promise.resolve().then(() => {
  4414. const element = state.get('_internal.elements.main');
  4415. const parent = element.parentNode;
  4416. const event = new Event('gstc-loaded');
  4417. element.dispatchEvent(event);
  4418. parent.dispatchEvent(event);
  4419. });
  4420. state.update('_internal.loadedEventTriggered', true);
  4421. }
  4422. function limitGlobalAndSetCenter(time) {
  4423. if (time.leftGlobal < time.finalFrom)
  4424. time.leftGlobal = time.finalFrom;
  4425. if (time.rightGlobal > time.finalTo)
  4426. time.rightGlobal = time.finalTo;
  4427. time.centerGlobal = time.leftGlobal + Math.round((time.rightGlobal - time.leftGlobal) / 2);
  4428. return time;
  4429. }
  4430. function guessPeriod(time, calendar) {
  4431. if (!time.zoom)
  4432. return time;
  4433. for (const level of calendar.levels) {
  4434. const formatting = level.formats.find(format => +time.zoom <= +format.zoomTo);
  4435. if (formatting && level.main) {
  4436. time.period = formatting.period;
  4437. }
  4438. }
  4439. return time;
  4440. }
  4441. function updateLevels(time, calendar) {
  4442. time.levels = [];
  4443. let index = 0;
  4444. for (const level of calendar.levels) {
  4445. const formatting = level.formats.find(format => +time.zoom <= +format.zoomTo);
  4446. if (level.main) {
  4447. time.format = formatting;
  4448. time.level = index;
  4449. }
  4450. if (formatting) {
  4451. time.levels.push(generatePeriodDates(formatting.period, time));
  4452. }
  4453. index++;
  4454. }
  4455. }
  4456. let working = false;
  4457. function recalculateTimes(reason) {
  4458. if (working)
  4459. return;
  4460. working = true;
  4461. const configTime = state.get('config.chart.time');
  4462. const chartWidth = state.get('_internal.chart.dimensions.width');
  4463. const calendar = state.get('config.chart.calendar');
  4464. const oldTime = Object.assign({}, state.get('_internal.chart.time'));
  4465. let time = api.mergeDeep({}, configTime);
  4466. if ((!time.from || !time.to) && !Object.keys(state.get('config.chart.items')).length) {
  4467. return;
  4468. }
  4469. let mainLevel = calendar.levels.find(level => level.main);
  4470. if (!mainLevel) {
  4471. throw new Error('Main calendar level not found (config.chart.calendar.levels).');
  4472. }
  4473. if (!time.calculatedZoomMode) {
  4474. if (time.period !== oldTime.period) {
  4475. let periodFormat = mainLevel.formats.find(format => format.period === time.period && format.default);
  4476. if (periodFormat) {
  4477. time.zoom = periodFormat.zoomTo;
  4478. }
  4479. }
  4480. guessPeriod(time, calendar);
  4481. }
  4482. // If _internal.chart.time (leftGlobal, centerGlobal, rightGlobal, from , to) was changed
  4483. // then we need to apply those values - no recalculation is needed (values form plugins etc)
  4484. const justApply = ['leftGlobal', 'centerGlobal', 'rightGlobal', 'from', 'to'].includes(reason.name);
  4485. if (justApply) {
  4486. time = Object.assign(Object.assign({}, time), { leftGlobal: configTime.leftGlobal, centerGlobal: configTime.centerGlobal, rightGlobal: configTime.rightGlobal, from: configTime.from, to: configTime.to });
  4487. }
  4488. let scrollLeft = 0;
  4489. // source of everything = time.timePerPixel
  4490. if (time.calculatedZoomMode && chartWidth) {
  4491. time.finalFrom = time.from;
  4492. time.finalTo = time.to;
  4493. time.totalViewDurationMs = api.time.date(time.finalTo).diff(time.finalFrom, 'milliseconds');
  4494. time.timePerPixel = time.totalViewDurationMs / chartWidth;
  4495. time.zoom = Math.log(time.timePerPixel) / Math.log(2);
  4496. guessPeriod(time, calendar);
  4497. time.totalViewDurationPx = Math.round(time.totalViewDurationMs / time.timePerPixel);
  4498. time.leftGlobal = time.from;
  4499. time.rightGlobal = time.to;
  4500. }
  4501. else {
  4502. time.timePerPixel = Math.pow(2, time.zoom);
  4503. time = api.time.recalculateFromTo(time);
  4504. time.totalViewDurationMs = api.time.date(time.finalTo).diff(time.finalFrom, 'milliseconds');
  4505. time.totalViewDurationPx = Math.round(time.totalViewDurationMs / time.timePerPixel);
  4506. scrollLeft = state.get('config.scroll.left');
  4507. }
  4508. if (!justApply && !time.calculatedZoomMode) {
  4509. // If time.zoom (or time.period) has been changed
  4510. // then we need to recalculate basing on time.centerGlobal
  4511. // and update scroll left
  4512. // if not then we need to calculate from scroll left
  4513. // because change was triggered by scroll
  4514. if (time.zoom !== oldTime.zoom && oldTime.centerGlobal) {
  4515. const chartWidthInMs = chartWidth * time.timePerPixel;
  4516. const halfChartInMs = Math.round(chartWidthInMs / 2);
  4517. time.leftGlobal = oldTime.centerGlobal - halfChartInMs;
  4518. time.rightGlobal = time.leftGlobal + chartWidthInMs;
  4519. scrollLeft = (time.leftGlobal - time.finalFrom) / time.timePerPixel;
  4520. scrollLeft = api.limitScrollLeft(time.totalViewDurationPx, chartWidth, scrollLeft);
  4521. }
  4522. else {
  4523. time.leftGlobal = scrollLeft * time.timePerPixel + time.finalFrom;
  4524. time.rightGlobal = time.leftGlobal + chartWidth * time.timePerPixel;
  4525. }
  4526. }
  4527. limitGlobalAndSetCenter(time);
  4528. time.leftInner = time.leftGlobal - time.finalFrom;
  4529. time.rightInner = time.rightGlobal - time.finalFrom;
  4530. time.leftPx = time.leftInner / time.timePerPixel;
  4531. time.rightPx = time.rightInner / time.timePerPixel;
  4532. updateLevels(time, calendar);
  4533. let xCompensation = 0;
  4534. if (time.levels[time.level] && time.levels[time.level].length !== 0) {
  4535. xCompensation = time.levels[time.level][0].subPx;
  4536. }
  4537. state.update(`_internal.chart.time`, time);
  4538. state.update('config.scroll.compensation.x', xCompensation);
  4539. state.update('config.chart.time', configTime => {
  4540. configTime.zoom = time.zoom;
  4541. configTime.period = time.format.period;
  4542. configTime.leftGlobal = time.leftGlobal;
  4543. configTime.centerGlobal = time.centerGlobal;
  4544. configTime.rightGlobal = time.rightGlobal;
  4545. configTime.from = time.from;
  4546. configTime.to = time.to;
  4547. configTime.finalFrom = time.finalFrom;
  4548. configTime.finalTo = time.finalTo;
  4549. return configTime;
  4550. });
  4551. state.update('config.scroll.left', scrollLeft);
  4552. update().then(() => {
  4553. if (!state.get('_internal.loaded.time')) {
  4554. state.update('_internal.loaded.time', true);
  4555. }
  4556. });
  4557. working = false;
  4558. }
  4559. const recalculationTriggerCache = {
  4560. initialized: false,
  4561. zoom: 0,
  4562. period: '',
  4563. scrollLeft: 0,
  4564. chartWidth: 0,
  4565. leftGlobal: 0,
  4566. centerGlobal: 0,
  4567. rightGlobal: 0,
  4568. from: 0,
  4569. to: 0
  4570. };
  4571. function recalculationIsNeeded() {
  4572. const configTime = state.get('config.chart.time');
  4573. const scrollLeft = state.get('config.scroll.left');
  4574. const chartWidth = state.get('_internal.chart.dimensions.width');
  4575. const cache = Object.assign({}, recalculationTriggerCache);
  4576. recalculationTriggerCache.zoom = configTime.zoom;
  4577. recalculationTriggerCache.period = configTime.period;
  4578. recalculationTriggerCache.leftGlobal = configTime.leftGlobal;
  4579. recalculationTriggerCache.centerGlobal = configTime.centerGlobal;
  4580. recalculationTriggerCache.rightGlobal = configTime.rightGlobal;
  4581. recalculationTriggerCache.from = configTime.from;
  4582. recalculationTriggerCache.to = configTime.to;
  4583. recalculationTriggerCache.scrollLeft = scrollLeft;
  4584. recalculationTriggerCache.chartWidth = chartWidth;
  4585. if (!recalculationTriggerCache.initialized) {
  4586. recalculationTriggerCache.initialized = true;
  4587. return { name: 'all' };
  4588. }
  4589. if (configTime.zoom !== cache.zoom)
  4590. return { name: 'zoom', oldValue: cache.zoom, newValue: configTime.zoom };
  4591. if (configTime.period !== cache.period)
  4592. return { name: 'period', oldValue: cache.period, newValue: configTime.period };
  4593. if (configTime.leftGlobal !== cache.leftGlobal)
  4594. return { name: 'leftGlobal', oldValue: cache.leftGlobal, newValue: configTime.leftGlobal };
  4595. if (configTime.centerGlobal !== cache.centerGlobal)
  4596. return { name: 'centerGlobal', oldValue: cache.centerGlobal, newValue: configTime.centerGlobal };
  4597. if (configTime.rightGlobal !== cache.rightGlobal)
  4598. return { name: 'rightGlobal', oldValue: cache.rightGlobal, newValue: configTime.rightGlobal };
  4599. if (configTime.from !== cache.from)
  4600. return { name: 'from', oldValue: cache.from, newValue: configTime.from };
  4601. if (configTime.to !== cache.to)
  4602. return { name: 'to', oldValue: cache.to, newValue: configTime.to };
  4603. if (scrollLeft !== cache.scrollLeft)
  4604. return { name: 'scroll', oldValue: cache.scrollLeft, newValue: scrollLeft };
  4605. if (chartWidth !== cache.chartWidth)
  4606. return { name: 'chartWidth', oldValue: cache.chartWidth, newValue: chartWidth };
  4607. return false;
  4608. }
  4609. onDestroy(state.subscribeAll(['config.chart.time', 'config.chart.calendar.levels', 'config.scroll.left', '_internal.chart.dimensions.width'], () => {
  4610. let reason = recalculationIsNeeded();
  4611. if (reason)
  4612. recalculateTimes(reason);
  4613. }, { bulk: true }));
  4614. // When time.from and time.to is not specified and items are reloaded;
  4615. // check if item is outside current time scope and extend it if needed
  4616. onDestroy(state.subscribe('config.chart.items.*.time', items => {
  4617. recalculateTimes({ name: 'items' });
  4618. }, { bulk: true }));
  4619. if (state.get('config.usageStatistics') === true && !localStorage.getItem('gstcus')) {
  4620. try {
  4621. fetch('https://gstc-us.neuronet.io/', {
  4622. method: 'POST',
  4623. cache: 'force-cache',
  4624. mode: 'cors',
  4625. credentials: 'omit',
  4626. redirect: 'follow',
  4627. body: JSON.stringify({ location: { href: location.href, host: location.host } })
  4628. }).catch(e => { });
  4629. localStorage.setItem('gstcus', 'true');
  4630. }
  4631. catch (e) { }
  4632. }
  4633. let scrollTop = 0;
  4634. let propagate = true;
  4635. onDestroy(state.subscribe('config.scroll.propagate', prpgt => (propagate = prpgt)));
  4636. /**
  4637. * Handle scroll Event
  4638. * @param {MouseEvent} event
  4639. */
  4640. function handleEvent(event) {
  4641. if (!propagate) {
  4642. event.stopPropagation();
  4643. event.preventDefault();
  4644. }
  4645. if (event.type === 'scroll') {
  4646. // @ts-ignore
  4647. const top = event.target.scrollTop;
  4648. /**
  4649. * Handle on scroll event
  4650. * @param {object} scroll
  4651. * @returns {object} scroll
  4652. */
  4653. const handleOnScroll = scroll => {
  4654. scroll.top = top;
  4655. scrollTop = scroll.top;
  4656. const scrollInner = state.get('_internal.elements.vertical-scroll-inner');
  4657. if (scrollInner) {
  4658. const scrollHeight = scrollInner.clientHeight;
  4659. scroll.percent.top = scroll.top / scrollHeight;
  4660. }
  4661. return scroll;
  4662. };
  4663. if (scrollTop !== top)
  4664. state.update('config.scroll', handleOnScroll, {
  4665. only: ['top', 'percent.top']
  4666. });
  4667. }
  4668. }
  4669. const onScroll = {
  4670. handleEvent,
  4671. passive: false,
  4672. capture: false
  4673. };
  4674. const dimensions = { width: 0, height: 0 };
  4675. let ro;
  4676. /**
  4677. * Resize action
  4678. * @param {Element} element
  4679. */
  4680. class ResizeAction {
  4681. constructor(element) {
  4682. if (!ro) {
  4683. ro = new index((entries, observer) => {
  4684. const width = element.clientWidth;
  4685. const height = element.clientHeight;
  4686. if (dimensions.width !== width || dimensions.height !== height) {
  4687. dimensions.width = width;
  4688. dimensions.height = height;
  4689. state.update('_internal.dimensions', dimensions);
  4690. }
  4691. });
  4692. ro.observe(element);
  4693. state.update('_internal.elements.main', element);
  4694. }
  4695. }
  4696. update() { }
  4697. destroy(element) {
  4698. ro.unobserve(element);
  4699. }
  4700. }
  4701. if (!componentActions.includes(ResizeAction)) {
  4702. componentActions.push(ResizeAction);
  4703. }
  4704. onDestroy(() => {
  4705. ro.disconnect();
  4706. });
  4707. /**
  4708. * Bind scroll element
  4709. * @param {HTMLElement} element
  4710. */
  4711. function bindScrollElement(element) {
  4712. if (!verticalScrollBarElement) {
  4713. verticalScrollBarElement = element;
  4714. state.update('_internal.elements.vertical-scroll', element);
  4715. }
  4716. }
  4717. onDestroy(state.subscribeAll(['_internal.loaded', '_internal.chart.time.totalViewDurationPx'], () => {
  4718. if (state.get('_internal.loadedEventTriggered'))
  4719. return;
  4720. const loaded = state.get('_internal.loaded');
  4721. if (loaded.main && loaded.chart && loaded.time && loaded['horizontal-scroll-inner']) {
  4722. const scroll = state.get('_internal.elements.horizontal-scroll-inner');
  4723. const width = state.get('_internal.chart.time.totalViewDurationPx');
  4724. if (scroll && scroll.clientWidth === Math.round(width)) {
  4725. setTimeout(triggerLoadedEvent, 0);
  4726. }
  4727. }
  4728. }));
  4729. function LoadedEventAction() {
  4730. state.update('_internal.loaded.main', true);
  4731. }
  4732. if (!componentActions.includes(LoadedEventAction))
  4733. componentActions.push(LoadedEventAction);
  4734. /**
  4735. * Bind scroll inner element
  4736. * @param {Element} element
  4737. */
  4738. function bindScrollInnerElement(element) {
  4739. if (!state.get('_internal.elements.vertical-scroll-inner'))
  4740. state.update('_internal.elements.vertical-scroll-inner', element);
  4741. if (!state.get('_internal.loaded.vertical-scroll-inner'))
  4742. state.update('_internal.loaded.vertical-scroll-inner', true);
  4743. }
  4744. const actionProps = Object.assign(Object.assign({}, props), { api, state });
  4745. const mainActions = Actions.create(componentActions, actionProps);
  4746. const verticalScrollActions = Actions.create([bindScrollElement]);
  4747. const verticalScrollAreaActions = Actions.create([bindScrollInnerElement]);
  4748. return templateProps => wrapper(html `
  4749. <div
  4750. data-info-url="https://github.com/neuronetio/gantt-schedule-timeline-calendar"
  4751. class=${className}
  4752. style=${styleMap}
  4753. @scroll=${onScroll}
  4754. @wheel=${onScroll}
  4755. data-actions=${mainActions}
  4756. >
  4757. ${List.html()}${Chart.html()}
  4758. <div
  4759. class=${classNameVerticalScroll}
  4760. style=${verticalScrollStyleMap}
  4761. @scroll=${onScroll}
  4762. @wheel=${onScroll}
  4763. data-actions=${verticalScrollActions}
  4764. >
  4765. <div style=${verticalScrollAreaStyleMap} data-actions=${verticalScrollAreaActions} />
  4766. </div>
  4767. </div>
  4768. `, { props, vido, templateProps });
  4769. }
  4770. /**
  4771. * List component
  4772. *
  4773. * @copyright Rafal Pospiech <https://neuronet.io>
  4774. * @author Rafal Pospiech <neuronet.io@gmail.com>
  4775. * @package gantt-schedule-timeline-calendar
  4776. * @license AGPL-3.0 (https://github.com/neuronetio/gantt-schedule-timeline-calendar/blob/master/LICENSE)
  4777. * @link https://github.com/neuronetio/gantt-schedule-timeline-calendar
  4778. */
  4779. function List(vido, props = {}) {
  4780. const { api, state, onDestroy, Actions, update, reuseComponents, html, schedule, StyleMap, cache } = vido;
  4781. const componentName = 'list';
  4782. const componentActions = api.getActions(componentName);
  4783. let wrapper;
  4784. onDestroy(state.subscribe('config.wrappers.List', value => (wrapper = value)));
  4785. let ListColumnComponent;
  4786. const listColumnUnsub = state.subscribe('config.components.ListColumn', value => (ListColumnComponent = value));
  4787. function renderExpanderIcons() {
  4788. const icons = state.get('config.list.expander.icons');
  4789. const rendered = {};
  4790. for (const iconName in icons) {
  4791. const html = icons[iconName];
  4792. rendered[iconName] = api.getSVGIconSrc(html);
  4793. }
  4794. state.update('_internal.list.expander.icons', rendered);
  4795. }
  4796. renderExpanderIcons();
  4797. function renderToggleIcons() {
  4798. const toggleIconsSrc = {
  4799. open: '',
  4800. close: ''
  4801. };
  4802. const icons = state.get('config.list.toggle.icons');
  4803. for (const iconName in icons) {
  4804. const html = icons[iconName];
  4805. toggleIconsSrc[iconName] = api.getSVGIconSrc(html);
  4806. }
  4807. state.update('_internal.list.toggle.icons', toggleIconsSrc);
  4808. }
  4809. renderToggleIcons();
  4810. let className;
  4811. let list, percent;
  4812. function onListChange() {
  4813. list = state.get('config.list');
  4814. percent = list.columns.percent;
  4815. update();
  4816. }
  4817. onDestroy(state.subscribe('config.list', onListChange));
  4818. onDestroy(state.subscribe('config.classNames', () => {
  4819. className = api.getClass(componentName, { list });
  4820. update();
  4821. }));
  4822. let listColumns = [];
  4823. function onListColumnsDataChange(data) {
  4824. const destroy = reuseComponents(listColumns, Object.values(data), column => ({ columnId: column.id }), ListColumnComponent);
  4825. update();
  4826. return destroy;
  4827. }
  4828. onDestroy(state.subscribe('config.list.columns.data;', onListColumnsDataChange));
  4829. const styleMap = new StyleMap({
  4830. height: '',
  4831. '--expander-padding-width': '',
  4832. '--expander-size': ''
  4833. });
  4834. onDestroy(state.subscribeAll(['config.height', 'config.list.expander'], bulk => {
  4835. const expander = state.get('config.list.expander');
  4836. styleMap.style['height'] = state.get('config.height') + 'px';
  4837. styleMap.style['--expander-padding-width'] = expander.padding + 'px';
  4838. styleMap.style['--expander-size'] = expander.size + 'px';
  4839. update();
  4840. }));
  4841. onDestroy(() => {
  4842. listColumns.forEach(listColumn => listColumn.destroy());
  4843. listColumnUnsub();
  4844. });
  4845. function onScroll(event) {
  4846. event.stopPropagation();
  4847. event.preventDefault();
  4848. if (event.type === 'scroll') {
  4849. state.update('config.scroll.top', event.target.scrollTop);
  4850. }
  4851. else {
  4852. const wheel = api.normalizeMouseWheelEvent(event);
  4853. state.update('config.scroll.top', top => {
  4854. const rowsHeight = state.get('_internal.list.rowsHeight');
  4855. const internalHeight = state.get('_internal.height');
  4856. return api.limitScrollTop(rowsHeight, internalHeight, (top += wheel.y * state.get('config.scroll.yMultiplier')));
  4857. });
  4858. }
  4859. }
  4860. let width;
  4861. function getWidth(element) {
  4862. if (!width) {
  4863. width = element.clientWidth;
  4864. if (percent === 0) {
  4865. width = 0;
  4866. }
  4867. state.update('_internal.list.width', width);
  4868. }
  4869. }
  4870. class ListAction {
  4871. constructor(element, data) {
  4872. data.state.update('_internal.elements.list', element);
  4873. getWidth(element);
  4874. }
  4875. update(element) {
  4876. return getWidth(element);
  4877. }
  4878. }
  4879. componentActions.push(ListAction);
  4880. const actions = Actions.create(componentActions, Object.assign(Object.assign({}, props), { api, state }));
  4881. return templateProps => wrapper(cache(list.columns.percent > 0
  4882. ? html `
  4883. <div class=${className} data-actions=${actions} style=${styleMap} @scroll=${onScroll} @wheel=${onScroll}>
  4884. ${listColumns.map(c => c.html())}
  4885. </div>
  4886. `
  4887. : ''), { vido, props: {}, templateProps });
  4888. }
  4889. /**
  4890. * ListColumn component
  4891. *
  4892. * @copyright Rafal Pospiech <https://neuronet.io>
  4893. * @author Rafal Pospiech <neuronet.io@gmail.com>
  4894. * @package gantt-schedule-timeline-calendar
  4895. * @license AGPL-3.0 (https://github.com/neuronetio/gantt-schedule-timeline-calendar/blob/master/LICENSE)
  4896. * @link https://github.com/neuronetio/gantt-schedule-timeline-calendar
  4897. */
  4898. /**
  4899. * Bind element action
  4900. */
  4901. class BindElementAction {
  4902. constructor(element, data) {
  4903. let shouldUpdate = false;
  4904. let elements = data.state.get('_internal.elements.list-columns');
  4905. if (typeof elements === 'undefined') {
  4906. elements = [];
  4907. shouldUpdate = true;
  4908. }
  4909. if (!elements.includes(element)) {
  4910. elements.push(element);
  4911. shouldUpdate = true;
  4912. }
  4913. if (shouldUpdate)
  4914. data.state.update('_internal.elements.list-columns', elements);
  4915. }
  4916. destroy(element, data) {
  4917. data.state.update('_internal.elements.list-columns', elements => {
  4918. return elements.filter(el => el !== element);
  4919. });
  4920. }
  4921. }
  4922. function ListColumn(vido, props) {
  4923. const { api, state, onDestroy, onChange, Actions, update, createComponent, reuseComponents, html, StyleMap } = vido;
  4924. let wrapper;
  4925. onDestroy(state.subscribe('config.wrappers.ListColumn', value => (wrapper = value)));
  4926. const componentsSub = [];
  4927. let ListColumnRowComponent;
  4928. componentsSub.push(state.subscribe('config.components.ListColumnRow', value => (ListColumnRowComponent = value)));
  4929. let ListColumnHeaderComponent;
  4930. componentsSub.push(state.subscribe('config.components.ListColumnHeader', value => (ListColumnHeaderComponent = value)));
  4931. const actionProps = Object.assign(Object.assign({}, props), { api, state });
  4932. const componentName = 'list-column';
  4933. const rowsComponentName = componentName + '-rows';
  4934. const componentActions = api.getActions(componentName);
  4935. const rowsActions = api.getActions(rowsComponentName);
  4936. let className, classNameContainer, calculatedWidth;
  4937. const widthStyleMap = new StyleMap({ width: '', '--width': '' });
  4938. const containerStyleMap = new StyleMap({ width: '', height: '' });
  4939. const scrollCompensationStyleMap = new StyleMap({ width: '', height: '' });
  4940. let column, columnPath = `config.list.columns.data.${props.columnId}`;
  4941. let columnSub = state.subscribe(columnPath, function columnChanged(val) {
  4942. column = val;
  4943. update();
  4944. });
  4945. let width;
  4946. function calculateStyle() {
  4947. const list = state.get('config.list');
  4948. const compensationY = state.get('config.scroll.compensation.y');
  4949. calculatedWidth = list.columns.data[column.id].width * list.columns.percent * 0.01;
  4950. width = calculatedWidth;
  4951. const height = state.get('_internal.height');
  4952. widthStyleMap.style.width = width + 'px';
  4953. widthStyleMap.style['--width'] = width + 'px';
  4954. containerStyleMap.style.height = height + 'px';
  4955. scrollCompensationStyleMap.style.height = height + Math.abs(compensationY) + 'px';
  4956. scrollCompensationStyleMap.style.transform = `translate(0px, ${compensationY}px)`;
  4957. }
  4958. let styleSub = state.subscribeAll([
  4959. 'config.list.columns.percent',
  4960. 'config.list.columns.resizer.width',
  4961. `config.list.columns.data.${column.id}.width`,
  4962. '_internal.chart.dimensions.width',
  4963. '_internal.height',
  4964. 'config.scroll.compensation.y',
  4965. '_internal.list.width'
  4966. ], calculateStyle, { bulk: true });
  4967. const ListColumnHeader = createComponent(ListColumnHeaderComponent, { columnId: props.columnId });
  4968. onDestroy(ListColumnHeader.destroy);
  4969. onChange(changedProps => {
  4970. props = changedProps;
  4971. for (const prop in props) {
  4972. actionProps[prop] = props[prop];
  4973. }
  4974. if (columnSub)
  4975. columnSub();
  4976. ListColumnHeader.change({ columnId: props.columnId });
  4977. columnPath = `config.list.columns.data.${props.columnId}`;
  4978. columnSub = state.subscribe(columnPath, function columnChanged(val) {
  4979. column = val;
  4980. update();
  4981. });
  4982. if (styleSub)
  4983. styleSub();
  4984. styleSub = state.subscribeAll([
  4985. 'config.list.columns.percent',
  4986. 'config.list.columns.resizer.width',
  4987. `config.list.columns.data.${column.id}.width`,
  4988. '_internal.chart.dimensions.width',
  4989. '_internal.height',
  4990. 'config.scroll.compensation.y',
  4991. '_internal.list.width'
  4992. ], calculateStyle, { bulk: true });
  4993. ListColumnHeader.change(props);
  4994. });
  4995. onDestroy(() => {
  4996. columnSub();
  4997. styleSub();
  4998. });
  4999. onDestroy(state.subscribe('config.classNames', value => {
  5000. className = api.getClass(componentName);
  5001. classNameContainer = api.getClass(rowsComponentName);
  5002. update();
  5003. }));
  5004. const visibleRows = [];
  5005. const visibleRowsChange = val => {
  5006. const destroy = reuseComponents(visibleRows, val || [], row => row && { columnId: props.columnId, rowId: row.id, width }, ListColumnRowComponent);
  5007. update();
  5008. return destroy;
  5009. };
  5010. onDestroy(state.subscribe('_internal.list.visibleRows;', visibleRowsChange));
  5011. onDestroy(() => {
  5012. visibleRows.forEach(row => row.destroy());
  5013. componentsSub.forEach(unsub => unsub());
  5014. });
  5015. function getRowHtml(row) {
  5016. return row.html();
  5017. }
  5018. componentActions.push(BindElementAction);
  5019. const headerActions = Actions.create(componentActions, { column, state: state, api: api });
  5020. const rowActions = Actions.create(rowsActions, { api, state });
  5021. return templateProps => wrapper(html `
  5022. <div class=${className} data-actions=${headerActions} style=${widthStyleMap}>
  5023. ${ListColumnHeader.html()}
  5024. <div class=${classNameContainer} style=${containerStyleMap} data-actions=${rowActions}>
  5025. <div class=${classNameContainer + '--scroll-compensation'} style=${scrollCompensationStyleMap}>
  5026. ${visibleRows.map(getRowHtml)}
  5027. </div>
  5028. </div>
  5029. </div>
  5030. `, { vido, props, templateProps });
  5031. }
  5032. /**
  5033. * ListColumnHeader component
  5034. *
  5035. * @copyright Rafal Pospiech <https://neuronet.io>
  5036. * @author Rafal Pospiech <neuronet.io@gmail.com>
  5037. * @package gantt-schedule-timeline-calendar
  5038. * @license AGPL-3.0 (https://github.com/neuronetio/gantt-schedule-timeline-calendar/blob/master/LICENSE)
  5039. * @link https://github.com/neuronetio/gantt-schedule-timeline-calendar
  5040. */
  5041. function ListColumnHeader(vido, props) {
  5042. const { api, state, onDestroy, onChange, Actions, update, createComponent, html, cache, StyleMap } = vido;
  5043. const actionProps = Object.assign(Object.assign({}, props), { api, state });
  5044. let wrapper;
  5045. onDestroy(state.subscribe('config.wrappers.ListColumnHeader', value => (wrapper = value)));
  5046. const componentName = 'list-column-header';
  5047. const componentActions = api.getActions(componentName);
  5048. const componentsSubs = [];
  5049. let ListColumnHeaderResizerComponent;
  5050. componentsSubs.push(state.subscribe('config.components.ListColumnHeaderResizer', value => (ListColumnHeaderResizerComponent = value)));
  5051. const ListColumnHeaderResizer = createComponent(ListColumnHeaderResizerComponent, { columnId: props.columnId });
  5052. let ListColumnRowExpanderComponent;
  5053. componentsSubs.push(state.subscribe('config.components.ListColumnRowExpander', value => (ListColumnRowExpanderComponent = value)));
  5054. const ListColumnRowExpander = createComponent(ListColumnRowExpanderComponent, {});
  5055. onDestroy(() => {
  5056. ListColumnHeaderResizer.destroy();
  5057. ListColumnRowExpander.destroy();
  5058. componentsSubs.forEach(unsub => unsub());
  5059. });
  5060. let column;
  5061. let columnSub = state.subscribe(`config.list.columns.data.${props.columnId}`, val => {
  5062. column = val;
  5063. update();
  5064. });
  5065. onDestroy(columnSub);
  5066. onChange(changedProps => {
  5067. props = changedProps;
  5068. for (const prop in props) {
  5069. actionProps[prop] = props[prop];
  5070. }
  5071. if (columnSub)
  5072. columnSub();
  5073. columnSub = state.subscribe(`config.list.columns.data.${props.columnId}`, val => {
  5074. column = val;
  5075. update();
  5076. });
  5077. });
  5078. let className, contentClass;
  5079. onDestroy(state.subscribe('config.classNames', () => {
  5080. className = api.getClass(componentName);
  5081. contentClass = api.getClass(componentName + '-content');
  5082. }));
  5083. const styleMap = new StyleMap({
  5084. height: '',
  5085. '--height': '',
  5086. '--paddings-count': ''
  5087. });
  5088. onDestroy(state.subscribe('config.headerHeight', () => {
  5089. const value = state.get('config');
  5090. styleMap.style['height'] = value.headerHeight + 'px';
  5091. styleMap.style['--height'] = value.headerHeight + 'px';
  5092. styleMap.style['--paddings-count'] = '1';
  5093. update();
  5094. }));
  5095. function withExpander() {
  5096. return html `
  5097. <div class=${contentClass}>
  5098. ${ListColumnRowExpander.html()}${ListColumnHeaderResizer.html(column)}
  5099. </div>
  5100. `;
  5101. }
  5102. function withoutExpander() {
  5103. return html `
  5104. <div class=${contentClass}>
  5105. ${ListColumnHeaderResizer.html(column)}
  5106. </div>
  5107. `;
  5108. }
  5109. const actions = Actions.create(componentActions, actionProps);
  5110. return templateProps => wrapper(html `
  5111. <div class=${className} style=${styleMap} data-actions=${actions}>
  5112. ${cache(column.expander ? withExpander() : withoutExpander())}
  5113. </div>
  5114. `, { vido, props, templateProps });
  5115. }
  5116. /**
  5117. * ListColumnHeaderResizer component
  5118. *
  5119. * @copyright Rafal Pospiech <https://neuronet.io>
  5120. * @author Rafal Pospiech <neuronet.io@gmail.com>
  5121. * @package gantt-schedule-timeline-calendar
  5122. * @license AGPL-3.0 (https://github.com/neuronetio/gantt-schedule-timeline-calendar/blob/master/LICENSE)
  5123. * @link https://github.com/neuronetio/gantt-schedule-timeline-calendar
  5124. */
  5125. function ListColumnHeaderResizer(vido, props) {
  5126. const { api, state, onDestroy, update, html, schedule, Actions, PointerAction, cache, StyleMap } = vido;
  5127. const componentName = 'list-column-header-resizer';
  5128. const componentActions = api.getActions(componentName);
  5129. const componentDotsActions = api.getActions(componentName + '-dots');
  5130. let wrapper;
  5131. onDestroy(state.subscribe('config.wrappers.ListColumnHeaderResizer', value => (wrapper = value)));
  5132. let column;
  5133. onDestroy(state.subscribe(`config.list.columns.data.${props.columnId}`, val => {
  5134. column = val;
  5135. update();
  5136. }));
  5137. let className, containerClass, dotsClass, dotClass, calculatedWidth;
  5138. const dotsStyleMap = new StyleMap({ width: '' });
  5139. let inRealTime = false;
  5140. onDestroy(state.subscribe('config.classNames', value => {
  5141. className = api.getClass(componentName, { column });
  5142. containerClass = api.getClass(componentName + '-container', { column });
  5143. dotsClass = api.getClass(componentName + '-dots', { column });
  5144. dotClass = api.getClass(componentName + '-dots-dot', { column });
  5145. update();
  5146. }));
  5147. onDestroy(state.subscribeAll([
  5148. `config.list.columns.data.${column.id}.width`,
  5149. 'config.list.columns.percent',
  5150. 'config.list.columns.resizer.width',
  5151. 'config.list.columns.resizer.inRealTime'
  5152. ], (value, path) => {
  5153. const list = state.get('config.list');
  5154. calculatedWidth = column.width * list.columns.percent * 0.01;
  5155. dotsStyleMap.style['--width'] = list.columns.resizer.width + 'px';
  5156. inRealTime = list.columns.resizer.inRealTime;
  5157. state.update('_internal.list.width', calculatedWidth);
  5158. update();
  5159. }));
  5160. let dots = [1, 2, 3, 4, 5, 6, 7, 8];
  5161. onDestroy(state.subscribe('config.list.columns.resizer.dots', value => {
  5162. dots = [];
  5163. for (let i = 0; i < value; i++) {
  5164. dots.push(i);
  5165. }
  5166. update();
  5167. }));
  5168. /*
  5169. let isMoving = false;
  5170. const lineStyleMap = new StyleMap({
  5171. '--display': 'none',
  5172. '--left': left + 'px'
  5173. });*/
  5174. let left = calculatedWidth;
  5175. const columnWidthPath = `config.list.columns.data.${column.id}.width`;
  5176. const actionProps = {
  5177. column,
  5178. api,
  5179. state,
  5180. pointerOptions: {
  5181. axis: 'x',
  5182. onMove: function onMove({ movementX }) {
  5183. let minWidth = state.get('config.list.columns.minWidth');
  5184. if (typeof column.minWidth === 'number') {
  5185. minWidth = column.minWidth;
  5186. }
  5187. left += movementX;
  5188. if (left < minWidth) {
  5189. left = minWidth;
  5190. }
  5191. if (inRealTime) {
  5192. state.update(columnWidthPath, left);
  5193. }
  5194. }
  5195. }
  5196. };
  5197. componentActions.push(PointerAction);
  5198. const actions = Actions.create(componentActions, actionProps);
  5199. const dotsActions = Actions.create(componentDotsActions, actionProps);
  5200. return templateProps => wrapper(html `
  5201. <div class=${className} data-actions=${actions}>
  5202. <div class=${containerClass}>
  5203. ${cache(column.header.html
  5204. ? html `
  5205. ${column.header.html}
  5206. `
  5207. : column.header.content)}
  5208. </div>
  5209. <div class=${dotsClass} style=${dotsStyleMap} data-actions=${dotsActions}>
  5210. ${dots.map(dot => html `
  5211. <div class=${dotClass} />
  5212. `)}
  5213. </div>
  5214. </div>
  5215. `, { vido, props, templateProps });
  5216. }
  5217. /**
  5218. * ListColumnRow component
  5219. *
  5220. * @copyright Rafal Pospiech <https://neuronet.io>
  5221. * @author Rafal Pospiech <neuronet.io@gmail.com>
  5222. * @package gantt-schedule-timeline-calendar
  5223. * @license AGPL-3.0 (https://github.com/neuronetio/gantt-schedule-timeline-calendar/blob/master/LICENSE)
  5224. * @link https://github.com/neuronetio/gantt-schedule-timeline-calendar
  5225. */
  5226. /**
  5227. * Bind element action
  5228. */
  5229. class BindElementAction$1 {
  5230. constructor(element, data) {
  5231. let elements = data.state.get('_internal.elements.list-column-rows');
  5232. let shouldUpdate = false;
  5233. if (typeof elements === 'undefined') {
  5234. shouldUpdate = true;
  5235. elements = [];
  5236. }
  5237. if (!elements.includes(element)) {
  5238. elements.push(element);
  5239. shouldUpdate = true;
  5240. }
  5241. if (shouldUpdate)
  5242. data.state.update('_internal.elements.list-column-rows', elements);
  5243. }
  5244. destroy(element, data) {
  5245. data.state.update('_internal.elements.list-column-rows', elements => {
  5246. return elements.filter(el => el !== element);
  5247. });
  5248. }
  5249. }
  5250. function ListColumnRow(vido, props) {
  5251. const { api, state, onDestroy, Detach, Actions, update, html, createComponent, onChange, StyleMap, unsafeHTML, PointerAction } = vido;
  5252. const actionProps = Object.assign(Object.assign({}, props), { api, state });
  5253. let shouldDetach = false;
  5254. const detach = new Detach(() => shouldDetach);
  5255. let wrapper;
  5256. onDestroy(state.subscribe('config.wrappers.ListColumnRow', value => (wrapper = value)));
  5257. let ListColumnRowExpanderComponent;
  5258. onDestroy(state.subscribe('config.components.ListColumnRowExpander', value => (ListColumnRowExpanderComponent = value)));
  5259. let rowPath = `_internal.flatTreeMapById.${props.rowId}`, row = state.get(rowPath);
  5260. let colPath = `config.list.columns.data.${props.columnId}`, column = state.get(colPath);
  5261. const styleMap = new StyleMap(column.expander
  5262. ? {
  5263. height: '',
  5264. top: '',
  5265. '--height': '',
  5266. '--expander-padding-width': '',
  5267. '--expander-size': ''
  5268. }
  5269. : {
  5270. height: '',
  5271. top: '',
  5272. '--height': ''
  5273. }, true);
  5274. let rowSub, colSub;
  5275. const ListColumnRowExpander = createComponent(ListColumnRowExpanderComponent, { row });
  5276. const onPropsChange = (changedProps, options) => {
  5277. if (options.leave || changedProps.rowId === undefined || changedProps.columnId === undefined) {
  5278. shouldDetach = true;
  5279. if (rowSub)
  5280. rowSub();
  5281. if (colSub)
  5282. colSub();
  5283. update();
  5284. return;
  5285. }
  5286. shouldDetach = false;
  5287. props = changedProps;
  5288. for (const prop in props) {
  5289. actionProps[prop] = props[prop];
  5290. }
  5291. const rowId = props.rowId;
  5292. const columnId = props.columnId;
  5293. if (rowSub)
  5294. rowSub();
  5295. if (colSub)
  5296. colSub();
  5297. rowPath = `_internal.flatTreeMapById.${rowId}`;
  5298. colPath = `config.list.columns.data.${columnId}`;
  5299. rowSub = state.subscribeAll([rowPath, colPath, 'config.list.expander'], bulk => {
  5300. column = state.get(colPath);
  5301. row = state.get(rowPath);
  5302. if (column === undefined || row === undefined) {
  5303. shouldDetach = true;
  5304. update();
  5305. return;
  5306. }
  5307. if (column === undefined || row === undefined)
  5308. return;
  5309. const expander = state.get('config.list.expander');
  5310. // @ts-ignore
  5311. styleMap.setStyle({}); // we must reset style because of user specified styling
  5312. styleMap.style['height'] = row.height + 'px';
  5313. styleMap.style['--height'] = row.height + 'px';
  5314. if (column.expander) {
  5315. styleMap.style['--expander-padding-width'] = expander.padding * (row._internal.parents.length + 1) + 'px';
  5316. }
  5317. for (const parentId of row._internal.parents) {
  5318. const parent = state.get(`_internal.flatTreeMapById.${parentId}`);
  5319. if (typeof parent.style === 'object' && parent.style.constructor.name === 'Object') {
  5320. if (typeof parent.style.children === 'object') {
  5321. const childrenStyle = parent.style.children;
  5322. for (const name in childrenStyle) {
  5323. styleMap.style[name] = childrenStyle[name];
  5324. }
  5325. }
  5326. }
  5327. }
  5328. if (typeof row.style === 'object' &&
  5329. row.style.constructor.name === 'Object' &&
  5330. typeof row.style.current === 'object') {
  5331. const rowCurrentStyle = row.style.current;
  5332. for (const name in rowCurrentStyle) {
  5333. styleMap.style[name] = rowCurrentStyle[name];
  5334. }
  5335. }
  5336. update();
  5337. }, { bulk: true });
  5338. if (ListColumnRowExpander) {
  5339. ListColumnRowExpander.change({ row });
  5340. }
  5341. colSub = state.subscribe(colPath, val => {
  5342. column = val;
  5343. update();
  5344. });
  5345. };
  5346. onChange(onPropsChange);
  5347. onDestroy(() => {
  5348. if (ListColumnRowExpander)
  5349. ListColumnRowExpander.destroy();
  5350. colSub();
  5351. rowSub();
  5352. });
  5353. const componentName = 'list-column-row';
  5354. const componentActions = api.getActions(componentName);
  5355. let className;
  5356. onDestroy(state.subscribe('config.classNames', value => {
  5357. className = api.getClass(componentName);
  5358. update();
  5359. }));
  5360. function getHtml() {
  5361. if (row === undefined)
  5362. return null;
  5363. if (typeof column.data === 'function')
  5364. return unsafeHTML(column.data(row));
  5365. return unsafeHTML(row[column.data]);
  5366. }
  5367. function getText() {
  5368. if (row === undefined)
  5369. return null;
  5370. if (typeof column.data === 'function')
  5371. return column.data(row);
  5372. return row[column.data];
  5373. }
  5374. if (!componentActions.includes(BindElementAction$1))
  5375. componentActions.push(BindElementAction$1);
  5376. actionProps.pointerOptions = {
  5377. axis: 'x|y',
  5378. onMove({ event, movementX, movementY }) {
  5379. event.stopPropagation();
  5380. event.preventDefault();
  5381. if (movementX) {
  5382. state.update('config.list.columns.percent', percent => {
  5383. percent += movementX * state.get('config.scroll.xMultiplier');
  5384. if (percent < 0)
  5385. percent = 0;
  5386. if (percent > 100)
  5387. percent = 100;
  5388. return percent;
  5389. });
  5390. }
  5391. else if (movementY) {
  5392. state.update('config.scroll.top', top => {
  5393. top -= movementY * state.get('config.scroll.yMultiplier');
  5394. const rowsHeight = state.get('_internal.list.rowsHeight');
  5395. const internalHeight = state.get('_internal.height');
  5396. top = api.limitScrollTop(rowsHeight, internalHeight, top);
  5397. return top;
  5398. });
  5399. }
  5400. }
  5401. };
  5402. componentActions.push(PointerAction);
  5403. const actions = Actions.create(componentActions, actionProps);
  5404. return templateProps => wrapper(html `
  5405. <div detach=${detach} class=${className} style=${styleMap} data-actions=${actions}>
  5406. ${column.expander ? ListColumnRowExpander.html() : null}
  5407. <div class=${className + '-content'}>
  5408. ${column.isHTML ? getHtml() : getText()}
  5409. </div>
  5410. </div>
  5411. `, { vido, props, templateProps });
  5412. }
  5413. /**
  5414. * ListColumnRowExpander component
  5415. *
  5416. * @copyright Rafal Pospiech <https://neuronet.io>
  5417. * @author Rafal Pospiech <neuronet.io@gmail.com>
  5418. * @package gantt-schedule-timeline-calendar
  5419. * @license AGPL-3.0 (https://github.com/neuronetio/gantt-schedule-timeline-calendar/blob/master/LICENSE)
  5420. * @link https://github.com/neuronetio/gantt-schedule-timeline-calendar
  5421. */
  5422. function ListColumnRowExpander(vido, props) {
  5423. const { api, state, onDestroy, Actions, update, html, createComponent, onChange } = vido;
  5424. const componentName = 'list-column-row-expander';
  5425. const componentActions = api.getActions(componentName);
  5426. const actionProps = Object.assign(Object.assign({}, props), { api, state });
  5427. let className;
  5428. let ListColumnRowExpanderToggleComponent;
  5429. const toggleUnsub = state.subscribe('config.components.ListColumnRowExpanderToggle', value => (ListColumnRowExpanderToggleComponent = value));
  5430. const ListColumnRowExpanderToggle = createComponent(ListColumnRowExpanderToggleComponent, props.row ? { row: props.row } : {});
  5431. onDestroy(() => {
  5432. ListColumnRowExpanderToggle.destroy();
  5433. toggleUnsub();
  5434. });
  5435. let wrapper;
  5436. onDestroy(state.subscribe('config.wrappers.ListColumnRowExpander', value => (wrapper = value)));
  5437. onDestroy(state.subscribe('config.classNames', value => {
  5438. className = api.getClass(componentName);
  5439. update();
  5440. }));
  5441. if (props.row) {
  5442. function onPropsChange(changedProps) {
  5443. props = changedProps;
  5444. for (const prop in props) {
  5445. actionProps[prop] = props[prop];
  5446. }
  5447. ListColumnRowExpanderToggle.change(props);
  5448. }
  5449. onChange(onPropsChange);
  5450. }
  5451. const actions = Actions.create(componentActions, actionProps);
  5452. return templateProps => wrapper(html `
  5453. <div class=${className} data-action=${actions}>
  5454. ${ListColumnRowExpanderToggle.html()}
  5455. </div>
  5456. `, { vido, props, templateProps });
  5457. }
  5458. /**
  5459. * ListColumnRowExpanderToggle component
  5460. *
  5461. * @copyright Rafal Pospiech <https://neuronet.io>
  5462. * @author Rafal Pospiech <neuronet.io@gmail.com>
  5463. * @package gantt-schedule-timeline-calendar
  5464. * @license AGPL-3.0 (https://github.com/neuronetio/gantt-schedule-timeline-calendar/blob/master/LICENSE)
  5465. * @link https://github.com/neuronetio/gantt-schedule-timeline-calendar
  5466. */
  5467. function ListColumnRowExpanderToggle(vido, props) {
  5468. const { api, state, onDestroy, Actions, update, html, onChange, cache } = vido;
  5469. const componentName = 'list-column-row-expander-toggle';
  5470. const actionProps = Object.assign(Object.assign({}, props), { api, state });
  5471. let wrapper;
  5472. onDestroy(state.subscribe('config.wrappers.ListColumnRowExpanderToggle', value => (wrapper = value)));
  5473. const componentActions = api.getActions(componentName);
  5474. let className, classNameChild, classNameOpen, classNameClosed;
  5475. let expanded = false;
  5476. let iconChild, iconOpen, iconClosed;
  5477. onDestroy(state.subscribe('config.classNames', value => {
  5478. className = api.getClass(componentName);
  5479. classNameChild = className + '-child';
  5480. classNameOpen = className + '-open';
  5481. classNameClosed = className + '-closed';
  5482. update();
  5483. }));
  5484. onDestroy(state.subscribe('_internal.list.expander.icons', icons => {
  5485. if (icons) {
  5486. iconChild = icons.child;
  5487. iconOpen = icons.open;
  5488. iconClosed = icons.closed;
  5489. }
  5490. update();
  5491. }));
  5492. if (props.row) {
  5493. function expandedChange(isExpanded) {
  5494. expanded = isExpanded;
  5495. update();
  5496. }
  5497. let expandedSub;
  5498. function onPropsChange(changedProps) {
  5499. var _a, _b;
  5500. props = changedProps;
  5501. for (const prop in props) {
  5502. actionProps[prop] = props[prop];
  5503. }
  5504. if (expandedSub)
  5505. expandedSub();
  5506. if ((_b = (_a = props) === null || _a === void 0 ? void 0 : _a.row) === null || _b === void 0 ? void 0 : _b.id)
  5507. expandedSub = state.subscribe(`config.list.rows.${props.row.id}.expanded`, expandedChange);
  5508. }
  5509. onChange(onPropsChange);
  5510. onDestroy(function listToggleDestroy() {
  5511. if (expandedSub)
  5512. expandedSub();
  5513. });
  5514. }
  5515. else {
  5516. function expandedChange(bulk) {
  5517. for (const rowExpanded of bulk) {
  5518. if (rowExpanded.value) {
  5519. expanded = true;
  5520. break;
  5521. }
  5522. }
  5523. update();
  5524. }
  5525. onDestroy(state.subscribe('config.list.rows.*.expanded', expandedChange, { bulk: true }));
  5526. }
  5527. function toggle() {
  5528. expanded = !expanded;
  5529. if (props.row) {
  5530. state.update(`config.list.rows.${props.row.id}.expanded`, expanded);
  5531. }
  5532. else {
  5533. state.update(`config.list.rows`, rows => {
  5534. for (const rowId in rows) {
  5535. rows[rowId].expanded = expanded;
  5536. }
  5537. return rows;
  5538. }, { only: ['*.expanded'] });
  5539. }
  5540. }
  5541. const getIcon = () => {
  5542. var _a, _b, _c;
  5543. if (iconChild) {
  5544. if (((_c = (_b = (_a = props.row) === null || _a === void 0 ? void 0 : _a._internal) === null || _b === void 0 ? void 0 : _b.children) === null || _c === void 0 ? void 0 : _c.length) === 0) {
  5545. return html `
  5546. <img width="16" height="16" class=${classNameChild} src=${iconChild} />
  5547. `;
  5548. }
  5549. return expanded
  5550. ? html `
  5551. <img width="16" height="16" class=${classNameOpen} src=${iconOpen} />
  5552. `
  5553. : html `
  5554. <img width="16" height="16" class=${classNameClosed} src=${iconClosed} />
  5555. `;
  5556. }
  5557. return '';
  5558. };
  5559. const actions = Actions.create(componentActions, actionProps);
  5560. return templateProps => wrapper(html `
  5561. <div class=${className} data-action=${actions} @click=${toggle}>
  5562. ${cache(getIcon())}
  5563. </div>
  5564. `, { vido, props, templateProps });
  5565. }
  5566. /**
  5567. * ListToggle component
  5568. *
  5569. * @copyright Rafal Pospiech <https://neuronet.io>
  5570. * @author Rafal Pospiech <neuronet.io@gmail.com>
  5571. * @package gantt-schedule-timeline-calendar
  5572. * @license AGPL-3.0 (https://github.com/neuronetio/gantt-schedule-timeline-calendar/blob/master/LICENSE)
  5573. * @link https://github.com/neuronetio/gantt-schedule-timeline-calendar
  5574. */
  5575. function ListToggle(vido, props = {}) {
  5576. const { html, onDestroy, api, state, update } = vido;
  5577. const componentName = 'list-toggle';
  5578. let className;
  5579. onDestroy(state.subscribe('config.classNames', classNames => {
  5580. className = api.getClass(componentName);
  5581. }));
  5582. let wrapper;
  5583. onDestroy(state.subscribe('config.wrappers.ListToggle', ListToggleWrapper => (wrapper = ListToggleWrapper)));
  5584. let toggleIconsSrc = {
  5585. open: '',
  5586. close: ''
  5587. };
  5588. onDestroy(state.subscribe('_internal.list.toggle.icons', value => {
  5589. if (value) {
  5590. toggleIconsSrc = value;
  5591. update();
  5592. }
  5593. }));
  5594. let open = true;
  5595. onDestroy(state.subscribe('config.list.columns.percent', percent => (percent === 0 ? (open = false) : (open = true))));
  5596. function toggle(ev) {
  5597. state.update('config.list.columns.percent', percent => {
  5598. return percent === 0 ? 100 : 0;
  5599. });
  5600. }
  5601. return templateProps => wrapper(html `
  5602. <div class=${className} @click=${toggle}><img src=${open ? toggleIconsSrc.close : toggleIconsSrc.open} /></div>
  5603. `, { props, vido, templateProps });
  5604. }
  5605. /**
  5606. * Chart component
  5607. *
  5608. * @copyright Rafal Pospiech <https://neuronet.io>
  5609. * @author Rafal Pospiech <neuronet.io@gmail.com>
  5610. * @package gantt-schedule-timeline-calendar
  5611. * @license AGPL-3.0 (https://github.com/neuronetio/gantt-schedule-timeline-calendar/blob/master/LICENSE)
  5612. * @link https://github.com/neuronetio/gantt-schedule-timeline-calendar
  5613. */
  5614. function Chart(vido, props = {}) {
  5615. const { api, state, onDestroy, Actions, update, html, StyleMap, createComponent } = vido;
  5616. const componentName = 'chart';
  5617. const ChartCalendarComponent = state.get('config.components.ChartCalendar');
  5618. const ChartTimelineComponent = state.get('config.components.ChartTimeline');
  5619. let wrapper;
  5620. onDestroy(state.subscribe('config.wrappers.Chart', value => (wrapper = value)));
  5621. const Calendar = createComponent(ChartCalendarComponent);
  5622. onDestroy(Calendar.destroy);
  5623. const Timeline = createComponent(ChartTimelineComponent);
  5624. onDestroy(Timeline.destroy);
  5625. let className, classNameScroll, classNameScrollInner, scrollElement, scrollInnerElement;
  5626. const componentActions = api.getActions(componentName);
  5627. onDestroy(state.subscribe('config.classNames', value => {
  5628. className = api.getClass(componentName);
  5629. classNameScroll = api.getClass('horizontal-scroll');
  5630. classNameScrollInner = api.getClass('horizontal-scroll-inner');
  5631. update();
  5632. }));
  5633. onDestroy(state.subscribeAll(['_internal.chart.dimensions.width', '_internal.chart.time.totalViewDurationPx'], function horizontalScroll() {
  5634. if (scrollElement)
  5635. scrollElement.style.width = state.get('_internal.chart.dimensions.width') + 'px';
  5636. if (scrollInnerElement)
  5637. scrollInnerElement.style.width = state.get('_internal.chart.time.totalViewDurationPx') + 'px';
  5638. }));
  5639. onDestroy(state.subscribe('config.scroll.left', left => {
  5640. if (scrollElement) {
  5641. scrollElement.scrollLeft = left;
  5642. }
  5643. }));
  5644. function onScrollHandler(event) {
  5645. if (event.type === 'scroll') {
  5646. // @ts-ignore
  5647. const left = event.target.scrollLeft;
  5648. state.update('config.scroll.left', left);
  5649. }
  5650. }
  5651. const onScroll = {
  5652. handleEvent: onScrollHandler,
  5653. passive: true,
  5654. capture: false
  5655. };
  5656. function onWheelHandler(event) {
  5657. if (event.type === 'wheel') {
  5658. const wheel = api.normalizeMouseWheelEvent(event);
  5659. const xMultiplier = state.get('config.scroll.xMultiplier');
  5660. const yMultiplier = state.get('config.scroll.yMultiplier');
  5661. const currentScrollLeft = state.get('config.scroll.left');
  5662. const totalViewDurationPx = state.get('_internal.chart.time.totalViewDurationPx');
  5663. if (event.shiftKey && wheel.y) {
  5664. const newScrollLeft = api.limitScrollLeft(totalViewDurationPx, chartWidth, currentScrollLeft + wheel.y * xMultiplier);
  5665. state.update('config.scroll.left', newScrollLeft); // will trigger scrollbar to move which will trigger scroll event
  5666. }
  5667. else if (event.ctrlKey && wheel.y) {
  5668. event.preventDefault();
  5669. state.update('config.chart.time.zoom', currentZoom => {
  5670. if (wheel.y < 0) {
  5671. return currentZoom - 1;
  5672. }
  5673. return currentZoom + 1;
  5674. });
  5675. }
  5676. else if (wheel.x) {
  5677. const currentScrollLeft = state.get('config.scroll.left');
  5678. state.update('config.scroll.left', api.limitScrollLeft(totalViewDurationPx, chartWidth, currentScrollLeft + wheel.x * xMultiplier));
  5679. }
  5680. else {
  5681. state.update('config.scroll.top', top => {
  5682. const rowsHeight = state.get('_internal.list.rowsHeight');
  5683. const internalHeight = state.get('_internal.height');
  5684. return api.limitScrollTop(rowsHeight, internalHeight, (top += wheel.y * yMultiplier));
  5685. });
  5686. }
  5687. }
  5688. }
  5689. const onWheel = {
  5690. handleEvent: onWheelHandler,
  5691. passive: false,
  5692. capture: false
  5693. };
  5694. function bindElement(element) {
  5695. if (!scrollElement) {
  5696. scrollElement = element;
  5697. state.update('_internal.elements.horizontal-scroll', element);
  5698. }
  5699. }
  5700. function bindInnerScroll(element) {
  5701. scrollInnerElement = element;
  5702. const old = state.get('_internal.elements.horizontal-scroll-inner');
  5703. if (old !== element)
  5704. state.update('_internal.elements.horizontal-scroll-inner', element);
  5705. if (!state.get('_internal.loaded.horizontal-scroll-inner'))
  5706. state.update('_internal.loaded.horizontal-scroll-inner', true);
  5707. }
  5708. let chartWidth = 0;
  5709. let ro;
  5710. componentActions.push(function bindElement(element) {
  5711. if (!ro) {
  5712. ro = new index((entries, observer) => {
  5713. const width = element.clientWidth;
  5714. const height = element.clientHeight;
  5715. const innerWidth = width - state.get('_internal.scrollBarHeight');
  5716. if (chartWidth !== width) {
  5717. chartWidth = width;
  5718. state.update('_internal.chart.dimensions', { width, innerWidth, height });
  5719. }
  5720. });
  5721. ro.observe(element);
  5722. state.update('_internal.elements.chart', element);
  5723. state.update('_internal.loaded.chart', true);
  5724. }
  5725. });
  5726. onDestroy(() => {
  5727. ro.disconnect();
  5728. });
  5729. const actions = Actions.create(componentActions, { api, state });
  5730. const scrollActions = Actions.create([bindElement]);
  5731. const scrollAreaActions = Actions.create([bindInnerScroll]);
  5732. return templateProps => wrapper(html `
  5733. <div class=${className} data-actions=${actions} @wheel=${onWheel} @scroll=${onScroll}>
  5734. ${Calendar.html()}${Timeline.html()}
  5735. <div class=${classNameScroll} data-actions=${scrollActions} @scroll=${onScroll}>
  5736. <div class=${classNameScrollInner} style="height: 1px" data-actions=${scrollAreaActions} />
  5737. </div>
  5738. </div>
  5739. `, { vido, props: {}, templateProps });
  5740. }
  5741. /**
  5742. * ChartCalendar component
  5743. *
  5744. * @copyright Rafal Pospiech <https://neuronet.io>
  5745. * @author Rafal Pospiech <neuronet.io@gmail.com>
  5746. * @package gantt-schedule-timeline-calendar
  5747. * @license AGPL-3.0 (https://github.com/neuronetio/gantt-schedule-timeline-calendar/blob/master/LICENSE)
  5748. * @link https://github.com/neuronetio/gantt-schedule-timeline-calendar
  5749. */
  5750. function ChartCalendar(vido, props) {
  5751. const { api, state, onDestroy, Actions, update, reuseComponents, html, StyleMap } = vido;
  5752. const componentName = 'chart-calendar';
  5753. const componentActions = api.getActions(componentName);
  5754. const actionProps = Object.assign(Object.assign({}, props), { api, state });
  5755. const ChartCalendarDateComponent = state.get('config.components.ChartCalendarDate');
  5756. let wrapper;
  5757. onDestroy(state.subscribe('config.wrappers.ChartCalendar', value => (wrapper = value)));
  5758. let className;
  5759. onDestroy(state.subscribe('config.classNames', value => {
  5760. className = api.getClass(componentName);
  5761. update();
  5762. }));
  5763. let headerHeight;
  5764. const styleMap = new StyleMap({ height: '', '--headerHeight': '', 'margin-left': '' });
  5765. onDestroy(state.subscribe('config.headerHeight', value => {
  5766. headerHeight = value;
  5767. styleMap.style['height'] = headerHeight + 'px';
  5768. styleMap.style['--calendar-height'] = headerHeight + 'px';
  5769. update();
  5770. }));
  5771. onDestroy(state.subscribe('config.scroll.compensation.x', compensation => {
  5772. styleMap.style['margin-left'] = -compensation + 'px';
  5773. update();
  5774. }));
  5775. const components = [[], []];
  5776. onDestroy(state.subscribe(`_internal.chart.time.levels`, levels => {
  5777. let level = 0;
  5778. for (const dates of levels) {
  5779. if (!dates.length)
  5780. continue;
  5781. let currentDateFormat = 'YYYY-MM-DD HH'; // hour
  5782. switch (dates[0].period) {
  5783. case 'day':
  5784. currentDateFormat = 'YYYY-MM-DD';
  5785. break;
  5786. case 'week':
  5787. currentDateFormat = 'YYYY-MM-ww';
  5788. break;
  5789. case 'month':
  5790. currentDateFormat = 'YYYY-MM';
  5791. break;
  5792. case 'year':
  5793. currentDateFormat = 'YYYY';
  5794. break;
  5795. }
  5796. const currentDate = api.time.date().format(currentDateFormat);
  5797. reuseComponents(components[level], dates, date => date && { level, date, currentDate, currentDateFormat }, ChartCalendarDateComponent);
  5798. level++;
  5799. }
  5800. update();
  5801. }));
  5802. onDestroy(() => {
  5803. components.forEach(level => level.forEach(component => component.destroy()));
  5804. });
  5805. componentActions.push(element => {
  5806. state.update('_internal.elements.chart-calendar', element);
  5807. });
  5808. const actions = Actions.create(componentActions, actionProps);
  5809. return templateProps => wrapper(html `
  5810. <div class=${className} data-actions=${actions} style=${styleMap}>
  5811. ${components.map((components, level) => html `
  5812. <div class=${className + '-dates ' + className + `-dates--level-${level}`}>
  5813. ${components.map(m => m.html())}
  5814. </div>
  5815. `)}
  5816. </div>
  5817. `, { props, vido, templateProps });
  5818. }
  5819. class Action$1 {
  5820. constructor() {
  5821. this.isAction = true;
  5822. }
  5823. }
  5824. Action$1.prototype.isAction = true;
  5825. /**
  5826. * ChartCalendarDay component
  5827. *
  5828. * @copyright Rafal Pospiech <https://neuronet.io>
  5829. * @author Rafal Pospiech <neuronet.io@gmail.com>
  5830. * @package gantt-schedule-timeline-calendar
  5831. * @license AGPL-3.0 (https://github.com/neuronetio/gantt-schedule-timeline-calendar/blob/master/LICENSE)
  5832. * @link https://github.com/neuronetio/gantt-schedule-timeline-calendar
  5833. */
  5834. /**
  5835. * Save element
  5836. * @param {HTMLElement} element
  5837. * @param {object} data
  5838. */
  5839. class BindElementAction$2 extends Action$1 {
  5840. constructor(element, data) {
  5841. super();
  5842. data.state.update('_internal.elements.chart-calendar-dates', elements => {
  5843. if (typeof elements === 'undefined') {
  5844. elements = [];
  5845. }
  5846. if (!elements.includes(element)) {
  5847. elements.push(element);
  5848. }
  5849. return elements;
  5850. });
  5851. }
  5852. }
  5853. function ChartCalendarDay(vido, props) {
  5854. const { api, state, onDestroy, Actions, update, onChange, html, StyleMap, Detach } = vido;
  5855. const componentName = 'chart-calendar-date';
  5856. const componentActions = api.getActions(componentName);
  5857. let wrapper;
  5858. onDestroy(state.subscribe('config.wrappers.ChartCalendarDate', value => (wrapper = value)));
  5859. let className;
  5860. onDestroy(state.subscribe('config.classNames', () => {
  5861. className = api.getClass(componentName, props);
  5862. }));
  5863. let current = '';
  5864. let time, htmlFormatted;
  5865. const styleMap = new StyleMap({ width: '', visibility: 'visible' }), scrollStyleMap = new StyleMap({
  5866. overflow: 'hidden',
  5867. 'text-align': 'left'
  5868. });
  5869. let formatClassName = '';
  5870. function updateDate() {
  5871. if (!props)
  5872. return;
  5873. const cache = state.get('_internal.cache.calendar');
  5874. const level = state.get(`config.chart.calendar.levels.${props.level}`);
  5875. styleMap.style.width = props.date.width + 'px';
  5876. styleMap.style.visibility = 'visible';
  5877. scrollStyleMap.style = { overflow: 'hidden', 'text-align': 'left', 'margin-left': props.date.subPx + 8 + 'px' };
  5878. time = state.get('_internal.chart.time');
  5879. const cacheKey = `${new Date(props.date.leftGlobal).toISOString()}-${props.date.period}-${props.level}-${time.zoom}`;
  5880. if (!cache[cacheKey]) {
  5881. cache[cacheKey] = {};
  5882. }
  5883. let timeStart, timeEnd;
  5884. {
  5885. timeStart = api.time.date(props.date.leftGlobal);
  5886. timeEnd = api.time.date(props.date.rightGlobal);
  5887. cache[cacheKey].timeStart = timeStart;
  5888. cache[cacheKey].timeEnd = timeEnd;
  5889. }
  5890. const formats = level.formats;
  5891. const formatting = formats.find(formatting => +time.zoom <= +formatting.zoomTo);
  5892. let format;
  5893. {
  5894. format = formatting ? formatting.format({ timeStart, timeEnd, className, vido, props }) : null;
  5895. cache[cacheKey].format = format;
  5896. }
  5897. {
  5898. if (timeStart.format(props.currentDateFormat) === props.currentDate) {
  5899. current = ' gstc-current';
  5900. }
  5901. else if (timeStart.subtract(1, props.date.period).format(props.currentDateFormat) === props.currentDate) {
  5902. current = ' gstc-next';
  5903. }
  5904. else if (timeStart.add(1, props.date.period).format(props.currentDateFormat) === props.currentDate) {
  5905. current = ' gstc-previous';
  5906. }
  5907. else {
  5908. current = '';
  5909. }
  5910. cache[cacheKey].current = current;
  5911. }
  5912. let finalClassName = className + '-content ' + className + `-content--${props.date.period}` + current;
  5913. if (formatting.className) {
  5914. finalClassName += ' ' + formatting.className;
  5915. formatClassName = ' ' + formatting.className;
  5916. }
  5917. else {
  5918. formatClassName = '';
  5919. }
  5920. // updating cache state is not necessary because it is object and nobody listen to cache
  5921. htmlFormatted = html `
  5922. <div class=${finalClassName}>
  5923. ${format}
  5924. </div>
  5925. `;
  5926. update();
  5927. }
  5928. let shouldDetach = false;
  5929. const detach = new Detach(() => shouldDetach);
  5930. let timeSub;
  5931. const actionProps = { date: props.date, period: props.period, api, state };
  5932. onChange((changedProps, options) => {
  5933. if (options.leave) {
  5934. shouldDetach = true;
  5935. return update();
  5936. }
  5937. shouldDetach = false;
  5938. props = changedProps;
  5939. actionProps.date = props.date;
  5940. actionProps.period = props.period;
  5941. if (timeSub) {
  5942. timeSub();
  5943. }
  5944. timeSub = state.subscribeAll(['_internal.chart.time', 'config.chart.calendar.levels'], updateDate, {
  5945. bulk: true
  5946. });
  5947. });
  5948. onDestroy(() => {
  5949. timeSub();
  5950. });
  5951. if (!componentActions.includes(BindElementAction$2))
  5952. componentActions.push(BindElementAction$2);
  5953. const actions = Actions.create(componentActions, actionProps);
  5954. return templateProps => wrapper(html `
  5955. <div
  5956. detach=${detach}
  5957. class=${className +
  5958. ' ' +
  5959. className +
  5960. `--${props.date.period}` +
  5961. ' ' +
  5962. className +
  5963. `--level-${props.level}` +
  5964. current +
  5965. formatClassName}
  5966. style=${styleMap}
  5967. data-actions=${actions}
  5968. >
  5969. ${htmlFormatted}
  5970. </div>
  5971. `, { props, vido, templateProps });
  5972. }
  5973. /**
  5974. * ChartTimeline component
  5975. *
  5976. * @copyright Rafal Pospiech <https://neuronet.io>
  5977. * @author Rafal Pospiech <neuronet.io@gmail.com>
  5978. * @package gantt-schedule-timeline-calendar
  5979. * @license AGPL-3.0 (https://github.com/neuronetio/gantt-schedule-timeline-calendar/blob/master/LICENSE)
  5980. * @link https://github.com/neuronetio/gantt-schedule-timeline-calendar
  5981. */
  5982. function ChartTimeline(vido, props) {
  5983. const { api, state, onDestroy, Action, Actions, update, html, createComponent, StyleMap } = vido;
  5984. const componentName = 'chart-timeline';
  5985. const componentActions = api.getActions(componentName);
  5986. const actionProps = Object.assign(Object.assign({}, props), { api, state });
  5987. let wrapper;
  5988. onDestroy(state.subscribe('config.wrappers.ChartTimeline', value => (wrapper = value)));
  5989. const GridComponent = state.get('config.components.ChartTimelineGrid');
  5990. const ItemsComponent = state.get('config.components.ChartTimelineItems');
  5991. const ListToggleComponent = state.get('config.components.ListToggle');
  5992. const Grid = createComponent(GridComponent);
  5993. onDestroy(Grid.destroy);
  5994. const Items = createComponent(ItemsComponent);
  5995. onDestroy(Items.destroy);
  5996. const ListToggle = createComponent(ListToggleComponent);
  5997. onDestroy(ListToggle.destroy);
  5998. let className, classNameInner;
  5999. onDestroy(state.subscribe('config.classNames', () => {
  6000. className = api.getClass(componentName);
  6001. classNameInner = api.getClass(componentName + '-inner');
  6002. update();
  6003. }));
  6004. let showToggle;
  6005. onDestroy(state.subscribe('config.list.toggle.display', val => (showToggle = val)));
  6006. const styleMap = new StyleMap({}), innerStyleMap = new StyleMap({});
  6007. function calculateStyle() {
  6008. const xCompensation = api.getCompensationX();
  6009. const yCompensation = api.getCompensationY();
  6010. const width = state.get('_internal.chart.dimensions.width');
  6011. const height = state.get('_internal.list.rowsHeight');
  6012. styleMap.style.height = state.get('_internal.height') + 'px';
  6013. styleMap.style['--height'] = styleMap.style.height;
  6014. styleMap.style['--negative-compensation-x'] = xCompensation + 'px';
  6015. styleMap.style['--compensation-x'] = Math.round(Math.abs(xCompensation)) + 'px';
  6016. styleMap.style['--negative-compensation-y'] = yCompensation + 'px';
  6017. styleMap.style['--compensation-y'] = Math.abs(yCompensation) + 'px';
  6018. if (width) {
  6019. styleMap.style.width = width + 'px';
  6020. styleMap.style['--width'] = width + 'px';
  6021. }
  6022. else {
  6023. styleMap.style.width = '0px';
  6024. styleMap.style['--width'] = '0px';
  6025. }
  6026. innerStyleMap.style.height = height + 'px';
  6027. if (width) {
  6028. innerStyleMap.style.width = width + xCompensation + 'px';
  6029. }
  6030. else {
  6031. innerStyleMap.style.width = '0px';
  6032. }
  6033. //innerStyleMap.style.transform = `translate(-${xCompensation}px, ${yCompensation}px)`;
  6034. innerStyleMap.style['margin-left'] = -xCompensation + 'px';
  6035. update();
  6036. }
  6037. onDestroy(state.subscribeAll([
  6038. '_internal.height',
  6039. '_internal.chart.dimensions.width',
  6040. '_internal.list.rowsHeight',
  6041. 'config.scroll.compensation',
  6042. '_internal.chart.time.dates.day'
  6043. ], calculateStyle));
  6044. componentActions.push(class BindElementAction extends Action {
  6045. constructor(element) {
  6046. super();
  6047. const old = state.get('_internal.elements.chart-timeline');
  6048. if (old !== element)
  6049. state.update('_internal.elements.chart-timeline', element);
  6050. }
  6051. });
  6052. const actions = Actions.create(componentActions, actionProps);
  6053. return templateProps => wrapper(html `
  6054. <div class=${className} style=${styleMap} data-actions=${actions} @wheel=${api.onScroll}>
  6055. <div class=${classNameInner} style=${innerStyleMap}>
  6056. ${Grid.html()}${Items.html()}${showToggle ? ListToggle.html() : ''}
  6057. </div>
  6058. </div>
  6059. `, { props, vido, templateProps });
  6060. }
  6061. /**
  6062. * ChartTimelineGrid component
  6063. *
  6064. * @copyright Rafal Pospiech <https://neuronet.io>
  6065. * @author Rafal Pospiech <neuronet.io@gmail.com>
  6066. * @package gantt-schedule-timeline-calendar
  6067. * @license AGPL-3.0 (https://github.com/neuronetio/gantt-schedule-timeline-calendar/blob/master/LICENSE)
  6068. * @link https://github.com/neuronetio/gantt-schedule-timeline-calendar
  6069. */
  6070. /**
  6071. * Bind element action
  6072. */
  6073. class BindElementAction$3 {
  6074. constructor(element, data) {
  6075. const old = data.state.get('_internal.elements.chart-timeline-grid');
  6076. if (old !== element)
  6077. data.state.update('_internal.elements.chart-timeline-grid', element);
  6078. }
  6079. destroy(element, data) {
  6080. data.state.update('_internal.elements', elements => {
  6081. delete elements['chart-timeline-grid'];
  6082. return elements;
  6083. });
  6084. }
  6085. }
  6086. function ChartTimelineGrid(vido, props) {
  6087. const { api, state, onDestroy, Actions, update, html, reuseComponents, StyleMap } = vido;
  6088. const componentName = 'chart-timeline-grid';
  6089. const componentActions = api.getActions(componentName);
  6090. const actionProps = { api, state };
  6091. let wrapper;
  6092. onDestroy(state.subscribe('config.wrappers.ChartTimelineGrid', value => (wrapper = value)));
  6093. const GridRowComponent = state.get('config.components.ChartTimelineGridRow');
  6094. let className;
  6095. onDestroy(state.subscribe('config.classNames', () => {
  6096. className = api.getClass(componentName);
  6097. update();
  6098. }));
  6099. let onBlockCreate;
  6100. onDestroy(state.subscribe('config.chart.grid.block.onCreate', onCreate => (onBlockCreate = onCreate)));
  6101. const rowsComponents = [];
  6102. const rowsWithBlocks = [];
  6103. const formatCache = new Map();
  6104. const styleMap = new StyleMap({});
  6105. /**
  6106. * Generate blocks
  6107. */
  6108. function generateBlocks() {
  6109. const width = state.get('_internal.chart.dimensions.width');
  6110. const height = state.get('_internal.height');
  6111. const time = state.get('_internal.chart.time');
  6112. const periodDates = state.get(`_internal.chart.time.levels.${time.level}`);
  6113. if (!periodDates || periodDates.length === 0) {
  6114. state.update('_internal.chart.grid.rowsWithBlocks', []);
  6115. return;
  6116. }
  6117. const visibleRows = state.get('_internal.list.visibleRows');
  6118. const xCompensation = api.getCompensationX();
  6119. const yCompensation = api.getCompensationY();
  6120. styleMap.style.height = height + Math.abs(yCompensation) + 'px';
  6121. styleMap.style.width = width + xCompensation + 'px';
  6122. let top = 0;
  6123. rowsWithBlocks.length = 0;
  6124. for (const row of visibleRows) {
  6125. const blocks = [];
  6126. for (const time of periodDates) {
  6127. let format;
  6128. if (formatCache.has(time.leftGlobal)) {
  6129. format = formatCache.get(time.leftGlobal);
  6130. }
  6131. else {
  6132. format = api.time.date(time.leftGlobal).format('YYYY-MM-DD HH:mm');
  6133. formatCache.set(time.leftGlobal, format);
  6134. }
  6135. const id = row.id + ':' + format;
  6136. let block = { id, time, row, top };
  6137. for (const onCreate of onBlockCreate) {
  6138. block = onCreate(block);
  6139. }
  6140. blocks.push(block);
  6141. }
  6142. rowsWithBlocks.push({ row, blocks, top, width });
  6143. top += row.height;
  6144. }
  6145. state.update('_internal.chart.grid.rowsWithBlocks', rowsWithBlocks);
  6146. }
  6147. onDestroy(state.subscribeAll([
  6148. '_internal.list.visibleRows;',
  6149. `_internal.chart.time.levels`,
  6150. '_internal.height',
  6151. '_internal.chart.dimensions.width'
  6152. ], generateBlocks, {
  6153. bulk: true
  6154. }));
  6155. /**
  6156. * Generate rows components
  6157. * @param {array} rowsWithBlocks
  6158. */
  6159. function generateRowsComponents(rowsWithBlocks) {
  6160. reuseComponents(rowsComponents, rowsWithBlocks || [], row => row, GridRowComponent);
  6161. update();
  6162. }
  6163. onDestroy(state.subscribe('_internal.chart.grid.rowsWithBlocks', generateRowsComponents));
  6164. onDestroy(() => {
  6165. rowsComponents.forEach(row => row.destroy());
  6166. });
  6167. componentActions.push(BindElementAction$3);
  6168. const actions = Actions.create(componentActions, actionProps);
  6169. return templateProps => wrapper(html `
  6170. <div class=${className} data-actions=${actions} style=${styleMap}>
  6171. ${rowsComponents.map(r => r.html())}
  6172. </div>
  6173. `, { props, vido, templateProps });
  6174. }
  6175. /**
  6176. * ChartTimelineGridRow component
  6177. *
  6178. * @copyright Rafal Pospiech <https://neuronet.io>
  6179. * @author Rafal Pospiech <neuronet.io@gmail.com>
  6180. * @package gantt-schedule-timeline-calendar
  6181. * @license AGPL-3.0 (https://github.com/neuronetio/gantt-schedule-timeline-calendar/blob/master/LICENSE)
  6182. * @link https://github.com/neuronetio/gantt-schedule-timeline-calendar
  6183. */
  6184. /**
  6185. * Bind element action
  6186. */
  6187. class BindElementAction$4 {
  6188. constructor(element, data) {
  6189. let shouldUpdate = false;
  6190. let rows = data.state.get('_internal.elements.chart-timeline-grid-rows');
  6191. if (typeof rows === 'undefined') {
  6192. rows = [];
  6193. shouldUpdate = true;
  6194. }
  6195. if (!rows.includes(element)) {
  6196. rows.push(element);
  6197. shouldUpdate = true;
  6198. }
  6199. if (shouldUpdate)
  6200. data.state.update('_internal.elements.chart-timeline-grid-rows', rows, { only: null });
  6201. }
  6202. destroy(element, data) {
  6203. data.state.update('_internal.elements.chart-timeline-grid-rows', rows => {
  6204. return rows.filter(el => el !== element);
  6205. });
  6206. }
  6207. }
  6208. function ChartTimelineGridRow(vido, props) {
  6209. const { api, state, onDestroy, Detach, Actions, update, html, reuseComponents, onChange, StyleMap } = vido;
  6210. const componentName = 'chart-timeline-grid-row';
  6211. const actionProps = Object.assign(Object.assign({}, props), { api,
  6212. state });
  6213. let wrapper;
  6214. onDestroy(state.subscribe('config.wrappers.ChartTimelineGridRow', value => {
  6215. wrapper = value;
  6216. update();
  6217. }));
  6218. const GridBlockComponent = state.get('config.components.ChartTimelineGridRowBlock');
  6219. const componentActions = api.getActions(componentName);
  6220. let className;
  6221. onDestroy(state.subscribe('config.classNames', () => {
  6222. className = api.getClass(componentName);
  6223. }));
  6224. const styleMap = new StyleMap({
  6225. width: props.width + 'px',
  6226. height: props.row.height + 'px',
  6227. overflow: 'hidden'
  6228. }, true);
  6229. let shouldDetach = false;
  6230. const detach = new Detach(() => shouldDetach);
  6231. const rowsBlocksComponents = [];
  6232. onChange(function onPropsChange(changedProps, options) {
  6233. var _a, _b, _c, _d, _e, _f, _g, _h, _j;
  6234. if (options.leave || changedProps.row === undefined) {
  6235. shouldDetach = true;
  6236. reuseComponents(rowsBlocksComponents, [], block => block, GridBlockComponent);
  6237. update();
  6238. return;
  6239. }
  6240. shouldDetach = false;
  6241. props = changedProps;
  6242. reuseComponents(rowsBlocksComponents, props.blocks, block => block, GridBlockComponent);
  6243. styleMap.setStyle({});
  6244. styleMap.style.height = props.row.height + 'px';
  6245. styleMap.style.width = props.width + 'px';
  6246. const rows = state.get('config.list.rows');
  6247. for (const parentId of props.row._internal.parents) {
  6248. const parent = rows[parentId];
  6249. const childrenStyle = (_d = (_c = (_b = (_a = parent) === null || _a === void 0 ? void 0 : _a.style) === null || _b === void 0 ? void 0 : _b.grid) === null || _c === void 0 ? void 0 : _c.row) === null || _d === void 0 ? void 0 : _d.children;
  6250. if (childrenStyle)
  6251. for (const name in childrenStyle) {
  6252. styleMap.style[name] = childrenStyle[name];
  6253. }
  6254. }
  6255. const currentStyle = (_j = (_h = (_g = (_f = (_e = props) === null || _e === void 0 ? void 0 : _e.row) === null || _f === void 0 ? void 0 : _f.style) === null || _g === void 0 ? void 0 : _g.grid) === null || _h === void 0 ? void 0 : _h.row) === null || _j === void 0 ? void 0 : _j.current;
  6256. if (currentStyle)
  6257. for (const name in currentStyle) {
  6258. styleMap.style[name] = currentStyle[name];
  6259. }
  6260. for (const prop in props) {
  6261. actionProps[prop] = props[prop];
  6262. }
  6263. update();
  6264. });
  6265. onDestroy(function destroy() {
  6266. rowsBlocksComponents.forEach(rowBlock => rowBlock.destroy());
  6267. });
  6268. if (componentActions.indexOf(BindElementAction$4) === -1) {
  6269. componentActions.push(BindElementAction$4);
  6270. }
  6271. const actions = Actions.create(componentActions, actionProps);
  6272. return templateProps => {
  6273. return wrapper(html `
  6274. <div detach=${detach} class=${className} data-actions=${actions} style=${styleMap}>
  6275. ${rowsBlocksComponents.map(r => r.html())}
  6276. </div>
  6277. `, { vido, props, templateProps });
  6278. };
  6279. }
  6280. /**
  6281. * ChartTimelineGridRowBlock component
  6282. *
  6283. * @copyright Rafal Pospiech <https://neuronet.io>
  6284. * @author Rafal Pospiech <neuronet.io@gmail.com>
  6285. * @package gantt-schedule-timeline-calendar
  6286. * @license AGPL-3.0 (https://github.com/neuronetio/gantt-schedule-timeline-calendar/blob/master/LICENSE)
  6287. * @link https://github.com/neuronetio/gantt-schedule-timeline-calendar
  6288. */
  6289. /**
  6290. * Bind element action
  6291. * @param {Element} element
  6292. * @param {any} data
  6293. * @returns {object} with update and destroy
  6294. */
  6295. class BindElementAction$5 {
  6296. constructor(element, data) {
  6297. let shouldUpdate = false;
  6298. let blocks = data.state.get('_internal.elements.chart-timeline-grid-row-blocks');
  6299. if (typeof blocks === 'undefined') {
  6300. blocks = [];
  6301. shouldUpdate = true;
  6302. }
  6303. if (!blocks.includes(element)) {
  6304. blocks.push(element);
  6305. shouldUpdate = true;
  6306. }
  6307. if (shouldUpdate)
  6308. data.state.update('_internal.elements.chart-timeline-grid-row-blocks', blocks, { only: null });
  6309. }
  6310. destroy(element, data) {
  6311. data.state.update('_internal.elements.chart-timeline-grid-row-blocks', blocks => {
  6312. return blocks.filter(el => el !== element);
  6313. }, { only: [''] });
  6314. }
  6315. }
  6316. const ChartTimelineGridRowBlock = (vido, props) => {
  6317. const { api, state, onDestroy, Detach, Actions, update, html, onChange, StyleMap } = vido;
  6318. const componentName = 'chart-timeline-grid-row-block';
  6319. const actionProps = Object.assign(Object.assign({}, props), { api,
  6320. state });
  6321. let shouldDetach = false;
  6322. const detach = new Detach(() => shouldDetach);
  6323. const componentActions = api.getActions(componentName);
  6324. let wrapper;
  6325. onDestroy(state.subscribe('config.wrappers.ChartTimelineGridRowBlock', value => {
  6326. wrapper = value;
  6327. update();
  6328. }));
  6329. let className;
  6330. function updateClassName(time) {
  6331. const currentTime = api.time
  6332. .date()
  6333. .startOf(time.period)
  6334. .valueOf();
  6335. className = api.getClass(componentName);
  6336. if (time.leftGlobal === currentTime) {
  6337. className += ' current';
  6338. }
  6339. }
  6340. updateClassName(props.time);
  6341. const styleMap = new StyleMap({ width: '', height: '' });
  6342. /**
  6343. * On props change
  6344. * @param {any} changedProps
  6345. */
  6346. function onPropsChange(changedProps, options) {
  6347. var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
  6348. if (options.leave || changedProps.row === undefined) {
  6349. shouldDetach = true;
  6350. return update();
  6351. }
  6352. shouldDetach = false;
  6353. props = changedProps;
  6354. for (const prop in props) {
  6355. actionProps[prop] = props[prop];
  6356. }
  6357. updateClassName(props.time);
  6358. styleMap.setStyle({});
  6359. styleMap.style.width = (((_b = (_a = props) === null || _a === void 0 ? void 0 : _a.time) === null || _b === void 0 ? void 0 : _b.width) || 0) + 'px';
  6360. styleMap.style.height = (((_d = (_c = props) === null || _c === void 0 ? void 0 : _c.row) === null || _d === void 0 ? void 0 : _d.height) || 0) + 'px';
  6361. const rows = state.get('config.list.rows');
  6362. for (const parentId of props.row._internal.parents) {
  6363. const parent = rows[parentId];
  6364. const childrenStyle = (_h = (_g = (_f = (_e = parent) === null || _e === void 0 ? void 0 : _e.style) === null || _f === void 0 ? void 0 : _f.grid) === null || _g === void 0 ? void 0 : _g.block) === null || _h === void 0 ? void 0 : _h.children;
  6365. if (childrenStyle)
  6366. styleMap.setStyle(Object.assign(Object.assign({}, styleMap.style), childrenStyle));
  6367. }
  6368. const currentStyle = (_o = (_m = (_l = (_k = (_j = props) === null || _j === void 0 ? void 0 : _j.row) === null || _k === void 0 ? void 0 : _k.style) === null || _l === void 0 ? void 0 : _l.grid) === null || _m === void 0 ? void 0 : _m.block) === null || _o === void 0 ? void 0 : _o.current;
  6369. if (currentStyle)
  6370. styleMap.setStyle(Object.assign(Object.assign({}, styleMap.style), currentStyle));
  6371. update();
  6372. }
  6373. onChange(onPropsChange);
  6374. componentActions.push(BindElementAction$5);
  6375. const actions = Actions.create(componentActions, actionProps);
  6376. return templateProps => {
  6377. return wrapper(html `
  6378. <div detach=${detach} class=${className} data-actions=${actions} style=${styleMap}></div>
  6379. `, { props, vido, templateProps });
  6380. };
  6381. };
  6382. /**
  6383. * ChartTimelineItems component
  6384. *
  6385. * @copyright Rafal Pospiech <https://neuronet.io>
  6386. * @author Rafal Pospiech <neuronet.io@gmail.com>
  6387. * @package gantt-schedule-timeline-calendar
  6388. * @license AGPL-3.0 (https://github.com/neuronetio/gantt-schedule-timeline-calendar/blob/master/LICENSE)
  6389. * @link https://github.com/neuronetio/gantt-schedule-timeline-calendar
  6390. */
  6391. function ChartTimelineItems(vido, props = {}) {
  6392. const { api, state, onDestroy, Actions, update, html, reuseComponents, StyleMap } = vido;
  6393. const componentName = 'chart-timeline-items';
  6394. const componentActions = api.getActions(componentName);
  6395. let wrapper;
  6396. onDestroy(state.subscribe('config.wrappers.ChartTimelineItems', value => (wrapper = value)));
  6397. let ItemsRowComponent;
  6398. onDestroy(state.subscribe('config.components.ChartTimelineItemsRow', value => (ItemsRowComponent = value)));
  6399. let className;
  6400. onDestroy(state.subscribe('config.classNames', () => {
  6401. className = api.getClass(componentName);
  6402. update();
  6403. }));
  6404. const styleMap = new StyleMap({}, true);
  6405. function calculateStyle() {
  6406. const width = state.get('_internal.chart.dimensions.width');
  6407. const height = state.get('_internal.height');
  6408. const yCompensation = api.getCompensationY();
  6409. const xCompensation = api.getCompensationX();
  6410. styleMap.style.width = width + xCompensation + 'px';
  6411. styleMap.style.height = height + Math.abs(yCompensation) + 'px';
  6412. }
  6413. onDestroy(state.subscribeAll([
  6414. '_internal.height',
  6415. '_internal.chart.dimensions.width',
  6416. 'config.scroll.compensation',
  6417. '_internal.chart.time.dates.day'
  6418. ], calculateStyle));
  6419. const rowsComponents = [];
  6420. function createRowComponents() {
  6421. const visibleRows = state.get('_internal.list.visibleRows');
  6422. reuseComponents(rowsComponents, visibleRows || [], row => ({ row }), ItemsRowComponent);
  6423. update();
  6424. }
  6425. onDestroy(state.subscribeAll(['_internal.list.visibleRows;', 'config.chart.items'], createRowComponents));
  6426. onDestroy(() => {
  6427. rowsComponents.forEach(row => row.destroy());
  6428. });
  6429. const actions = Actions.create(componentActions, { api, state });
  6430. return templateProps => wrapper(html `
  6431. <div class=${className} style=${styleMap} data-actions=${actions}>
  6432. ${rowsComponents.map(r => r.html())}
  6433. </div>
  6434. `, { props, vido, templateProps });
  6435. }
  6436. /**
  6437. * ChartTimelineItemsRow component
  6438. *
  6439. * @copyright Rafal Pospiech <https://neuronet.io>
  6440. * @author Rafal Pospiech <neuronet.io@gmail.com>
  6441. * @package gantt-schedule-timeline-calendar
  6442. * @license AGPL-3.0 (https://github.com/neuronetio/gantt-schedule-timeline-calendar/blob/master/LICENSE)
  6443. * @link https://github.com/neuronetio/gantt-schedule-timeline-calendar
  6444. */
  6445. /**
  6446. * Bind element action
  6447. * @param {Element} element
  6448. * @param {any} data
  6449. */
  6450. class BindElementAction$6 {
  6451. constructor(element, data) {
  6452. let shouldUpdate = false;
  6453. let rows = data.state.get('_internal.elements.chart-timeline-items-rows');
  6454. if (typeof rows === 'undefined') {
  6455. rows = [];
  6456. shouldUpdate = true;
  6457. }
  6458. if (!rows.includes(element)) {
  6459. rows.push(element);
  6460. shouldUpdate = true;
  6461. }
  6462. if (shouldUpdate)
  6463. data.state.update('_internal.elements.chart-timeline-items-rows', rows, { only: null });
  6464. }
  6465. destroy(element, data) {
  6466. data.state.update('_internal.elements.chart-timeline-items-rows', rows => {
  6467. return rows.filter(el => el !== element);
  6468. });
  6469. }
  6470. }
  6471. const ChartTimelineItemsRow = (vido, props) => {
  6472. const { api, state, onDestroy, Detach, Actions, update, html, onChange, reuseComponents, StyleMap } = vido;
  6473. const actionProps = Object.assign(Object.assign({}, props), { api, state });
  6474. let wrapper;
  6475. onDestroy(state.subscribe('config.wrappers.ChartTimelineItemsRow', value => (wrapper = value)));
  6476. let ItemComponent;
  6477. onDestroy(state.subscribe('config.components.ChartTimelineItemsRowItem', value => (ItemComponent = value)));
  6478. let itemsPath = `_internal.flatTreeMapById.${props.row.id}._internal.items`;
  6479. let rowSub, itemsSub;
  6480. const itemComponents = [], styleMap = new StyleMap({ width: '', height: '' }, true);
  6481. let shouldDetach = false;
  6482. const detach = new Detach(() => shouldDetach);
  6483. const updateDom = () => {
  6484. const chart = state.get('_internal.chart');
  6485. shouldDetach = false;
  6486. const xCompensation = api.getCompensationX();
  6487. styleMap.style.width = chart.dimensions.width + xCompensation + 'px';
  6488. if (!props) {
  6489. shouldDetach = true;
  6490. return;
  6491. }
  6492. styleMap.style.height = props.row.height + 'px';
  6493. styleMap.style['--row-height'] = props.row.height + 'px';
  6494. };
  6495. function updateRow(row) {
  6496. itemsPath = `_internal.flatTreeMapById.${row.id}._internal.items`;
  6497. if (typeof rowSub === 'function') {
  6498. rowSub();
  6499. }
  6500. if (typeof itemsSub === 'function') {
  6501. itemsSub();
  6502. }
  6503. rowSub = state.subscribe('_internal.chart', value => {
  6504. if (value === undefined) {
  6505. shouldDetach = true;
  6506. return update();
  6507. }
  6508. updateDom();
  6509. update();
  6510. });
  6511. itemsSub = state.subscribe(itemsPath, value => {
  6512. if (value === undefined) {
  6513. shouldDetach = true;
  6514. reuseComponents(itemComponents, [], item => ({ row, item }), ItemComponent);
  6515. return update();
  6516. }
  6517. reuseComponents(itemComponents, value, item => ({ row, item }), ItemComponent);
  6518. updateDom();
  6519. update();
  6520. });
  6521. }
  6522. /**
  6523. * On props change
  6524. * @param {any} changedProps
  6525. */
  6526. onChange((changedProps, options) => {
  6527. if (options.leave || changedProps.row === undefined) {
  6528. shouldDetach = true;
  6529. return update();
  6530. }
  6531. props = changedProps;
  6532. for (const prop in props) {
  6533. actionProps[prop] = props[prop];
  6534. }
  6535. updateRow(props.row);
  6536. });
  6537. onDestroy(() => {
  6538. itemsSub();
  6539. rowSub();
  6540. itemComponents.forEach(item => item.destroy());
  6541. });
  6542. const componentName = 'chart-timeline-items-row';
  6543. const componentActions = api.getActions(componentName);
  6544. let className;
  6545. onDestroy(state.subscribe('config.classNames', () => {
  6546. className = api.getClass(componentName, props);
  6547. update();
  6548. }));
  6549. componentActions.push(BindElementAction$6);
  6550. const actions = Actions.create(componentActions, actionProps);
  6551. return templateProps => {
  6552. return wrapper(html `
  6553. <div detach=${detach} class=${className} data-actions=${actions} style=${styleMap}>
  6554. ${itemComponents.map(i => i.html())}
  6555. </div>
  6556. `, { props, vido, templateProps });
  6557. };
  6558. };
  6559. /**
  6560. * ChartTimelineItemsRowItem component
  6561. *
  6562. * @copyright Rafal Pospiech <https://neuronet.io>
  6563. * @author Rafal Pospiech <neuronet.io@gmail.com>
  6564. * @package gantt-schedule-timeline-calendar
  6565. * @license AGPL-3.0 (https://github.com/neuronetio/gantt-schedule-timeline-calendar/blob/master/LICENSE)
  6566. * @link https://github.com/neuronetio/gantt-schedule-timeline-calendar
  6567. */
  6568. /**
  6569. * Bind element action
  6570. */
  6571. class BindElementAction$7 {
  6572. constructor(element, data) {
  6573. let shouldUpdate = false;
  6574. let items = data.state.get('_internal.elements.chart-timeline-items-row-items');
  6575. if (typeof items === 'undefined') {
  6576. items = [];
  6577. shouldUpdate = true;
  6578. }
  6579. if (!items.includes(element)) {
  6580. items.push(element);
  6581. shouldUpdate = true;
  6582. }
  6583. if (shouldUpdate)
  6584. data.state.update('_internal.elements.chart-timeline-items-row-items', items, { only: null });
  6585. }
  6586. destroy(element, data) {
  6587. data.state.update('_internal.elements.chart-timeline-items-row-items', items => {
  6588. return items.filter(el => el !== element);
  6589. });
  6590. }
  6591. }
  6592. function ChartTimelineItemsRowItem(vido, props) {
  6593. const { api, state, onDestroy, Detach, Actions, update, html, onChange, unsafeHTML, StyleMap } = vido;
  6594. let wrapper;
  6595. onDestroy(state.subscribe('config.wrappers.ChartTimelineItemsRowItem', value => (wrapper = value)));
  6596. let itemLeftPx = 0, itemWidthPx = 0, leave = false, cutLeft = false, cutRight = false;
  6597. const styleMap = new StyleMap({ width: '', height: '', left: '' }), leftCutStyleMap = new StyleMap({ 'margin-left': '0px' }), rightCutStyleMap = new StyleMap({ 'margin-right': '0px' }), actionProps = {
  6598. item: props.item,
  6599. row: props.row,
  6600. left: itemLeftPx,
  6601. width: itemWidthPx,
  6602. api,
  6603. state
  6604. };
  6605. let shouldDetach = false;
  6606. function updateItem() {
  6607. var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
  6608. if (leave)
  6609. return;
  6610. const time = state.get('_internal.chart.time');
  6611. itemLeftPx = api.time.globalTimeToViewPixelOffset(props.item.time.start);
  6612. itemLeftPx = Math.round(itemLeftPx * 10) * 0.1;
  6613. itemWidthPx = (props.item.time.end - props.item.time.start) / time.timePerPixel;
  6614. itemWidthPx -= state.get('config.chart.spacing') || 0;
  6615. if (itemWidthPx) {
  6616. itemWidthPx = Math.round(itemWidthPx * 10) * 0.1;
  6617. }
  6618. if (props.item.time.start < time.leftGlobal) {
  6619. leftCutStyleMap.style['margin-left'] = (time.leftGlobal - props.item.time.start) / time.timePerPixel + 'px';
  6620. cutLeft = true;
  6621. }
  6622. else {
  6623. leftCutStyleMap.style['margin-left'] = '0px';
  6624. cutLeft = false;
  6625. }
  6626. if (props.item.time.end > time.rightGlobal) {
  6627. rightCutStyleMap.style['margin-right'] = (props.item.time.end - time.rightGlobal) / time.timePerPixel + 'px';
  6628. cutRight = true;
  6629. }
  6630. else {
  6631. cutRight = false;
  6632. rightCutStyleMap.style['margin-right'] = '0px';
  6633. }
  6634. const oldWidth = styleMap.style.width;
  6635. const oldLeft = styleMap.style.left;
  6636. const xCompensation = api.getCompensationX();
  6637. styleMap.setStyle({});
  6638. const inViewPort = api.isItemInViewport(props.item, time.leftGlobal, time.rightGlobal);
  6639. shouldDetach = !inViewPort;
  6640. if (inViewPort) {
  6641. // update style only when visible to prevent browser's recalculate style
  6642. styleMap.style.width = itemWidthPx + 'px';
  6643. styleMap.style.left = itemLeftPx + xCompensation + 'px';
  6644. }
  6645. else {
  6646. styleMap.style.width = oldWidth;
  6647. styleMap.style.left = oldLeft;
  6648. }
  6649. const rows = state.get('config.list.rows');
  6650. for (const parentId of props.row._internal.parents) {
  6651. const parent = rows[parentId];
  6652. const childrenStyle = (_d = (_c = (_b = (_a = parent) === null || _a === void 0 ? void 0 : _a.style) === null || _b === void 0 ? void 0 : _b.items) === null || _c === void 0 ? void 0 : _c.item) === null || _d === void 0 ? void 0 : _d.children;
  6653. if (childrenStyle)
  6654. styleMap.setStyle(Object.assign(Object.assign({}, styleMap.style), childrenStyle));
  6655. }
  6656. const currentRowItemsStyle = (_j = (_h = (_g = (_f = (_e = props) === null || _e === void 0 ? void 0 : _e.row) === null || _f === void 0 ? void 0 : _f.style) === null || _g === void 0 ? void 0 : _g.items) === null || _h === void 0 ? void 0 : _h.item) === null || _j === void 0 ? void 0 : _j.current;
  6657. if (currentRowItemsStyle)
  6658. styleMap.setStyle(Object.assign(Object.assign({}, styleMap.style), currentRowItemsStyle));
  6659. const currentStyle = (_l = (_k = props) === null || _k === void 0 ? void 0 : _k.item) === null || _l === void 0 ? void 0 : _l.style;
  6660. if (currentStyle)
  6661. styleMap.setStyle(Object.assign(Object.assign({}, styleMap.style), currentStyle));
  6662. actionProps.left = itemLeftPx + xCompensation;
  6663. actionProps.width = itemWidthPx;
  6664. update();
  6665. }
  6666. const componentName = 'chart-timeline-items-row-item';
  6667. const cutterName = api.getClass(componentName) + '-cut';
  6668. const cutterLeft = html `
  6669. <div class=${cutterName} style=${leftCutStyleMap}>
  6670. <svg xmlns="http://www.w3.org/2000/svg" height="16" viewBox="0 0 18 16" width="16">
  6671. <path fill-opacity="0.5" fill="#ffffff" d="m5,3l-5,5l5,5l0,-10z" />
  6672. </svg>
  6673. </div>
  6674. `;
  6675. const cutterRight = html `
  6676. <div class=${cutterName} style=${rightCutStyleMap}>
  6677. <svg xmlns="http://www.w3.org/2000/svg" height="16" viewBox="0 0 4 16" width="16">
  6678. <path transform="rotate(-180 2.5,8) " fill-opacity="0.5" fill="#ffffff" d="m5,3l-5,5l5,5l0,-10z" />
  6679. </svg>
  6680. </div>
  6681. `;
  6682. function onPropsChange(changedProps, options) {
  6683. if (options.leave || changedProps.row === undefined || changedProps.item === undefined) {
  6684. leave = true;
  6685. shouldDetach = true;
  6686. return update();
  6687. }
  6688. else {
  6689. shouldDetach = false;
  6690. leave = false;
  6691. }
  6692. props = changedProps;
  6693. actionProps.item = props.item;
  6694. actionProps.row = props.row;
  6695. updateItem();
  6696. }
  6697. onChange(onPropsChange);
  6698. const componentActions = api.getActions(componentName);
  6699. let className, labelClassName;
  6700. onDestroy(state.subscribe('config.classNames', () => {
  6701. className = api.getClass(componentName, props);
  6702. labelClassName = api.getClass(componentName + '-label', props);
  6703. update();
  6704. }));
  6705. onDestroy(state.subscribeAll(['_internal.chart.time', 'config.scroll.compensation.x'], updateItem));
  6706. componentActions.push(BindElementAction$7);
  6707. const actions = Actions.create(componentActions, actionProps);
  6708. const detach = new Detach(() => shouldDetach);
  6709. return templateProps => {
  6710. return wrapper(html `
  6711. <div detach=${detach} class=${className} data-actions=${actions} style=${styleMap}>
  6712. ${cutLeft ? cutterLeft : ''}
  6713. <div class=${labelClassName}>
  6714. ${props.item.isHtml ? unsafeHTML(props.item.label) : props.item.label}
  6715. </div>
  6716. ${cutRight ? cutterRight : ''}
  6717. </div>
  6718. `, { vido, props, templateProps });
  6719. };
  6720. }
  6721. /**
  6722. * Gantt-Schedule-Timeline-Calendar
  6723. *
  6724. * @copyright Rafal Pospiech <https://neuronet.io>
  6725. * @author Rafal Pospiech <neuronet.io@gmail.com>
  6726. * @package gantt-schedule-timeline-calendar
  6727. * @license AGPL-3.0
  6728. */
  6729. const actionNames = [
  6730. 'main',
  6731. 'list',
  6732. 'list-column',
  6733. 'list-column-header',
  6734. 'list-column-header-resizer',
  6735. 'list-column-header-resizer-dots',
  6736. 'list-column-row',
  6737. 'list-column-row-expander',
  6738. 'list-column-row-expander-toggle',
  6739. 'list-toggle',
  6740. 'chart',
  6741. 'chart-calendar',
  6742. 'chart-calendar-date',
  6743. 'chart-timeline',
  6744. 'chart-timeline-grid',
  6745. 'chart-timeline-grid-row',
  6746. 'chart-timeline-grid-row-block',
  6747. 'chart-timeline-items',
  6748. 'chart-timeline-items-row',
  6749. 'chart-timeline-items-row-item'
  6750. ];
  6751. function generateEmptyActions() {
  6752. const actions = {};
  6753. actionNames.forEach(name => (actions[name] = []));
  6754. return actions;
  6755. }
  6756. function generateEmptySlots() {
  6757. const slots = {};
  6758. actionNames.forEach(name => {
  6759. slots[name] = { before: [], after: [] };
  6760. });
  6761. return slots;
  6762. }
  6763. // default configuration
  6764. function defaultConfig() {
  6765. const actions = generateEmptyActions();
  6766. const slots = generateEmptySlots();
  6767. return {
  6768. plugins: [],
  6769. plugin: {},
  6770. height: 822,
  6771. headerHeight: 72,
  6772. components: {
  6773. Main,
  6774. List,
  6775. ListColumn,
  6776. ListColumnHeader,
  6777. ListColumnHeaderResizer,
  6778. ListColumnRow,
  6779. ListColumnRowExpander,
  6780. ListColumnRowExpanderToggle,
  6781. ListToggle,
  6782. Chart,
  6783. ChartCalendar,
  6784. ChartCalendarDate: ChartCalendarDay,
  6785. ChartTimeline,
  6786. ChartTimelineGrid,
  6787. ChartTimelineGridRow,
  6788. ChartTimelineGridRowBlock,
  6789. ChartTimelineItems,
  6790. ChartTimelineItemsRow,
  6791. ChartTimelineItemsRowItem
  6792. },
  6793. wrappers: {
  6794. Main(input) {
  6795. return input;
  6796. },
  6797. List(input) {
  6798. return input;
  6799. },
  6800. ListColumn(input) {
  6801. return input;
  6802. },
  6803. ListColumnHeader(input) {
  6804. return input;
  6805. },
  6806. ListColumnHeaderResizer(input) {
  6807. return input;
  6808. },
  6809. ListColumnRow(input) {
  6810. return input;
  6811. },
  6812. ListColumnRowExpander(input) {
  6813. return input;
  6814. },
  6815. ListColumnRowExpanderToggle(input) {
  6816. return input;
  6817. },
  6818. ListToggle(input) {
  6819. return input;
  6820. },
  6821. Chart(input) {
  6822. return input;
  6823. },
  6824. ChartCalendar(input) {
  6825. return input;
  6826. },
  6827. ChartCalendarDate(input) {
  6828. return input;
  6829. },
  6830. ChartTimeline(input) {
  6831. return input;
  6832. },
  6833. ChartTimelineGrid(input) {
  6834. return input;
  6835. },
  6836. ChartTimelineGridRow(input) {
  6837. return input;
  6838. },
  6839. ChartTimelineGridRowBlock(input) {
  6840. return input;
  6841. },
  6842. ChartTimelineItems(input) {
  6843. return input;
  6844. },
  6845. ChartTimelineItemsRow(input) {
  6846. return input;
  6847. },
  6848. ChartTimelineItemsRowItem(input) {
  6849. return input;
  6850. }
  6851. },
  6852. list: {
  6853. rows: {},
  6854. rowHeight: 40,
  6855. columns: {
  6856. percent: 100,
  6857. resizer: {
  6858. width: 10,
  6859. inRealTime: true,
  6860. dots: 6
  6861. },
  6862. minWidth: 50,
  6863. data: {}
  6864. },
  6865. expander: {
  6866. padding: 18,
  6867. size: 20,
  6868. icon: {
  6869. width: 16,
  6870. height: 16
  6871. },
  6872. icons: {
  6873. child: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><ellipse ry="4" rx="4" id="svg_1" cy="12" cx="12" fill="#000000B0"/></svg>',
  6874. open: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z"/><path fill="none" d="M0 0h24v24H0V0z"/></svg>',
  6875. closed: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z"/><path fill="none" d="M0 0h24v24H0V0z"/></svg>'
  6876. }
  6877. },
  6878. toggle: {
  6879. display: true,
  6880. icons: {
  6881. open: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path stroke="null" d="m16.406954,16.012672l4.00393,-4.012673l-4.00393,-4.012673l1.232651,-1.232651l5.245324,5.245324l-5.245324,5.245324l-1.232651,-1.232651z"/><path stroke="null" d="m-0.343497,12.97734zm1.620144,0l11.341011,0l0,-1.954681l-11.341011,0l0,1.954681zm0,3.909362l11.341011,0l0,-1.954681l-11.341011,0l0,1.954681zm0,-9.773404l0,1.95468l11.341011,0l0,-1.95468l-11.341011,0z"/></svg>`,
  6882. close: `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path transform="rotate(-180 4.392796516418457,12) " stroke="null" d="m1.153809,16.012672l4.00393,-4.012673l-4.00393,-4.012673l1.232651,-1.232651l5.245324,5.245324l-5.245324,5.245324l-1.232651,-1.232651z"/><path stroke="null" d="m9.773297,12.97734zm1.620144,0l11.341011,0l0,-1.954681l-11.341011,0l0,1.954681zm0,3.909362l11.341011,0l0,-1.954681l-11.341011,0l0,1.954681zm0,-9.773404l0,1.95468l11.341011,0l0,-1.95468l-11.341011,0z"/></svg>`
  6883. }
  6884. }
  6885. },
  6886. scroll: {
  6887. propagate: true,
  6888. smooth: false,
  6889. top: 0,
  6890. left: 0,
  6891. xMultiplier: 3,
  6892. yMultiplier: 3,
  6893. percent: {
  6894. top: 0,
  6895. left: 0
  6896. },
  6897. compensation: {
  6898. x: 0,
  6899. y: 0
  6900. }
  6901. },
  6902. chart: {
  6903. time: {
  6904. period: 'day',
  6905. from: 0,
  6906. to: 0,
  6907. finalFrom: 0,
  6908. finalTo: 0,
  6909. zoom: 21,
  6910. leftGlobal: 0,
  6911. centerGlobal: 0,
  6912. rightGlobal: 0,
  6913. levels: [],
  6914. calculatedZoomMode: false
  6915. },
  6916. calendar: {
  6917. expand: true,
  6918. levels: [
  6919. {
  6920. formats: [
  6921. {
  6922. zoomTo: 17,
  6923. period: 'day',
  6924. className: 'gstc-date-medium gstc-date-left',
  6925. format({ timeStart }) {
  6926. return timeStart.format('DD MMMM YYYY (dddd)');
  6927. }
  6928. },
  6929. {
  6930. zoomTo: 23,
  6931. period: 'month',
  6932. format({ timeStart }) {
  6933. return timeStart.format('MMMM YYYY');
  6934. }
  6935. },
  6936. {
  6937. zoomTo: 24,
  6938. period: 'month',
  6939. format({ timeStart, className, vido }) {
  6940. return timeStart.format("MMMM 'YY");
  6941. }
  6942. },
  6943. {
  6944. zoomTo: 25,
  6945. period: 'month',
  6946. format({ timeStart }) {
  6947. return timeStart.format('MMM YYYY');
  6948. }
  6949. },
  6950. {
  6951. zoomTo: 27,
  6952. period: 'year',
  6953. format({ timeStart }) {
  6954. return timeStart.format('YYYY');
  6955. }
  6956. },
  6957. {
  6958. zoomTo: 100,
  6959. period: 'year',
  6960. default: true,
  6961. format() {
  6962. return null;
  6963. }
  6964. }
  6965. ]
  6966. },
  6967. {
  6968. main: true,
  6969. formats: [
  6970. {
  6971. zoomTo: 16,
  6972. period: 'hour',
  6973. format({ timeStart }) {
  6974. return timeStart.format('HH:mm');
  6975. }
  6976. },
  6977. {
  6978. zoomTo: 17,
  6979. period: 'hour',
  6980. default: true,
  6981. format({ timeStart }) {
  6982. return timeStart.format('HH');
  6983. }
  6984. },
  6985. {
  6986. zoomTo: 19,
  6987. period: 'day',
  6988. className: 'gstc-date-medium',
  6989. format({ timeStart, className, vido }) {
  6990. return vido.html `<span class="${className}-content gstc-date-bold">${timeStart.format('DD')}</span> <span class="${className}-content gstc-date-thin">${timeStart.format('dddd')}</span>`;
  6991. }
  6992. },
  6993. {
  6994. zoomTo: 20,
  6995. period: 'day',
  6996. default: true,
  6997. format({ timeStart, vido, className }) {
  6998. return vido.html `<div class="${className}-content gstc-date-top">${timeStart.format('DD')}</div><div class="${className}-content gstc-date-small">${timeStart.format('dddd')}</div>`;
  6999. }
  7000. },
  7001. {
  7002. zoomTo: 21,
  7003. period: 'day',
  7004. format({ timeStart, vido, className }) {
  7005. return vido.html `<div class="${className}-content gstc-date-top">${timeStart.format('DD')}</div><div class="${className}-content gstc-date-small">${timeStart.format('ddd')}</div>`;
  7006. }
  7007. },
  7008. {
  7009. zoomTo: 22,
  7010. period: 'day',
  7011. className: 'gstc-date-vertical',
  7012. format({ timeStart, className, vido }) {
  7013. return vido.html `<div class="${className}-content gstc-date-top">${timeStart.format('DD')}</div><div class="${className}-content gstc-date-extra-small">${timeStart.format('ddd')}</div>`;
  7014. }
  7015. },
  7016. {
  7017. zoomTo: 23,
  7018. period: 'week',
  7019. default: true,
  7020. format({ timeStart, timeEnd, className, vido }) {
  7021. return vido.html `<div class="${className}-content gstc-date-top">${timeStart.format('DD')} - ${timeEnd.format('DD')}</div><div class="${className}-content gstc-date-small gstc-date-thin">${timeStart.format('ddd')} - ${timeEnd.format('dd')}</div>`;
  7022. }
  7023. },
  7024. {
  7025. zoomTo: 25,
  7026. period: 'week',
  7027. className: 'gstc-date-vertical',
  7028. format({ timeStart, timeEnd, className, vido }) {
  7029. return vido.html `<div class="${className}-content gstc-date-top gstc-date-small gstc-date-normal">${timeStart.format('DD')}</div><div class="gstc-dash gstc-date-small">-</div><div class="${className}-content gstc-date-small gstc-date-normal">${timeEnd.format('DD')}</div>`;
  7030. }
  7031. },
  7032. {
  7033. zoomTo: 26,
  7034. period: 'month',
  7035. default: true,
  7036. className: 'gstc-date-month-level-1',
  7037. format({ timeStart, vido, className }) {
  7038. return vido.html `<div class="${className}-content gstc-date-top">${timeStart.format('MMM')}</div><div class="${className}-content gstc-date-small gstc-date-bottom">${timeStart.format('MM')}</div>`;
  7039. }
  7040. },
  7041. {
  7042. zoomTo: 27,
  7043. period: 'month',
  7044. className: 'gstc-date-vertical',
  7045. format({ timeStart, className, vido }) {
  7046. return vido.html `<div class="${className}-content gstc-date-top">${timeStart.format('MM')}</div><div class="${className}-content gstc-date-extra-small">${timeStart.format('MMM')}</div>`;
  7047. }
  7048. },
  7049. {
  7050. zoomTo: 28,
  7051. period: 'year',
  7052. default: true,
  7053. className: 'gstc-date-big',
  7054. format({ timeStart }) {
  7055. return timeStart.format('YYYY');
  7056. }
  7057. },
  7058. {
  7059. zoomTo: 29,
  7060. period: 'year',
  7061. className: 'gstc-date-medium',
  7062. format({ timeStart }) {
  7063. return timeStart.format('YYYY');
  7064. }
  7065. },
  7066. {
  7067. zoomTo: 30,
  7068. period: 'year',
  7069. className: 'gstc-date-medium',
  7070. format({ timeStart }) {
  7071. return timeStart.format('YY');
  7072. }
  7073. },
  7074. {
  7075. zoomTo: 100,
  7076. period: 'year',
  7077. default: true,
  7078. format() {
  7079. return null;
  7080. }
  7081. }
  7082. ]
  7083. }
  7084. ]
  7085. },
  7086. grid: {
  7087. block: {
  7088. onCreate: []
  7089. }
  7090. },
  7091. items: {},
  7092. spacing: 1
  7093. },
  7094. slots,
  7095. classNames: {},
  7096. actions,
  7097. locale: {
  7098. name: 'en',
  7099. weekdays: 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'),
  7100. weekdaysShort: 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'),
  7101. weekdaysMin: 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'),
  7102. months: 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'),
  7103. monthsShort: 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'),
  7104. weekStart: 1,
  7105. relativeTime: {
  7106. future: 'in %s',
  7107. past: '%s ago',
  7108. s: 'a few seconds',
  7109. m: 'a minute',
  7110. mm: '%d minutes',
  7111. h: 'an hour',
  7112. hh: '%d hours',
  7113. d: 'a day',
  7114. dd: '%d days',
  7115. M: 'a month',
  7116. MM: '%d months',
  7117. y: 'a year',
  7118. yy: '%d years'
  7119. },
  7120. formats: {
  7121. LT: 'HH:mm',
  7122. LTS: 'HH:mm:ss',
  7123. L: 'DD/MM/YYYY',
  7124. LL: 'D MMMM YYYY',
  7125. LLL: 'D MMMM YYYY HH:mm',
  7126. LLLL: 'dddd, D MMMM YYYY HH:mm'
  7127. },
  7128. ordinal: (n) => {
  7129. const s = ['th', 'st', 'nd', 'rd'];
  7130. const v = n % 100;
  7131. return `[${n}${s[(v - 20) % 10] || s[v] || s[0]}]`;
  7132. }
  7133. },
  7134. utcMode: false,
  7135. usageStatistics: true
  7136. };
  7137. }
  7138. var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
  7139. function createCommonjsModule(fn, module) {
  7140. return module = { exports: {} }, fn(module, module.exports), module.exports;
  7141. }
  7142. var dayjs_min = createCommonjsModule(function (module, exports) {
  7143. !function(t,n){module.exports=n();}(commonjsGlobal,function(){var t="millisecond",n="second",e="minute",r="hour",i="day",s="week",u="month",o="quarter",a="year",h=/^(\d{4})-?(\d{1,2})-?(\d{0,2})[^0-9]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?.?(\d{1,3})?$/,f=/\[([^\]]+)]|Y{2,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g,c=function(t,n,e){var r=String(t);return !r||r.length>=n?t:""+Array(n+1-r.length).join(e)+t},d={s:c,z:function(t){var n=-t.utcOffset(),e=Math.abs(n),r=Math.floor(e/60),i=e%60;return (n<=0?"+":"-")+c(r,2,"0")+":"+c(i,2,"0")},m:function(t,n){var e=12*(n.year()-t.year())+(n.month()-t.month()),r=t.clone().add(e,u),i=n-r<0,s=t.clone().add(e+(i?-1:1),u);return Number(-(e+(n-r)/(i?r-s:s-r))||0)},a:function(t){return t<0?Math.ceil(t)||0:Math.floor(t)},p:function(h){return {M:u,y:a,w:s,d:i,D:"date",h:r,m:e,s:n,ms:t,Q:o}[h]||String(h||"").toLowerCase().replace(/s$/,"")},u:function(t){return void 0===t}},$={name:"en",weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_")},l="en",m={};m[l]=$;var y=function(t){return t instanceof v},M=function(t,n,e){var r;if(!t)return l;if("string"==typeof t)m[t]&&(r=t),n&&(m[t]=n,r=t);else{var i=t.name;m[i]=t,r=i;}return !e&&r&&(l=r),r||!e&&l},g=function(t,n,e){if(y(t))return t.clone();var r=n?"string"==typeof n?{format:n,pl:e}:n:{};return r.date=t,new v(r)},D=d;D.l=M,D.i=y,D.w=function(t,n){return g(t,{locale:n.$L,utc:n.$u,$offset:n.$offset})};var v=function(){function c(t){this.$L=this.$L||M(t.locale,null,!0),this.parse(t);}var d=c.prototype;return d.parse=function(t){this.$d=function(t){var n=t.date,e=t.utc;if(null===n)return new Date(NaN);if(D.u(n))return new Date;if(n instanceof Date)return new Date(n);if("string"==typeof n&&!/Z$/i.test(n)){var r=n.match(h);if(r)return e?new Date(Date.UTC(r[1],r[2]-1,r[3]||1,r[4]||0,r[5]||0,r[6]||0,r[7]||0)):new Date(r[1],r[2]-1,r[3]||1,r[4]||0,r[5]||0,r[6]||0,r[7]||0)}return new Date(n)}(t),this.init();},d.init=function(){var t=this.$d;this.$y=t.getFullYear(),this.$M=t.getMonth(),this.$D=t.getDate(),this.$W=t.getDay(),this.$H=t.getHours(),this.$m=t.getMinutes(),this.$s=t.getSeconds(),this.$ms=t.getMilliseconds();},d.$utils=function(){return D},d.isValid=function(){return !("Invalid Date"===this.$d.toString())},d.isSame=function(t,n){var e=g(t);return this.startOf(n)<=e&&e<=this.endOf(n)},d.isAfter=function(t,n){return g(t)<this.startOf(n)},d.isBefore=function(t,n){return this.endOf(n)<g(t)},d.$g=function(t,n,e){return D.u(t)?this[n]:this.set(e,t)},d.year=function(t){return this.$g(t,"$y",a)},d.month=function(t){return this.$g(t,"$M",u)},d.day=function(t){return this.$g(t,"$W",i)},d.date=function(t){return this.$g(t,"$D","date")},d.hour=function(t){return this.$g(t,"$H",r)},d.minute=function(t){return this.$g(t,"$m",e)},d.second=function(t){return this.$g(t,"$s",n)},d.millisecond=function(n){return this.$g(n,"$ms",t)},d.unix=function(){return Math.floor(this.valueOf()/1e3)},d.valueOf=function(){return this.$d.getTime()},d.startOf=function(t,o){var h=this,f=!!D.u(o)||o,c=D.p(t),d=function(t,n){var e=D.w(h.$u?Date.UTC(h.$y,n,t):new Date(h.$y,n,t),h);return f?e:e.endOf(i)},$=function(t,n){return D.w(h.toDate()[t].apply(h.toDate(),(f?[0,0,0,0]:[23,59,59,999]).slice(n)),h)},l=this.$W,m=this.$M,y=this.$D,M="set"+(this.$u?"UTC":"");switch(c){case a:return f?d(1,0):d(31,11);case u:return f?d(1,m):d(0,m+1);case s:var g=this.$locale().weekStart||0,v=(l<g?l+7:l)-g;return d(f?y-v:y+(6-v),m);case i:case"date":return $(M+"Hours",0);case r:return $(M+"Minutes",1);case e:return $(M+"Seconds",2);case n:return $(M+"Milliseconds",3);default:return this.clone()}},d.endOf=function(t){return this.startOf(t,!1)},d.$set=function(s,o){var h,f=D.p(s),c="set"+(this.$u?"UTC":""),d=(h={},h[i]=c+"Date",h.date=c+"Date",h[u]=c+"Month",h[a]=c+"FullYear",h[r]=c+"Hours",h[e]=c+"Minutes",h[n]=c+"Seconds",h[t]=c+"Milliseconds",h)[f],$=f===i?this.$D+(o-this.$W):o;if(f===u||f===a){var l=this.clone().set("date",1);l.$d[d]($),l.init(),t
  7144. });
  7145. var utc = createCommonjsModule(function (module, exports) {
  7146. !function(t,i){module.exports=i();}(commonjsGlobal,function(){return function(t,i,e){var s=(new Date).getTimezoneOffset(),n=i.prototype;e.utc=function(t,e){return new i({date:t,utc:!0,format:e})},n.utc=function(){return e(this.toDate(),{locale:this.$L,utc:!0})},n.local=function(){return e(this.toDate(),{locale:this.$L,utc:!1})};var u=n.parse;n.parse=function(t){t.utc&&(this.$u=!0),this.$utils().u(t.$offset)||(this.$offset=t.$offset),u.call(this,t);};var o=n.init;n.init=function(){if(this.$u){var t=this.$d;this.$y=t.getUTCFullYear(),this.$M=t.getUTCMonth(),this.$D=t.getUTCDate(),this.$W=t.getUTCDay(),this.$H=t.getUTCHours(),this.$m=t.getUTCMinutes(),this.$s=t.getUTCSeconds(),this.$ms=t.getUTCMilliseconds();}else o.call(this);};var f=n.utcOffset;n.utcOffset=function(t){var i=this.$utils().u;if(i(t))return this.$u?0:i(this.$offset)?f.call(this):this.$offset;var e,n=Math.abs(t)<=16?60*t:t;return 0!==t?(e=this.local().add(n+s,"minute")).$offset=n:e=this.utc(),e};var r=n.format;n.format=function(t){var i=t||(this.$u?"YYYY-MM-DDTHH:mm:ss[Z]":"");return r.call(this,i)},n.valueOf=function(){var t=this.$utils().u(this.$offset)?0:this.$offset+s;return this.$d.valueOf()-6e4*t},n.isUTC=function(){return !!this.$u},n.toISOString=function(){return this.toDate().toISOString()},n.toString=function(){return this.toDate().toUTCString()};}});
  7147. });
  7148. var advancedFormat = createCommonjsModule(function (module, exports) {
  7149. !function(e,t){module.exports=t();}(commonjsGlobal,function(){return function(e,t,r){var n=t.prototype,o=n.format;r.en.ordinal=function(e){var t=["th","st","nd","rd"],r=e%100;return "["+e+(t[(r-20)%10]||t[r]||t[0])+"]"},n.format=function(e){var t=this,r=this.$locale(),n=this.$utils(),a=(e||"YYYY-MM-DDTHH:mm:ssZ").replace(/\[([^\]]+)]|Q|wo|ww|w|gggg|Do|X|x|k{1,2}|S/g,function(e){switch(e){case"Q":return Math.ceil((t.$M+1)/3);case"Do":return r.ordinal(t.$D);case"gggg":return t.weekYear();case"wo":return r.ordinal(t.week(),"W");case"w":case"ww":return n.s(t.week(),"w"===e?1:2,"0");case"k":case"kk":return n.s(String(0===t.$H?24:t.$H),"k"===e?1:2,"0");case"X":return Math.floor(t.$d.getTime()/1e3);case"x":return t.$d.getTime();default:return e}});return o.bind(this)(a)};}});
  7150. });
  7151. var weekOfYear = createCommonjsModule(function (module, exports) {
  7152. !function(e,t){module.exports=t();}(commonjsGlobal,function(){var e="week",t="year";return function(i,n){var r=n.prototype;r.week=function(i){if(void 0===i&&(i=null),null!==i)return this.add(7*(i-this.week()),"day");var n=this.$locale().yearStart||1;if(11===this.month()&&this.date()>25){var r=this.startOf(t).add(1,t).date(n),f=this.endOf(e);if(r.isBefore(f))return 1}var s=this.startOf(t).date(n).startOf(e).subtract(1,"millisecond"),a=this.diff(s,e,!0);return a<0?this.startOf("week").week():Math.ceil(a)},r.weeks=function(e){return void 0===e&&(e=null),this.week(e)};}});
  7153. });
  7154. /**
  7155. * Gantt-Schedule-Timeline-Calendar
  7156. *
  7157. * @copyright Rafal Pospiech <https://neuronet.io>
  7158. * @author Rafal Pospiech <neuronet.io@gmail.com>
  7159. * @package gantt-schedule-timeline-calendar
  7160. * @license AGPL-3.0
  7161. */
  7162. dayjs_min.extend(advancedFormat);
  7163. dayjs_min.extend(weekOfYear);
  7164. class TimeApi {
  7165. constructor(state) {
  7166. this.utcMode = false;
  7167. this.state = state;
  7168. this.locale = state.get('config.locale');
  7169. this.utcMode = state.get('config.utcMode');
  7170. if (this.utcMode) {
  7171. dayjs_min.extend(utc);
  7172. }
  7173. // @ts-ignore
  7174. dayjs_min.locale(this.locale, null, true);
  7175. }
  7176. date(time) {
  7177. const _dayjs = this.utcMode ? dayjs_min.utc : dayjs_min;
  7178. return time ? _dayjs(time).locale(this.locale.name) : _dayjs().locale(this.locale.name);
  7179. }
  7180. addAdditionalSpace(time) {
  7181. if (time.additionalSpaces && time.additionalSpaces[time.period]) {
  7182. const add = time.additionalSpaces[time.period];
  7183. if (add.before) {
  7184. time.finalFrom = this.date(time.from)
  7185. .subtract(add.before, add.period)
  7186. .valueOf();
  7187. }
  7188. if (add.after) {
  7189. time.finalTo = this.date(time.to)
  7190. .add(add.after, add.period)
  7191. .valueOf();
  7192. }
  7193. }
  7194. return time;
  7195. }
  7196. recalculateFromTo(time) {
  7197. const period = time.period;
  7198. time = Object.assign({}, time);
  7199. time.from = +time.from;
  7200. time.to = +time.to;
  7201. let from = Number.MAX_SAFE_INTEGER, to = 0;
  7202. const items = this.state.get('config.chart.items');
  7203. if (Object.keys(items).length === 0) {
  7204. return time;
  7205. }
  7206. if (time.from === 0 || time.to === 0) {
  7207. for (const itemId in items) {
  7208. const item = items[itemId];
  7209. if (item.time.start < from && item.time.start) {
  7210. from = item.time.start;
  7211. }
  7212. if (item.time.end > to) {
  7213. to = item.time.end;
  7214. }
  7215. }
  7216. if (time.from === 0) {
  7217. time.from = this.date(from)
  7218. .startOf(period)
  7219. .valueOf();
  7220. }
  7221. if (time.to === 0) {
  7222. time.to = this.date(to)
  7223. .endOf(period)
  7224. .valueOf();
  7225. }
  7226. }
  7227. time.finalFrom = time.from;
  7228. time.finalTo = time.to;
  7229. time = this.addAdditionalSpace(time);
  7230. return time;
  7231. }
  7232. getCenter(time) {
  7233. return time.leftGlobal + (time.rightGlobal - time.leftGlobal) / 2;
  7234. }
  7235. timeToPixelOffset(milliseconds) {
  7236. const timePerPixel = this.state.get('_internal.chart.time.timePerPixel') || 1;
  7237. return milliseconds / timePerPixel;
  7238. }
  7239. globalTimeToViewPixelOffset(milliseconds, withCompensation = false) {
  7240. const time = this.state.get('_internal.chart.time');
  7241. let xCompensation = this.state.get('config.scroll.compensation.x') || 0;
  7242. const viewPixelOffset = (milliseconds - time.leftGlobal) / time.timePerPixel;
  7243. if (withCompensation)
  7244. return viewPixelOffset + xCompensation;
  7245. return viewPixelOffset;
  7246. }
  7247. }
  7248. // forked from https://github.com/joonhocho/superwild
  7249. function Matcher(pattern, wchar = '*') {
  7250. this.wchar = wchar;
  7251. this.pattern = pattern;
  7252. this.segments = [];
  7253. this.starCount = 0;
  7254. this.minLength = 0;
  7255. this.maxLength = 0;
  7256. this.segStartIndex = 0;
  7257. for (let i = 0, len = pattern.length; i < len; i += 1) {
  7258. const char = pattern[i];
  7259. if (char === wchar) {
  7260. this.starCount += 1;
  7261. if (i > this.segStartIndex) {
  7262. this.segments.push(pattern.substring(this.segStartIndex, i));
  7263. }
  7264. this.segments.push(char);
  7265. this.segStartIndex = i + 1;
  7266. }
  7267. }
  7268. if (this.segStartIndex < pattern.length) {
  7269. this.segments.push(pattern.substring(this.segStartIndex));
  7270. }
  7271. if (this.starCount) {
  7272. this.minLength = pattern.length - this.starCount;
  7273. this.maxLength = Infinity;
  7274. }
  7275. else {
  7276. this.maxLength = this.minLength = pattern.length;
  7277. }
  7278. }
  7279. Matcher.prototype.match = function match(match) {
  7280. if (this.pattern === this.wchar) {
  7281. return true;
  7282. }
  7283. if (this.segments.length === 0) {
  7284. return this.pattern === match;
  7285. }
  7286. const { length } = match;
  7287. if (length < this.minLength || length > this.maxLength) {
  7288. return false;
  7289. }
  7290. let segLeftIndex = 0;
  7291. let segRightIndex = this.segments.length - 1;
  7292. let rightPos = match.length - 1;
  7293. let rightIsStar = false;
  7294. while (true) {
  7295. const segment = this.segments[segRightIndex];
  7296. segRightIndex -= 1;
  7297. if (segment === this.wchar) {
  7298. rightIsStar = true;
  7299. }
  7300. else {
  7301. const lastIndex = rightPos + 1 - segment.length;
  7302. const index = match.lastIndexOf(segment, lastIndex);
  7303. if (index === -1 || index > lastIndex) {
  7304. return false;
  7305. }
  7306. if (rightIsStar) {
  7307. rightPos = index - 1;
  7308. rightIsStar = false;
  7309. }
  7310. else {
  7311. if (index !== lastIndex) {
  7312. return false;
  7313. }
  7314. rightPos -= segment.length;
  7315. }
  7316. }
  7317. if (segLeftIndex > segRightIndex) {
  7318. break;
  7319. }
  7320. }
  7321. return true;
  7322. };
  7323. function WildcardObject(obj, delimeter, wildcard) {
  7324. this.obj = obj;
  7325. this.delimeter = delimeter;
  7326. this.wildcard = wildcard;
  7327. }
  7328. WildcardObject.prototype.simpleMatch = function simpleMatch(first, second) {
  7329. if (first === second)
  7330. return true;
  7331. if (first === this.wildcard)
  7332. return true;
  7333. const index = first.indexOf(this.wildcard);
  7334. if (index > -1) {
  7335. const end = first.substr(index + 1);
  7336. if (index === 0 || second.substring(0, index) === first.substring(0, index)) {
  7337. const len = end.length;
  7338. if (len > 0) {
  7339. return second.substr(-len) === end;
  7340. }
  7341. return true;
  7342. }
  7343. }
  7344. return false;
  7345. };
  7346. WildcardObject.prototype.match = function match(first, second) {
  7347. return (first === second ||
  7348. first === this.wildcard ||
  7349. second === this.wildcard ||
  7350. this.simpleMatch(first, second) ||
  7351. new Matcher(first).match(second));
  7352. };
  7353. WildcardObject.prototype.handleArray = function handleArray(wildcard, currentArr, partIndex, path, result = {}) {
  7354. let nextPartIndex = wildcard.indexOf(this.delimeter, partIndex);
  7355. let end = false;
  7356. if (nextPartIndex === -1) {
  7357. end = true;
  7358. nextPartIndex = wildcard.length;
  7359. }
  7360. const currentWildcardPath = wildcard.substring(partIndex, nextPartIndex);
  7361. let index = 0;
  7362. for (const item of currentArr) {
  7363. const key = index.toString();
  7364. const currentPath = path === '' ? key : path + this.delimeter + index;
  7365. if (currentWildcardPath === this.wildcard ||
  7366. currentWildcardPath === key ||
  7367. this.simpleMatch(currentWildcardPath, key)) {
  7368. end ? (result[currentPath] = item) : this.goFurther(wildcard, item, nextPartIndex + 1, currentPath, result);
  7369. }
  7370. index++;
  7371. }
  7372. return result;
  7373. };
  7374. WildcardObject.prototype.handleObject = function handleObject(wildcard, currentObj, partIndex, path, result = {}) {
  7375. let nextPartIndex = wildcard.indexOf(this.delimeter, partIndex);
  7376. let end = false;
  7377. if (nextPartIndex === -1) {
  7378. end = true;
  7379. nextPartIndex = wildcard.length;
  7380. }
  7381. const currentWildcardPath = wildcard.substring(partIndex, nextPartIndex);
  7382. for (let key in currentObj) {
  7383. key = key.toString();
  7384. const currentPath = path === '' ? key : path + this.delimeter + key;
  7385. if (currentWildcardPath === this.wildcard ||
  7386. currentWildcardPath === key ||
  7387. this.simpleMatch(currentWildcardPath, key)) {
  7388. end
  7389. ? (result[currentPath] = currentObj[key])
  7390. : this.goFurther(wildcard, currentObj[key], nextPartIndex + 1, currentPath, result);
  7391. }
  7392. }
  7393. return result;
  7394. };
  7395. WildcardObject.prototype.goFurther = function goFurther(wildcard, currentObj, partIndex, currentPath, result = {}) {
  7396. if (Array.isArray(currentObj)) {
  7397. return this.handleArray(wildcard, currentObj, partIndex, currentPath, result);
  7398. }
  7399. return this.handleObject(wildcard, currentObj, partIndex, currentPath, result);
  7400. };
  7401. WildcardObject.prototype.get = function get(wildcard) {
  7402. return this.goFurther(wildcard, this.obj, 0, '');
  7403. };
  7404. class ObjectPath {
  7405. static get(path, obj, copiedPath = null) {
  7406. if (copiedPath === null) {
  7407. copiedPath = path.slice();
  7408. }
  7409. if (copiedPath.length === 0 || typeof obj === "undefined") {
  7410. return obj;
  7411. }
  7412. const currentPath = copiedPath.shift();
  7413. if (!obj.hasOwnProperty(currentPath)) {
  7414. return undefined;
  7415. }
  7416. if (copiedPath.length === 0) {
  7417. return obj[currentPath];
  7418. }
  7419. return ObjectPath.get(path, obj[currentPath], copiedPath);
  7420. }
  7421. static set(path, newValue, obj, copiedPath = null) {
  7422. if (copiedPath === null) {
  7423. copiedPath = path.slice();
  7424. }
  7425. if (copiedPath.length === 0) {
  7426. for (const key in obj) {
  7427. delete obj[key];
  7428. }
  7429. for (const key in newValue) {
  7430. obj[key] = newValue[key];
  7431. }
  7432. return;
  7433. }
  7434. const currentPath = copiedPath.shift();
  7435. if (copiedPath.length === 0) {
  7436. obj[currentPath] = newValue;
  7437. return;
  7438. }
  7439. if (!obj) {
  7440. obj = {};
  7441. }
  7442. if (!obj.hasOwnProperty(currentPath)) {
  7443. obj[currentPath] = {};
  7444. }
  7445. ObjectPath.set(path, newValue, obj[currentPath], copiedPath);
  7446. }
  7447. }
  7448. function log(message, info) {
  7449. console.debug(message, info);
  7450. }
  7451. const defaultOptions$1 = {
  7452. delimeter: `.`,
  7453. notRecursive: `;`,
  7454. param: `:`,
  7455. wildcard: `*`,
  7456. log
  7457. };
  7458. const defaultListenerOptions = {
  7459. bulk: false,
  7460. debug: false,
  7461. source: "",
  7462. data: undefined
  7463. };
  7464. const defaultUpdateOptions = {
  7465. only: [],
  7466. source: "",
  7467. debug: false,
  7468. data: undefined,
  7469. updateAfter: false
  7470. };
  7471. class DeepState {
  7472. constructor(data = {}, options = defaultOptions$1) {
  7473. this.listeners = new Map();
  7474. this.waitingListeners = new Map();
  7475. this.data = data;
  7476. this.options = Object.assign(Object.assign({}, defaultOptions$1), options);
  7477. this.id = 0;
  7478. this.pathGet = ObjectPath.get;
  7479. this.pathSet = ObjectPath.set;
  7480. this.scan = new WildcardObject(this.data, this.options.delimeter, this.options.wildcard);
  7481. }
  7482. getListeners() {
  7483. return this.listeners;
  7484. }
  7485. destroy() {
  7486. this.data = undefined;
  7487. this.listeners = new Map();
  7488. }
  7489. match(first, second) {
  7490. if (first === second)
  7491. return true;
  7492. if (first === this.options.wildcard || second === this.options.wildcard)
  7493. return true;
  7494. return this.scan.match(first, second);
  7495. }
  7496. getIndicesOf(searchStr, str) {
  7497. const searchStrLen = searchStr.length;
  7498. if (searchStrLen == 0) {
  7499. return [];
  7500. }
  7501. let startIndex = 0, index, indices = [];
  7502. while ((index = str.indexOf(searchStr, startIndex)) > -1) {
  7503. indices.push(index);
  7504. startIndex = index + searchStrLen;
  7505. }
  7506. return indices;
  7507. }
  7508. getIndicesCount(searchStr, str) {
  7509. const searchStrLen = searchStr.length;
  7510. if (searchStrLen == 0) {
  7511. return 0;
  7512. }
  7513. let startIndex = 0, index, indices = 0;
  7514. while ((index = str.indexOf(searchStr, startIndex)) > -1) {
  7515. indices++;
  7516. startIndex = index + searchStrLen;
  7517. }
  7518. return indices;
  7519. }
  7520. cutPath(longer, shorter) {
  7521. longer = this.cleanNotRecursivePath(longer);
  7522. shorter = this.cleanNotRecursivePath(shorter);
  7523. const shorterPartsLen = this.getIndicesCount(this.options.delimeter, shorter);
  7524. const longerParts = this.getIndicesOf(this.options.delimeter, longer);
  7525. return longer.substr(0, longerParts[shorterPartsLen]);
  7526. }
  7527. trimPath(path) {
  7528. path = this.cleanNotRecursivePath(path);
  7529. if (path.charAt(0) === this.options.delimeter) {
  7530. return path.substr(1);
  7531. }
  7532. return path;
  7533. }
  7534. split(path) {
  7535. return path === "" ? [] : path.split(this.options.delimeter);
  7536. }
  7537. isWildcard(path) {
  7538. return path.includes(this.options.wildcard);
  7539. }
  7540. isNotRecursive(path) {
  7541. return path.endsWith(this.options.notRecursive);
  7542. }
  7543. cleanNotRecursivePath(path) {
  7544. return this.isNotRecursive(path) ? path.substring(0, path.length - 1) : path;
  7545. }
  7546. hasParams(path) {
  7547. return path.includes(this.options.param);
  7548. }
  7549. getParamsInfo(path) {
  7550. let paramsInfo = { replaced: "", original: path, params: {} };
  7551. let partIndex = 0;
  7552. let fullReplaced = [];
  7553. for (const part of this.split(path)) {
  7554. paramsInfo.params[partIndex] = {
  7555. original: part,
  7556. replaced: "",
  7557. name: ""
  7558. };
  7559. const reg = new RegExp(`\\${this.options.param}([^\\${this.options.delimeter}\\${this.options.param}]+)`, "g");
  7560. let param = reg.exec(part);
  7561. if (param) {
  7562. paramsInfo.params[partIndex].name = param[1];
  7563. }
  7564. else {
  7565. delete paramsInfo.params[partIndex];
  7566. fullReplaced.push(part);
  7567. partIndex++;
  7568. continue;
  7569. }
  7570. reg.lastIndex = 0;
  7571. paramsInfo.params[partIndex].replaced = part.replace(reg, this.options.wildcard);
  7572. fullReplaced.push(paramsInfo.params[partIndex].replaced);
  7573. partIndex++;
  7574. }
  7575. paramsInfo.replaced = fullReplaced.join(this.options.delimeter);
  7576. return paramsInfo;
  7577. }
  7578. getParams(paramsInfo, path) {
  7579. if (!paramsInfo) {
  7580. return undefined;
  7581. }
  7582. const split = this.split(path);
  7583. const result = {};
  7584. for (const partIndex in paramsInfo.params) {
  7585. const param = paramsInfo.params[partIndex];
  7586. result[param.name] = split[partIndex];
  7587. }
  7588. return result;
  7589. }
  7590. waitForAll(userPaths, fn) {
  7591. const paths = {};
  7592. for (let path of userPaths) {
  7593. paths[path] = { dirty: false };
  7594. if (this.hasParams(path)) {
  7595. paths[path].paramsInfo = this.getParamsInfo(path);
  7596. }
  7597. paths[path].isWildcard = this.isWildcard(path);
  7598. paths[path].isRecursive = !this.isNotRecursive(path);
  7599. }
  7600. this.waitingListeners.set(userPaths, { fn, paths });
  7601. fn(paths);
  7602. return function unsubscribe() {
  7603. this.waitingListeners.delete(userPaths);
  7604. };
  7605. }
  7606. executeWaitingListeners(updatePath) {
  7607. for (const waitingListener of this.waitingListeners.values()) {
  7608. const { fn, paths } = waitingListener;
  7609. let dirty = 0;
  7610. let all = 0;
  7611. for (let path in paths) {
  7612. const pathInfo = paths[path];
  7613. let match = false;
  7614. if (pathInfo.isRecursive)
  7615. updatePath = this.cutPath(updatePath, path);
  7616. if (pathInfo.isWildcard && this.match(path, updatePath))
  7617. match = true;
  7618. if (updatePath === path)
  7619. match = true;
  7620. if (match) {
  7621. pathInfo.dirty = true;
  7622. }
  7623. if (pathInfo.dirty) {
  7624. dirty++;
  7625. }
  7626. all++;
  7627. }
  7628. if (dirty === all) {
  7629. fn(paths);
  7630. }
  7631. }
  7632. }
  7633. subscribeAll(userPaths, fn, options = defaultListenerOptions) {
  7634. let unsubscribers = [];
  7635. for (const userPath of userPaths) {
  7636. unsubscribers.push(this.subscribe(userPath, fn, options));
  7637. }
  7638. return function unsubscribe() {
  7639. for (const unsubscribe of unsubscribers) {
  7640. unsubscribe();
  7641. }
  7642. };
  7643. }
  7644. getCleanListenersCollection(values = {}) {
  7645. return Object.assign({ listeners: new Map(), isRecursive: false, isWildcard: false, hasParams: false, match: undefined, paramsInfo: undefined, path: undefined, count: 0 }, values);
  7646. }
  7647. getCleanListener(fn, options = defaultListenerOptions) {
  7648. return {
  7649. fn,
  7650. options: Object.assign(Object.assign({}, defaultListenerOptions), options)
  7651. };
  7652. }
  7653. getListenerCollectionMatch(listenerPath, isRecursive, isWildcard) {
  7654. listenerPath = this.cleanNotRecursivePath(listenerPath);
  7655. const self = this;
  7656. return function listenerCollectionMatch(path) {
  7657. if (isRecursive)
  7658. path = self.cutPath(path, listenerPath);
  7659. if (isWildcard && self.match(listenerPath, path))
  7660. return true;
  7661. return listenerPath === path;
  7662. };
  7663. }
  7664. getListenersCollection(listenerPath, listener) {
  7665. if (this.listeners.has(listenerPath)) {
  7666. let listenersCollection = this.listeners.get(listenerPath);
  7667. listenersCollection.listeners.set(++this.id, listener);
  7668. return listenersCollection;
  7669. }
  7670. let collCfg = {
  7671. isRecursive: true,
  7672. isWildcard: false,
  7673. hasParams: false,
  7674. paramsInfo: undefined,
  7675. originalPath: listenerPath,
  7676. path: listenerPath
  7677. };
  7678. if (this.hasParams(collCfg.path)) {
  7679. collCfg.paramsInfo = this.getParamsInfo(collCfg.path);
  7680. collCfg.path = collCfg.paramsInfo.replaced;
  7681. collCfg.hasParams = true;
  7682. }
  7683. collCfg.isWildcard = this.isWildcard(collCfg.path);
  7684. if (this.isNotRecursive(collCfg.path)) {
  7685. collCfg.isRecursive = false;
  7686. }
  7687. let listenersCollection = this.getCleanListenersCollection(Object.assign(Object.assign({}, collCfg), { match: this.getListenerCollectionMatch(collCfg.path, collCfg.isRecursive, collCfg.isWildcard) }));
  7688. this.id++;
  7689. listenersCollection.listeners.set(this.id, listener);
  7690. this.listeners.set(collCfg.path, listenersCollection);
  7691. return listenersCollection;
  7692. }
  7693. subscribe(listenerPath, fn, options = defaultListenerOptions, type = "subscribe") {
  7694. let listener = this.getCleanListener(fn, options);
  7695. const listenersCollection = this.getListenersCollection(listenerPath, listener);
  7696. listenersCollection.count++;
  7697. listenerPath = listenersCollection.path;
  7698. if (!listenersCollection.isWildcard) {
  7699. fn(this.pathGet(this.split(this.cleanNotRecursivePath(listenerPath)), this.data), {
  7700. type,
  7701. listener,
  7702. listenersCollection,
  7703. path: {
  7704. listener: listenerPath,
  7705. update: undefined,
  7706. resolved: this.cleanNotRecursivePath(listenerPath)
  7707. },
  7708. params: this.getParams(listenersCollection.paramsInfo, listenerPath),
  7709. options
  7710. });
  7711. }
  7712. else {
  7713. const paths = this.scan.get(this.cleanNotRecursivePath(listenerPath));
  7714. if (options.bulk) {
  7715. const bulkValue = [];
  7716. for (const path in paths) {
  7717. bulkValue.push({
  7718. path,
  7719. params: this.getParams(listenersCollection.paramsInfo, path),
  7720. value: paths[path]
  7721. });
  7722. }
  7723. fn(bulkValue, {
  7724. type,
  7725. listener,
  7726. listenersCollection,
  7727. path: {
  7728. listener: listenerPath,
  7729. update: undefined,
  7730. resolved: undefined
  7731. },
  7732. options,
  7733. params: undefined
  7734. });
  7735. }
  7736. else {
  7737. for (const path in paths) {
  7738. fn(paths[path], {
  7739. type,
  7740. listener,
  7741. listenersCollection,
  7742. path: {
  7743. listener: listenerPath,
  7744. update: undefined,
  7745. resolved: this.cleanNotRecursivePath(path)
  7746. },
  7747. params: this.getParams(listenersCollection.paramsInfo, path),
  7748. options
  7749. });
  7750. }
  7751. }
  7752. }
  7753. this.debugSubscribe(listener, listenersCollection, listenerPath);
  7754. return this.unsubscribe(listenerPath, this.id);
  7755. }
  7756. unsubscribe(path, id) {
  7757. const listeners = this.listeners;
  7758. const listenersCollection = listeners.get(path);
  7759. return function unsub() {
  7760. listenersCollection.listeners.delete(id);
  7761. listenersCollection.count--;
  7762. if (listenersCollection.count === 0) {
  7763. listeners.delete(path);
  7764. }
  7765. };
  7766. }
  7767. same(newValue, oldValue) {
  7768. return ((["number", "string", "undefined", "boolean"].includes(typeof newValue) || newValue === null) &&
  7769. oldValue === newValue);
  7770. }
  7771. notifyListeners(listeners, exclude = [], returnNotified = true) {
  7772. const alreadyNotified = [];
  7773. for (const path in listeners) {
  7774. let { single, bulk } = listeners[path];
  7775. for (const singleListener of single) {
  7776. if (exclude.includes(singleListener))
  7777. continue;
  7778. const time = this.debugTime(singleListener);
  7779. singleListener.listener.fn(singleListener.value(), singleListener.eventInfo);
  7780. if (returnNotified)
  7781. alreadyNotified.push(singleListener);
  7782. this.debugListener(time, singleListener);
  7783. }
  7784. for (const bulkListener of bulk) {
  7785. if (exclude.includes(bulkListener))
  7786. continue;
  7787. const time = this.debugTime(bulkListener);
  7788. const bulkValue = [];
  7789. for (const bulk of bulkListener.value) {
  7790. bulkValue.push(Object.assign(Object.assign({}, bulk), { value: bulk.value() }));
  7791. }
  7792. bulkListener.listener.fn(bulkValue, bulkListener.eventInfo);
  7793. if (returnNotified)
  7794. alreadyNotified.push(bulkListener);
  7795. this.debugListener(time, bulkListener);
  7796. }
  7797. }
  7798. return alreadyNotified;
  7799. }
  7800. getSubscribedListeners(updatePath, newValue, options, type = "update", originalPath = null) {
  7801. options = Object.assign(Object.assign({}, defaultUpdateOptions), options);
  7802. const listeners = {};
  7803. for (let [listenerPath, listenersCollection] of this.listeners) {
  7804. listeners[listenerPath] = { single: [], bulk: [], bulkData: [] };
  7805. if (listenersCollection.match(updatePath)) {
  7806. const params = listenersCollection.paramsInfo
  7807. ? this.getParams(listenersCollection.paramsInfo, updatePath)
  7808. : undefined;
  7809. const value = listenersCollection.isRecursive || listenersCollection.isWildcard
  7810. ? () => this.get(this.cutPath(updatePath, listenerPath))
  7811. : () => newValue;
  7812. const bulkValue = [{ value, path: updatePath, params }];
  7813. for (const listener of listenersCollection.listeners.values()) {
  7814. if (listener.options.bulk) {
  7815. listeners[listenerPath].bulk.push({
  7816. listener,
  7817. listenersCollection,
  7818. eventInfo: {
  7819. type,
  7820. listener,
  7821. path: {
  7822. listener: listenerPath,
  7823. update: originalPath ? originalPath : updatePath,
  7824. resolved: undefined
  7825. },
  7826. params,
  7827. options
  7828. },
  7829. value: bulkValue
  7830. });
  7831. }
  7832. else {
  7833. listeners[listenerPath].single.push({
  7834. listener,
  7835. listenersCollection,
  7836. eventInfo: {
  7837. type,
  7838. listener,
  7839. path: {
  7840. listener: listenerPath,
  7841. update: originalPath ? originalPath : updatePath,
  7842. resolved: this.cleanNotRecursivePath(updatePath)
  7843. },
  7844. params,
  7845. options
  7846. },
  7847. value
  7848. });
  7849. }
  7850. }
  7851. }
  7852. }
  7853. return listeners;
  7854. }
  7855. notifySubscribedListeners(updatePath, newValue, options, type = "update", originalPath = null) {
  7856. return this.notifyListeners(this.getSubscribedListeners(updatePath, newValue, options, type, originalPath));
  7857. }
  7858. getNestedListeners(updatePath, newValue, options, type = "update", originalPath = null) {
  7859. const listeners = {};
  7860. for (let [listenerPath, listenersCollection] of this.listeners) {
  7861. listeners[listenerPath] = { single: [], bulk: [] };
  7862. const currentCuttedPath = this.cutPath(listenerPath, updatePath);
  7863. if (this.match(currentCuttedPath, updatePath)) {
  7864. const restPath = this.trimPath(listenerPath.substr(currentCuttedPath.length));
  7865. const values = new WildcardObject(newValue, this.options.delimeter, this.options.wildcard).get(restPath);
  7866. const params = listenersCollection.paramsInfo
  7867. ? this.getParams(listenersCollection.paramsInfo, updatePath)
  7868. : undefined;
  7869. const bulk = [];
  7870. const bulkListeners = {};
  7871. for (const currentRestPath in values) {
  7872. const value = () => values[currentRestPath];
  7873. const fullPath = [updatePath, currentRestPath].join(this.options.delimeter);
  7874. for (const [listenerId, listener] of listenersCollection.listeners) {
  7875. const eventInfo = {
  7876. type,
  7877. listener,
  7878. listenersCollection,
  7879. path: {
  7880. listener: listenerPath,
  7881. update: originalPath ? originalPath : updatePath,
  7882. resolved: this.cleanNotRecursivePath(fullPath)
  7883. },
  7884. params,
  7885. options
  7886. };
  7887. if (listener.options.bulk) {
  7888. bulk.push({ value, path: fullPath, params });
  7889. bulkListeners[listenerId] = listener;
  7890. }
  7891. else {
  7892. listeners[listenerPath].single.push({
  7893. listener,
  7894. listenersCollection,
  7895. eventInfo,
  7896. value
  7897. });
  7898. }
  7899. }
  7900. }
  7901. for (const listenerId in bulkListeners) {
  7902. const listener = bulkListeners[listenerId];
  7903. const eventInfo = {
  7904. type,
  7905. listener,
  7906. listenersCollection,
  7907. path: {
  7908. listener: listenerPath,
  7909. update: updatePath,
  7910. resolved: undefined
  7911. },
  7912. options,
  7913. params
  7914. };
  7915. listeners[listenerPath].bulk.push({
  7916. listener,
  7917. listenersCollection,
  7918. eventInfo,
  7919. value: bulk
  7920. });
  7921. }
  7922. }
  7923. }
  7924. return listeners;
  7925. }
  7926. notifyNestedListeners(updatePath, newValue, options, type = "update", alreadyNotified, originalPath = null) {
  7927. return this.notifyListeners(this.getNestedListeners(updatePath, newValue, options, type, originalPath), alreadyNotified, false);
  7928. }
  7929. getNotifyOnlyListeners(updatePath, newValue, options, type = "update", originalPath = null) {
  7930. const listeners = {};
  7931. if (typeof options.only !== "object" ||
  7932. !Array.isArray(options.only) ||
  7933. typeof options.only[0] === "undefined" ||
  7934. !this.canBeNested(newValue)) {
  7935. return listeners;
  7936. }
  7937. for (const notifyPath of options.only) {
  7938. const wildcardScan = new WildcardObject(newValue, this.options.delimeter, this.options.wildcard).get(notifyPath);
  7939. listeners[notifyPath] = { bulk: [], single: [] };
  7940. for (const wildcardPath in wildcardScan) {
  7941. const fullPath = updatePath + this.options.delimeter + wildcardPath;
  7942. for (const [listenerPath, listenersCollection] of this.listeners) {
  7943. const params = listenersCollection.paramsInfo
  7944. ? this.getParams(listenersCollection.paramsInfo, fullPath)
  7945. : undefined;
  7946. if (this.match(listenerPath, fullPath)) {
  7947. const value = () => wildcardScan[wildcardPath];
  7948. const bulkValue = [{ value, path: fullPath, params }];
  7949. for (const listener of listenersCollection.listeners.values()) {
  7950. const eventInfo = {
  7951. type,
  7952. listener,
  7953. listenersCollection,
  7954. path: {
  7955. listener: listenerPath,
  7956. update: originalPath ? originalPath : updatePath,
  7957. resolved: this.cleanNotRecursivePath(fullPath)
  7958. },
  7959. params,
  7960. options
  7961. };
  7962. if (listener.options.bulk) {
  7963. if (!listeners[notifyPath].bulk.some(bulkListener => bulkListener.listener === listener)) {
  7964. listeners[notifyPath].bulk.push({
  7965. listener,
  7966. listenersCollection,
  7967. eventInfo,
  7968. value: bulkValue
  7969. });
  7970. }
  7971. }
  7972. else {
  7973. listeners[notifyPath].single.push({
  7974. listener,
  7975. listenersCollection,
  7976. eventInfo,
  7977. value
  7978. });
  7979. }
  7980. }
  7981. }
  7982. }
  7983. }
  7984. }
  7985. return listeners;
  7986. }
  7987. notifyOnly(updatePath, newValue, options, type = "update", originalPath = "") {
  7988. return (typeof this.notifyListeners(this.getNotifyOnlyListeners(updatePath, newValue, options, type, originalPath))[0] !==
  7989. "undefined");
  7990. }
  7991. canBeNested(newValue) {
  7992. return typeof newValue === "object" && newValue !== null;
  7993. }
  7994. getUpdateValues(oldValue, split, fn) {
  7995. if (typeof oldValue === "object" && oldValue !== null) {
  7996. Array.isArray(oldValue) ? (oldValue = oldValue.slice()) : (oldValue = Object.assign({}, oldValue));
  7997. }
  7998. let newValue = fn;
  7999. if (typeof fn === "function") {
  8000. newValue = fn(this.pathGet(split, this.data));
  8001. }
  8002. return { newValue, oldValue };
  8003. }
  8004. wildcardUpdate(updatePath, fn, options = defaultUpdateOptions) {
  8005. options = Object.assign(Object.assign({}, defaultUpdateOptions), options);
  8006. const scanned = this.scan.get(updatePath);
  8007. const bulk = {};
  8008. for (const path in scanned) {
  8009. const split = this.split(path);
  8010. const { oldValue, newValue } = this.getUpdateValues(scanned[path], split, fn);
  8011. if (!this.same(newValue, oldValue))
  8012. bulk[path] = newValue;
  8013. }
  8014. const groupedListenersPack = [];
  8015. const waitingPaths = [];
  8016. for (const path in bulk) {
  8017. const newValue = bulk[path];
  8018. if (options.only.length) {
  8019. groupedListenersPack.push(this.getNotifyOnlyListeners(path, newValue, options, "update", updatePath));
  8020. }
  8021. else {
  8022. groupedListenersPack.push(this.getSubscribedListeners(path, newValue, options, "update", updatePath));
  8023. this.canBeNested(newValue) &&
  8024. groupedListenersPack.push(this.getNestedListeners(path, newValue, options, "update", updatePath));
  8025. }
  8026. options.debug && this.options.log("Wildcard update", { path, newValue });
  8027. this.pathSet(this.split(path), newValue, this.data);
  8028. waitingPaths.push(path);
  8029. }
  8030. let alreadyNotified = [];
  8031. for (const groupedListeners of groupedListenersPack) {
  8032. alreadyNotified = [...alreadyNotified, ...this.notifyListeners(groupedListeners, alreadyNotified)];
  8033. }
  8034. for (const path of waitingPaths) {
  8035. this.executeWaitingListeners(path);
  8036. }
  8037. }
  8038. update(updatePath, fn, options = defaultUpdateOptions) {
  8039. if (this.isWildcard(updatePath)) {
  8040. return this.wildcardUpdate(updatePath, fn, options);
  8041. }
  8042. const split = this.split(updatePath);
  8043. const { oldValue, newValue } = this.getUpdateValues(this.pathGet(split, this.data), split, fn);
  8044. if (options.debug) {
  8045. this.options.log(`Updating ${updatePath} ${options.source ? `from ${options.source}` : ""}`, {
  8046. oldValue,
  8047. newValue
  8048. });
  8049. }
  8050. if (this.same(newValue, oldValue)) {
  8051. return newValue;
  8052. }
  8053. if (!options.updateAfter) {
  8054. this.pathSet(split, newValue, this.data);
  8055. }
  8056. options = Object.assign(Object.assign({}, defaultUpdateOptions), options);
  8057. if (options.only === null) {
  8058. return newValue;
  8059. }
  8060. if (options.only.length) {
  8061. this.notifyOnly(updatePath, newValue, options);
  8062. this.executeWaitingListeners(updatePath);
  8063. return newValue;
  8064. }
  8065. const alreadyNotified = this.notifySubscribedListeners(updatePath, newValue, options);
  8066. if (this.canBeNested(newValue)) {
  8067. this.notifyNestedListeners(updatePath, newValue, options, "update", alreadyNotified);
  8068. }
  8069. this.executeWaitingListeners(updatePath);
  8070. if (options.updateAfter) {
  8071. this.pathSet(split, newValue, this.data);
  8072. }
  8073. return newValue;
  8074. }
  8075. get(userPath = undefined) {
  8076. if (typeof userPath === "undefined" || userPath === "") {
  8077. return this.data;
  8078. }
  8079. return this.pathGet(this.split(userPath), this.data);
  8080. }
  8081. debugSubscribe(listener, listenersCollection, listenerPath) {
  8082. if (listener.options.debug) {
  8083. this.options.log("listener subscribed", {
  8084. listenerPath,
  8085. listener,
  8086. listenersCollection
  8087. });
  8088. }
  8089. }
  8090. debugListener(time, groupedListener) {
  8091. if (groupedListener.eventInfo.options.debug || groupedListener.listener.options.debug) {
  8092. this.options.log("Listener fired", {
  8093. time: Date.now() - time,
  8094. info: groupedListener
  8095. });
  8096. }
  8097. }
  8098. debugTime(groupedListener) {
  8099. return groupedListener.listener.options.debug || groupedListener.eventInfo.options.debug ? Date.now() : 0;
  8100. }
  8101. }
  8102. /**
  8103. * Schedule - a throttle function that uses requestAnimationFrame to limit the rate at which a function is called.
  8104. *
  8105. * @param {function} fn
  8106. * @returns {function}
  8107. */
  8108. /**
  8109. * Is object - helper function to determine if specified variable is an object
  8110. *
  8111. * @param {any} item
  8112. * @returns {boolean}
  8113. */
  8114. function isObject$1(item) {
  8115. return item && typeof item === 'object' && !Array.isArray(item);
  8116. }
  8117. /**
  8118. * Merge deep - helper function which will merge objects recursively - creating brand new one - like clone
  8119. *
  8120. * @param {object} target
  8121. * @params {object} sources
  8122. * @returns {object}
  8123. */
  8124. function mergeDeep$1(target, ...sources) {
  8125. const source = sources.shift();
  8126. if (isObject$1(target) && isObject$1(source)) {
  8127. for (const key in source) {
  8128. if (isObject$1(source[key])) {
  8129. if (typeof target[key] === 'undefined') {
  8130. target[key] = {};
  8131. }
  8132. target[key] = mergeDeep$1(target[key], source[key]);
  8133. }
  8134. else if (Array.isArray(source[key])) {
  8135. target[key] = [];
  8136. for (let item of source[key]) {
  8137. if (isObject$1(item)) {
  8138. target[key].push(mergeDeep$1({}, item));
  8139. continue;
  8140. }
  8141. target[key].push(item);
  8142. }
  8143. }
  8144. else {
  8145. target[key] = source[key];
  8146. }
  8147. }
  8148. }
  8149. if (!sources.length) {
  8150. return target;
  8151. }
  8152. return mergeDeep$1(target, ...sources);
  8153. }
  8154. /**
  8155. * Api functions
  8156. *
  8157. * @copyright Rafal Pospiech <https://neuronet.io>
  8158. * @author Rafal Pospiech <neuronet.io@gmail.com>
  8159. * @package gantt-schedule-timeline-calendar
  8160. * @license AGPL-3.0
  8161. */
  8162. const lib = 'gantt-schedule-timeline-calendar';
  8163. function mergeActions(userConfig, defaultConfig) {
  8164. const defaultConfigActions = mergeDeep$1({}, defaultConfig.actions);
  8165. const userActions = mergeDeep$1({}, userConfig.actions);
  8166. let allActionNames = [...Object.keys(defaultConfigActions), ...Object.keys(userActions)];
  8167. allActionNames = allActionNames.filter(i => allActionNames.includes(i));
  8168. const actions = {};
  8169. for (const actionName of allActionNames) {
  8170. actions[actionName] = [];
  8171. if (typeof defaultConfigActions[actionName] !== 'undefined' && Array.isArray(defaultConfigActions[actionName])) {
  8172. actions[actionName] = [...defaultConfigActions[actionName]];
  8173. }
  8174. if (typeof userActions[actionName] !== 'undefined' && Array.isArray(userActions[actionName])) {
  8175. actions[actionName] = [...actions[actionName], ...userActions[actionName]];
  8176. }
  8177. }
  8178. delete userConfig.actions;
  8179. delete defaultConfig.actions;
  8180. return actions;
  8181. }
  8182. function stateFromConfig(userConfig) {
  8183. const defaultConfig$1 = defaultConfig();
  8184. const actions = mergeActions(userConfig, defaultConfig$1);
  8185. const state = { config: mergeDeep$1({}, defaultConfig$1, userConfig) };
  8186. state.config.actions = actions;
  8187. // @ts-ignore
  8188. return (this.state = new DeepState(state, { delimeter: '.' }));
  8189. }
  8190. const publicApi = {
  8191. name: lib,
  8192. stateFromConfig,
  8193. mergeDeep: mergeDeep$1,
  8194. date(time) {
  8195. return time ? dayjs_min(time) : dayjs_min();
  8196. },
  8197. setPeriod(period) {
  8198. this.state.update('config.chart.time.period', period);
  8199. return this.state.get('config.chart.time.zoom');
  8200. },
  8201. dayjs: dayjs_min
  8202. };
  8203. function getInternalApi(state) {
  8204. let $state = state.get();
  8205. let unsubscribes = [];
  8206. const iconsCache = {};
  8207. const api = {
  8208. name: lib,
  8209. debug: false,
  8210. setVido(Vido) {
  8211. },
  8212. log(...args) {
  8213. if (this.debug) {
  8214. console.log.call(console, ...args);
  8215. }
  8216. },
  8217. mergeDeep: mergeDeep$1,
  8218. getClass(name) {
  8219. let simple = `${lib}__${name}`;
  8220. if (name === this.name) {
  8221. simple = this.name;
  8222. }
  8223. return simple;
  8224. },
  8225. allActions: [],
  8226. getActions(name) {
  8227. if (!this.allActions.includes(name))
  8228. this.allActions.push(name);
  8229. let actions = state.get('config.actions.' + name);
  8230. if (typeof actions === 'undefined') {
  8231. actions = [];
  8232. }
  8233. return actions.slice();
  8234. },
  8235. isItemInViewport(item, left, right) {
  8236. return ((item.time.start >= left && item.time.start < right) ||
  8237. (item.time.end >= left && item.time.end < right) ||
  8238. (item.time.start <= left && item.time.end >= right));
  8239. },
  8240. prepareItems(items) {
  8241. for (const item of items) {
  8242. item.time.start = +item.time.start;
  8243. item.time.end = +item.time.end;
  8244. item.id = String(item.id);
  8245. }
  8246. return items;
  8247. },
  8248. fillEmptyRowValues(rows) {
  8249. let top = 0;
  8250. for (const rowId in rows) {
  8251. const row = rows[rowId];
  8252. row._internal = {
  8253. parents: [],
  8254. children: [],
  8255. items: []
  8256. };
  8257. if (typeof row.height !== 'number') {
  8258. row.height = $state.config.list.rowHeight;
  8259. }
  8260. if (typeof row.expanded !== 'boolean') {
  8261. row.expanded = false;
  8262. }
  8263. row.top = top;
  8264. top += row.height;
  8265. }
  8266. return rows;
  8267. },
  8268. generateParents(rows, parentName = 'parentId') {
  8269. const parents = {};
  8270. for (const row of rows) {
  8271. const parentId = row[parentName] !== undefined && row[parentName] !== null ? row[parentName] : '';
  8272. if (parents[parentId] === undefined) {
  8273. parents[parentId] = {};
  8274. }
  8275. parents[parentId][row.id] = row;
  8276. }
  8277. return parents;
  8278. },
  8279. fastTree(rowParents, node, parents = []) {
  8280. const children = rowParents[node.id];
  8281. node._internal.parents = parents;
  8282. if (typeof children === 'undefined') {
  8283. node._internal.children = [];
  8284. return node;
  8285. }
  8286. if (node.id !== '') {
  8287. parents = [...parents, node.id];
  8288. }
  8289. node._internal.children = Object.values(children);
  8290. for (const childrenId in children) {
  8291. const child = children[childrenId];
  8292. this.fastTree(rowParents, child, parents);
  8293. }
  8294. return node;
  8295. },
  8296. makeTreeMap(rows, items) {
  8297. const itemParents = this.generateParents(items, 'rowId');
  8298. for (const row of rows) {
  8299. row._internal.items = itemParents[row.id] !== undefined ? Object.values(itemParents[row.id]) : [];
  8300. }
  8301. const rowParents = this.generateParents(rows);
  8302. const tree = { id: '', _internal: { children: [], parents: [], items: [] } };
  8303. return this.fastTree(rowParents, tree);
  8304. },
  8305. getFlatTreeMapById(treeMap, flatTreeMapById = {}) {
  8306. for (const child of treeMap._internal.children) {
  8307. flatTreeMapById[child.id] = child;
  8308. this.getFlatTreeMapById(child, flatTreeMapById);
  8309. }
  8310. return flatTreeMapById;
  8311. },
  8312. flattenTreeMap(treeMap, rows = []) {
  8313. for (const child of treeMap._internal.children) {
  8314. rows.push(child.id);
  8315. this.flattenTreeMap(child, rows);
  8316. }
  8317. return rows;
  8318. },
  8319. getRowsFromMap(flatTreeMap, rows) {
  8320. return flatTreeMap.map(node => rows[node.id]);
  8321. },
  8322. getRowsFromIds(ids, rows) {
  8323. const result = [];
  8324. for (const id of ids) {
  8325. result.push(rows[id]);
  8326. }
  8327. return result;
  8328. },
  8329. getRowsWithParentsExpanded(flatTreeMap, flatTreeMapById, rows) {
  8330. if (!flatTreeMap ||
  8331. !flatTreeMapById ||
  8332. !rows ||
  8333. flatTreeMap.length === 0 ||
  8334. flatTreeMapById.length === 0 ||
  8335. Object.keys(rows).length === 0) {
  8336. return [];
  8337. }
  8338. const rowsWithParentsExpanded = [];
  8339. next: for (const rowId of flatTreeMap) {
  8340. for (const parentId of flatTreeMapById[rowId]._internal.parents) {
  8341. const parent = rows[parentId];
  8342. if (!parent || !parent.expanded) {
  8343. continue next;
  8344. }
  8345. }
  8346. rowsWithParentsExpanded.push(rowId);
  8347. }
  8348. return rowsWithParentsExpanded;
  8349. },
  8350. getRowsHeight(rows) {
  8351. let height = 0;
  8352. for (const row of rows) {
  8353. if (row)
  8354. height += row.height;
  8355. }
  8356. return height;
  8357. },
  8358. /**
  8359. * Get visible rows - get rows that are inside current viewport (height)
  8360. *
  8361. * @param {array} rowsWithParentsExpanded rows that have parent expanded- they are visible
  8362. */
  8363. getVisibleRowsAndCompensation(rowsWithParentsExpanded) {
  8364. const visibleRows = [];
  8365. let currentRowsOffset = 0;
  8366. let rowOffset = 0;
  8367. const scrollTop = state.get('config.scroll.top');
  8368. const height = state.get('_internal.height');
  8369. let chartViewBottom = 0;
  8370. let compensation = 0;
  8371. for (const row of rowsWithParentsExpanded) {
  8372. if (row === undefined)
  8373. continue;
  8374. chartViewBottom = scrollTop + height;
  8375. if (currentRowsOffset + row.height >= scrollTop && currentRowsOffset <= chartViewBottom) {
  8376. row.top = rowOffset;
  8377. compensation = row.top + scrollTop - currentRowsOffset;
  8378. rowOffset += row.height;
  8379. visibleRows.push(row);
  8380. }
  8381. currentRowsOffset += row.height;
  8382. if (currentRowsOffset >= chartViewBottom) {
  8383. break;
  8384. }
  8385. }
  8386. return { visibleRows, compensation };
  8387. },
  8388. /**
  8389. * Normalize mouse wheel event to get proper scroll metrics
  8390. *
  8391. * @param {Event} event mouse wheel event
  8392. */
  8393. normalizeMouseWheelEvent(event) {
  8394. // @ts-ignore
  8395. let x = event.deltaX || 0;
  8396. // @ts-ignore
  8397. let y = event.deltaY || 0;
  8398. // @ts-ignore
  8399. let z = event.deltaZ || 0;
  8400. // @ts-ignore
  8401. const mode = event.deltaMode;
  8402. const lineHeight = state.get('config.list.rowHeight');
  8403. let scale = 1;
  8404. switch (mode) {
  8405. case 1:
  8406. if (lineHeight) {
  8407. scale = lineHeight;
  8408. }
  8409. break;
  8410. case 2:
  8411. // @ts-ignore
  8412. scale = window.height;
  8413. break;
  8414. }
  8415. x *= scale;
  8416. y *= scale;
  8417. z *= scale;
  8418. return { x, y, z, event };
  8419. },
  8420. normalizePointerEvent(event) {
  8421. const result = { x: 0, y: 0, pageX: 0, pageY: 0, clientX: 0, clientY: 0, screenX: 0, screenY: 0 };
  8422. switch (event.type) {
  8423. case 'wheel':
  8424. const wheel = this.normalizeMouseWheelEvent(event);
  8425. result.x = wheel.x;
  8426. result.y = wheel.y;
  8427. result.pageX = result.x;
  8428. result.pageY = result.y;
  8429. result.screenX = result.x;
  8430. result.screenY = result.y;
  8431. result.clientX = result.x;
  8432. result.clientY = result.y;
  8433. break;
  8434. case 'touchstart':
  8435. case 'touchmove':
  8436. case 'touchend':
  8437. case 'touchcancel':
  8438. result.x = event.changedTouches[0].screenX;
  8439. result.y = event.changedTouches[0].screenY;
  8440. result.pageX = event.changedTouches[0].pageX;
  8441. result.pageY = event.changedTouches[0].pageY;
  8442. result.screenX = event.changedTouches[0].screenX;
  8443. result.screenY = event.changedTouches[0].screenY;
  8444. result.clientX = event.changedTouches[0].clientX;
  8445. result.clientY = event.changedTouches[0].clientY;
  8446. break;
  8447. default:
  8448. result.x = event.x;
  8449. result.y = event.y;
  8450. result.pageX = event.pageX;
  8451. result.pageY = event.pageY;
  8452. result.screenX = event.screenX;
  8453. result.screenY = event.screenY;
  8454. result.clientX = event.clientX;
  8455. result.clientY = event.clientY;
  8456. break;
  8457. }
  8458. return result;
  8459. },
  8460. limitScrollLeft(totalViewDurationPx, chartWidth, scrollLeft) {
  8461. const width = totalViewDurationPx - chartWidth;
  8462. if (scrollLeft < 0) {
  8463. scrollLeft = 0;
  8464. }
  8465. else if (scrollLeft > width) {
  8466. scrollLeft = width;
  8467. }
  8468. return Math.round(scrollLeft);
  8469. },
  8470. limitScrollTop(rowsHeight, internalHeight, scrollTop) {
  8471. const height = rowsHeight - internalHeight;
  8472. if (scrollTop < 0) {
  8473. scrollTop = 0;
  8474. }
  8475. else if (scrollTop > height) {
  8476. scrollTop = height;
  8477. }
  8478. return Math.round(scrollTop);
  8479. },
  8480. time: new TimeApi(state),
  8481. /**
  8482. * Get scrollbar height - compute it from element
  8483. *
  8484. * @returns {number}
  8485. */
  8486. getScrollBarHeight(add = 0) {
  8487. const outer = document.createElement('div');
  8488. outer.style.visibility = 'hidden';
  8489. outer.style.height = '100px';
  8490. document.body.appendChild(outer);
  8491. const noScroll = outer.offsetHeight;
  8492. outer.style.msOverflowStyle = 'scrollbar';
  8493. outer.style.overflow = 'scroll';
  8494. const inner = document.createElement('div');
  8495. inner.style.height = '100%';
  8496. outer.appendChild(inner);
  8497. const withScroll = inner.offsetHeight;
  8498. outer.parentNode.removeChild(outer);
  8499. return noScroll - withScroll + add;
  8500. },
  8501. scrollToTime(toTime) {
  8502. const time = state.get('_internal.chart.time');
  8503. state.update('config.scroll', scroll => {
  8504. const chartWidth = state.get('_internal.chart.dimensions.width');
  8505. const halfTime = (chartWidth / 2) * time.timePerPixel;
  8506. const leftGlobal = toTime - halfTime - time.finalFrom;
  8507. scroll.left = this.limitScrollLeft(time.totalViewDurationPx, chartWidth, leftGlobal / time.timePerPixel);
  8508. return scroll;
  8509. });
  8510. },
  8511. /**
  8512. * Get grid blocks that are under specified rectangle
  8513. *
  8514. * @param {number} x beginging at chart-timeline bounding rect
  8515. * @param {number} y beginging at chart-timeline bounding rect
  8516. * @param {number} width
  8517. * @param {number} height
  8518. * @returns {array} array of {element, data}
  8519. */
  8520. getGridBlocksUnderRect(x, y, width, height) {
  8521. const main = state.get('_internal.elements.main');
  8522. if (!main)
  8523. return [];
  8524. },
  8525. getCompensationX() {
  8526. return state.get('config.scroll.compensation.x') || 0;
  8527. },
  8528. getCompensationY() {
  8529. return state.get('config.scroll.compensation.y') || 0;
  8530. },
  8531. getSVGIconSrc(svg) {
  8532. if (typeof iconsCache[svg] === 'string')
  8533. return iconsCache[svg];
  8534. iconsCache[svg] = 'data:image/svg+xml;base64,' + btoa(svg);
  8535. return iconsCache[svg];
  8536. },
  8537. /**
  8538. * Destroy things to release memory
  8539. */
  8540. destroy() {
  8541. $state = undefined;
  8542. for (const unsubscribe of unsubscribes) {
  8543. unsubscribe();
  8544. }
  8545. unsubscribes = [];
  8546. if (api.debug) {
  8547. // @ts-ignore
  8548. delete window.state;
  8549. }
  8550. }
  8551. };
  8552. if (api.debug) {
  8553. // @ts-ignore
  8554. window.state = state;
  8555. // @ts-ignore
  8556. window.api = api;
  8557. }
  8558. return api;
  8559. }
  8560. /**
  8561. * Gantt-Schedule-Timeline-Calendar
  8562. *
  8563. * @copyright Rafal Pospiech <https://neuronet.io>
  8564. * @author Rafal Pospiech <neuronet.io@gmail.com>
  8565. * @package gantt-schedule-timeline-calendar
  8566. * @license AGPL-3.0
  8567. */
  8568. function GSTC(options) {
  8569. const state = options.state;
  8570. const api = getInternalApi(state);
  8571. const _internal = {
  8572. components: {
  8573. Main
  8574. },
  8575. scrollBarHeight: api.getScrollBarHeight(2),
  8576. height: 0,
  8577. treeMap: {},
  8578. flatTreeMap: [],
  8579. flatTreeMapById: {},
  8580. list: {
  8581. expandedHeight: 0,
  8582. visibleRows: [],
  8583. rows: {},
  8584. width: 0
  8585. },
  8586. dimensions: {
  8587. width: 0,
  8588. height: 0
  8589. },
  8590. chart: {
  8591. dimensions: {
  8592. width: 0,
  8593. innerWidth: 0
  8594. },
  8595. visibleItems: [],
  8596. time: {
  8597. levels: [],
  8598. timePerPixel: 0,
  8599. firstTaskTime: 0,
  8600. lastTaskTime: 0,
  8601. totalViewDurationMs: 0,
  8602. totalViewDurationPx: 0,
  8603. leftGlobal: 0,
  8604. rightGlobal: 0,
  8605. leftPx: 0,
  8606. rightPx: 0,
  8607. leftInner: 0,
  8608. rightInner: 0,
  8609. maxWidth: {}
  8610. }
  8611. },
  8612. elements: {},
  8613. cache: {
  8614. calendar: {}
  8615. },
  8616. loaded: {}
  8617. };
  8618. if (typeof options.debug === 'boolean' && options.debug) {
  8619. // @ts-ignore
  8620. window.state = state;
  8621. }
  8622. state.update('', oldValue => {
  8623. return {
  8624. config: oldValue.config,
  8625. _internal
  8626. };
  8627. });
  8628. // @ts-ignore
  8629. const vido = Vido(state, api);
  8630. api.setVido(vido);
  8631. const app = vido.createApp({ component: Main, props: {}, element: options.element });
  8632. const internalApi = app.vidoInstance.api;
  8633. return { state, app, api: internalApi };
  8634. }
  8635. GSTC.api = publicApi;
  8636. return GSTC;
  8637. })));
  8638. //# sourceMappingURL=index.umd.js.map