This is a gentle fork from https://framagit.org/marienfressinaud/photos.marienfressinaud.fr with a responsive and optimized mindset. https://media.larlet.fr/
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.

stimulus-3.0.1.js 63KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944
  1. /*
  2. Stimulus 3.0.1
  3. Copyright © 2021 Basecamp, LLC
  4. */
  5. class EventListener {
  6. constructor(eventTarget, eventName, eventOptions) {
  7. this.eventTarget = eventTarget;
  8. this.eventName = eventName;
  9. this.eventOptions = eventOptions;
  10. this.unorderedBindings = new Set();
  11. }
  12. connect() {
  13. this.eventTarget.addEventListener(this.eventName, this, this.eventOptions);
  14. }
  15. disconnect() {
  16. this.eventTarget.removeEventListener(this.eventName, this, this.eventOptions);
  17. }
  18. bindingConnected(binding) {
  19. this.unorderedBindings.add(binding);
  20. }
  21. bindingDisconnected(binding) {
  22. this.unorderedBindings.delete(binding);
  23. }
  24. handleEvent(event) {
  25. const extendedEvent = extendEvent(event);
  26. for (const binding of this.bindings) {
  27. if (extendedEvent.immediatePropagationStopped) {
  28. break;
  29. }
  30. else {
  31. binding.handleEvent(extendedEvent);
  32. }
  33. }
  34. }
  35. get bindings() {
  36. return Array.from(this.unorderedBindings).sort((left, right) => {
  37. const leftIndex = left.index, rightIndex = right.index;
  38. return leftIndex < rightIndex ? -1 : leftIndex > rightIndex ? 1 : 0;
  39. });
  40. }
  41. }
  42. function extendEvent(event) {
  43. if ("immediatePropagationStopped" in event) {
  44. return event;
  45. }
  46. else {
  47. const { stopImmediatePropagation } = event;
  48. return Object.assign(event, {
  49. immediatePropagationStopped: false,
  50. stopImmediatePropagation() {
  51. this.immediatePropagationStopped = true;
  52. stopImmediatePropagation.call(this);
  53. }
  54. });
  55. }
  56. }
  57. class Dispatcher {
  58. constructor(application) {
  59. this.application = application;
  60. this.eventListenerMaps = new Map;
  61. this.started = false;
  62. }
  63. start() {
  64. if (!this.started) {
  65. this.started = true;
  66. this.eventListeners.forEach(eventListener => eventListener.connect());
  67. }
  68. }
  69. stop() {
  70. if (this.started) {
  71. this.started = false;
  72. this.eventListeners.forEach(eventListener => eventListener.disconnect());
  73. }
  74. }
  75. get eventListeners() {
  76. return Array.from(this.eventListenerMaps.values())
  77. .reduce((listeners, map) => listeners.concat(Array.from(map.values())), []);
  78. }
  79. bindingConnected(binding) {
  80. this.fetchEventListenerForBinding(binding).bindingConnected(binding);
  81. }
  82. bindingDisconnected(binding) {
  83. this.fetchEventListenerForBinding(binding).bindingDisconnected(binding);
  84. }
  85. handleError(error, message, detail = {}) {
  86. this.application.handleError(error, `Error ${message}`, detail);
  87. }
  88. fetchEventListenerForBinding(binding) {
  89. const { eventTarget, eventName, eventOptions } = binding;
  90. return this.fetchEventListener(eventTarget, eventName, eventOptions);
  91. }
  92. fetchEventListener(eventTarget, eventName, eventOptions) {
  93. const eventListenerMap = this.fetchEventListenerMapForEventTarget(eventTarget);
  94. const cacheKey = this.cacheKey(eventName, eventOptions);
  95. let eventListener = eventListenerMap.get(cacheKey);
  96. if (!eventListener) {
  97. eventListener = this.createEventListener(eventTarget, eventName, eventOptions);
  98. eventListenerMap.set(cacheKey, eventListener);
  99. }
  100. return eventListener;
  101. }
  102. createEventListener(eventTarget, eventName, eventOptions) {
  103. const eventListener = new EventListener(eventTarget, eventName, eventOptions);
  104. if (this.started) {
  105. eventListener.connect();
  106. }
  107. return eventListener;
  108. }
  109. fetchEventListenerMapForEventTarget(eventTarget) {
  110. let eventListenerMap = this.eventListenerMaps.get(eventTarget);
  111. if (!eventListenerMap) {
  112. eventListenerMap = new Map;
  113. this.eventListenerMaps.set(eventTarget, eventListenerMap);
  114. }
  115. return eventListenerMap;
  116. }
  117. cacheKey(eventName, eventOptions) {
  118. const parts = [eventName];
  119. Object.keys(eventOptions).sort().forEach(key => {
  120. parts.push(`${eventOptions[key] ? "" : "!"}${key}`);
  121. });
  122. return parts.join(":");
  123. }
  124. }
  125. const descriptorPattern = /^((.+?)(@(window|document))?->)?(.+?)(#([^:]+?))(:(.+))?$/;
  126. function parseActionDescriptorString(descriptorString) {
  127. const source = descriptorString.trim();
  128. const matches = source.match(descriptorPattern) || [];
  129. return {
  130. eventTarget: parseEventTarget(matches[4]),
  131. eventName: matches[2],
  132. eventOptions: matches[9] ? parseEventOptions(matches[9]) : {},
  133. identifier: matches[5],
  134. methodName: matches[7]
  135. };
  136. }
  137. function parseEventTarget(eventTargetName) {
  138. if (eventTargetName == "window") {
  139. return window;
  140. }
  141. else if (eventTargetName == "document") {
  142. return document;
  143. }
  144. }
  145. function parseEventOptions(eventOptions) {
  146. return eventOptions.split(":").reduce((options, token) => Object.assign(options, { [token.replace(/^!/, "")]: !/^!/.test(token) }), {});
  147. }
  148. function stringifyEventTarget(eventTarget) {
  149. if (eventTarget == window) {
  150. return "window";
  151. }
  152. else if (eventTarget == document) {
  153. return "document";
  154. }
  155. }
  156. function camelize(value) {
  157. return value.replace(/(?:[_-])([a-z0-9])/g, (_, char) => char.toUpperCase());
  158. }
  159. function capitalize(value) {
  160. return value.charAt(0).toUpperCase() + value.slice(1);
  161. }
  162. function dasherize(value) {
  163. return value.replace(/([A-Z])/g, (_, char) => `-${char.toLowerCase()}`);
  164. }
  165. function tokenize(value) {
  166. return value.match(/[^\s]+/g) || [];
  167. }
  168. class Action {
  169. constructor(element, index, descriptor) {
  170. this.element = element;
  171. this.index = index;
  172. this.eventTarget = descriptor.eventTarget || element;
  173. this.eventName = descriptor.eventName || getDefaultEventNameForElement(element) || error("missing event name");
  174. this.eventOptions = descriptor.eventOptions || {};
  175. this.identifier = descriptor.identifier || error("missing identifier");
  176. this.methodName = descriptor.methodName || error("missing method name");
  177. }
  178. static forToken(token) {
  179. return new this(token.element, token.index, parseActionDescriptorString(token.content));
  180. }
  181. toString() {
  182. const eventNameSuffix = this.eventTargetName ? `@${this.eventTargetName}` : "";
  183. return `${this.eventName}${eventNameSuffix}->${this.identifier}#${this.methodName}`;
  184. }
  185. get params() {
  186. if (this.eventTarget instanceof Element) {
  187. return this.getParamsFromEventTargetAttributes(this.eventTarget);
  188. }
  189. else {
  190. return {};
  191. }
  192. }
  193. getParamsFromEventTargetAttributes(eventTarget) {
  194. const params = {};
  195. const pattern = new RegExp(`^data-${this.identifier}-(.+)-param$`);
  196. const attributes = Array.from(eventTarget.attributes);
  197. attributes.forEach(({ name, value }) => {
  198. const match = name.match(pattern);
  199. const key = match && match[1];
  200. if (key) {
  201. Object.assign(params, { [camelize(key)]: typecast(value) });
  202. }
  203. });
  204. return params;
  205. }
  206. get eventTargetName() {
  207. return stringifyEventTarget(this.eventTarget);
  208. }
  209. }
  210. const defaultEventNames = {
  211. "a": e => "click",
  212. "button": e => "click",
  213. "form": e => "submit",
  214. "details": e => "toggle",
  215. "input": e => e.getAttribute("type") == "submit" ? "click" : "input",
  216. "select": e => "change",
  217. "textarea": e => "input"
  218. };
  219. function getDefaultEventNameForElement(element) {
  220. const tagName = element.tagName.toLowerCase();
  221. if (tagName in defaultEventNames) {
  222. return defaultEventNames[tagName](element);
  223. }
  224. }
  225. function error(message) {
  226. throw new Error(message);
  227. }
  228. function typecast(value) {
  229. try {
  230. return JSON.parse(value);
  231. }
  232. catch (o_O) {
  233. return value;
  234. }
  235. }
  236. class Binding {
  237. constructor(context, action) {
  238. this.context = context;
  239. this.action = action;
  240. }
  241. get index() {
  242. return this.action.index;
  243. }
  244. get eventTarget() {
  245. return this.action.eventTarget;
  246. }
  247. get eventOptions() {
  248. return this.action.eventOptions;
  249. }
  250. get identifier() {
  251. return this.context.identifier;
  252. }
  253. handleEvent(event) {
  254. if (this.willBeInvokedByEvent(event)) {
  255. this.invokeWithEvent(event);
  256. }
  257. }
  258. get eventName() {
  259. return this.action.eventName;
  260. }
  261. get method() {
  262. const method = this.controller[this.methodName];
  263. if (typeof method == "function") {
  264. return method;
  265. }
  266. throw new Error(`Action "${this.action}" references undefined method "${this.methodName}"`);
  267. }
  268. invokeWithEvent(event) {
  269. const { target, currentTarget } = event;
  270. try {
  271. const { params } = this.action;
  272. const actionEvent = Object.assign(event, { params });
  273. this.method.call(this.controller, actionEvent);
  274. this.context.logDebugActivity(this.methodName, { event, target, currentTarget, action: this.methodName });
  275. }
  276. catch (error) {
  277. const { identifier, controller, element, index } = this;
  278. const detail = { identifier, controller, element, index, event };
  279. this.context.handleError(error, `invoking action "${this.action}"`, detail);
  280. }
  281. }
  282. willBeInvokedByEvent(event) {
  283. const eventTarget = event.target;
  284. if (this.element === eventTarget) {
  285. return true;
  286. }
  287. else if (eventTarget instanceof Element && this.element.contains(eventTarget)) {
  288. return this.scope.containsElement(eventTarget);
  289. }
  290. else {
  291. return this.scope.containsElement(this.action.element);
  292. }
  293. }
  294. get controller() {
  295. return this.context.controller;
  296. }
  297. get methodName() {
  298. return this.action.methodName;
  299. }
  300. get element() {
  301. return this.scope.element;
  302. }
  303. get scope() {
  304. return this.context.scope;
  305. }
  306. }
  307. class ElementObserver {
  308. constructor(element, delegate) {
  309. this.mutationObserverInit = { attributes: true, childList: true, subtree: true };
  310. this.element = element;
  311. this.started = false;
  312. this.delegate = delegate;
  313. this.elements = new Set;
  314. this.mutationObserver = new MutationObserver((mutations) => this.processMutations(mutations));
  315. }
  316. start() {
  317. if (!this.started) {
  318. this.started = true;
  319. this.mutationObserver.observe(this.element, this.mutationObserverInit);
  320. this.refresh();
  321. }
  322. }
  323. pause(callback) {
  324. if (this.started) {
  325. this.mutationObserver.disconnect();
  326. this.started = false;
  327. }
  328. callback();
  329. if (!this.started) {
  330. this.mutationObserver.observe(this.element, this.mutationObserverInit);
  331. this.started = true;
  332. }
  333. }
  334. stop() {
  335. if (this.started) {
  336. this.mutationObserver.takeRecords();
  337. this.mutationObserver.disconnect();
  338. this.started = false;
  339. }
  340. }
  341. refresh() {
  342. if (this.started) {
  343. const matches = new Set(this.matchElementsInTree());
  344. for (const element of Array.from(this.elements)) {
  345. if (!matches.has(element)) {
  346. this.removeElement(element);
  347. }
  348. }
  349. for (const element of Array.from(matches)) {
  350. this.addElement(element);
  351. }
  352. }
  353. }
  354. processMutations(mutations) {
  355. if (this.started) {
  356. for (const mutation of mutations) {
  357. this.processMutation(mutation);
  358. }
  359. }
  360. }
  361. processMutation(mutation) {
  362. if (mutation.type == "attributes") {
  363. this.processAttributeChange(mutation.target, mutation.attributeName);
  364. }
  365. else if (mutation.type == "childList") {
  366. this.processRemovedNodes(mutation.removedNodes);
  367. this.processAddedNodes(mutation.addedNodes);
  368. }
  369. }
  370. processAttributeChange(node, attributeName) {
  371. const element = node;
  372. if (this.elements.has(element)) {
  373. if (this.delegate.elementAttributeChanged && this.matchElement(element)) {
  374. this.delegate.elementAttributeChanged(element, attributeName);
  375. }
  376. else {
  377. this.removeElement(element);
  378. }
  379. }
  380. else if (this.matchElement(element)) {
  381. this.addElement(element);
  382. }
  383. }
  384. processRemovedNodes(nodes) {
  385. for (const node of Array.from(nodes)) {
  386. const element = this.elementFromNode(node);
  387. if (element) {
  388. this.processTree(element, this.removeElement);
  389. }
  390. }
  391. }
  392. processAddedNodes(nodes) {
  393. for (const node of Array.from(nodes)) {
  394. const element = this.elementFromNode(node);
  395. if (element && this.elementIsActive(element)) {
  396. this.processTree(element, this.addElement);
  397. }
  398. }
  399. }
  400. matchElement(element) {
  401. return this.delegate.matchElement(element);
  402. }
  403. matchElementsInTree(tree = this.element) {
  404. return this.delegate.matchElementsInTree(tree);
  405. }
  406. processTree(tree, processor) {
  407. for (const element of this.matchElementsInTree(tree)) {
  408. processor.call(this, element);
  409. }
  410. }
  411. elementFromNode(node) {
  412. if (node.nodeType == Node.ELEMENT_NODE) {
  413. return node;
  414. }
  415. }
  416. elementIsActive(element) {
  417. if (element.isConnected != this.element.isConnected) {
  418. return false;
  419. }
  420. else {
  421. return this.element.contains(element);
  422. }
  423. }
  424. addElement(element) {
  425. if (!this.elements.has(element)) {
  426. if (this.elementIsActive(element)) {
  427. this.elements.add(element);
  428. if (this.delegate.elementMatched) {
  429. this.delegate.elementMatched(element);
  430. }
  431. }
  432. }
  433. }
  434. removeElement(element) {
  435. if (this.elements.has(element)) {
  436. this.elements.delete(element);
  437. if (this.delegate.elementUnmatched) {
  438. this.delegate.elementUnmatched(element);
  439. }
  440. }
  441. }
  442. }
  443. class AttributeObserver {
  444. constructor(element, attributeName, delegate) {
  445. this.attributeName = attributeName;
  446. this.delegate = delegate;
  447. this.elementObserver = new ElementObserver(element, this);
  448. }
  449. get element() {
  450. return this.elementObserver.element;
  451. }
  452. get selector() {
  453. return `[${this.attributeName}]`;
  454. }
  455. start() {
  456. this.elementObserver.start();
  457. }
  458. pause(callback) {
  459. this.elementObserver.pause(callback);
  460. }
  461. stop() {
  462. this.elementObserver.stop();
  463. }
  464. refresh() {
  465. this.elementObserver.refresh();
  466. }
  467. get started() {
  468. return this.elementObserver.started;
  469. }
  470. matchElement(element) {
  471. return element.hasAttribute(this.attributeName);
  472. }
  473. matchElementsInTree(tree) {
  474. const match = this.matchElement(tree) ? [tree] : [];
  475. const matches = Array.from(tree.querySelectorAll(this.selector));
  476. return match.concat(matches);
  477. }
  478. elementMatched(element) {
  479. if (this.delegate.elementMatchedAttribute) {
  480. this.delegate.elementMatchedAttribute(element, this.attributeName);
  481. }
  482. }
  483. elementUnmatched(element) {
  484. if (this.delegate.elementUnmatchedAttribute) {
  485. this.delegate.elementUnmatchedAttribute(element, this.attributeName);
  486. }
  487. }
  488. elementAttributeChanged(element, attributeName) {
  489. if (this.delegate.elementAttributeValueChanged && this.attributeName == attributeName) {
  490. this.delegate.elementAttributeValueChanged(element, attributeName);
  491. }
  492. }
  493. }
  494. class StringMapObserver {
  495. constructor(element, delegate) {
  496. this.element = element;
  497. this.delegate = delegate;
  498. this.started = false;
  499. this.stringMap = new Map;
  500. this.mutationObserver = new MutationObserver(mutations => this.processMutations(mutations));
  501. }
  502. start() {
  503. if (!this.started) {
  504. this.started = true;
  505. this.mutationObserver.observe(this.element, { attributes: true, attributeOldValue: true });
  506. this.refresh();
  507. }
  508. }
  509. stop() {
  510. if (this.started) {
  511. this.mutationObserver.takeRecords();
  512. this.mutationObserver.disconnect();
  513. this.started = false;
  514. }
  515. }
  516. refresh() {
  517. if (this.started) {
  518. for (const attributeName of this.knownAttributeNames) {
  519. this.refreshAttribute(attributeName, null);
  520. }
  521. }
  522. }
  523. processMutations(mutations) {
  524. if (this.started) {
  525. for (const mutation of mutations) {
  526. this.processMutation(mutation);
  527. }
  528. }
  529. }
  530. processMutation(mutation) {
  531. const attributeName = mutation.attributeName;
  532. if (attributeName) {
  533. this.refreshAttribute(attributeName, mutation.oldValue);
  534. }
  535. }
  536. refreshAttribute(attributeName, oldValue) {
  537. const key = this.delegate.getStringMapKeyForAttribute(attributeName);
  538. if (key != null) {
  539. if (!this.stringMap.has(attributeName)) {
  540. this.stringMapKeyAdded(key, attributeName);
  541. }
  542. const value = this.element.getAttribute(attributeName);
  543. if (this.stringMap.get(attributeName) != value) {
  544. this.stringMapValueChanged(value, key, oldValue);
  545. }
  546. if (value == null) {
  547. const oldValue = this.stringMap.get(attributeName);
  548. this.stringMap.delete(attributeName);
  549. if (oldValue)
  550. this.stringMapKeyRemoved(key, attributeName, oldValue);
  551. }
  552. else {
  553. this.stringMap.set(attributeName, value);
  554. }
  555. }
  556. }
  557. stringMapKeyAdded(key, attributeName) {
  558. if (this.delegate.stringMapKeyAdded) {
  559. this.delegate.stringMapKeyAdded(key, attributeName);
  560. }
  561. }
  562. stringMapValueChanged(value, key, oldValue) {
  563. if (this.delegate.stringMapValueChanged) {
  564. this.delegate.stringMapValueChanged(value, key, oldValue);
  565. }
  566. }
  567. stringMapKeyRemoved(key, attributeName, oldValue) {
  568. if (this.delegate.stringMapKeyRemoved) {
  569. this.delegate.stringMapKeyRemoved(key, attributeName, oldValue);
  570. }
  571. }
  572. get knownAttributeNames() {
  573. return Array.from(new Set(this.currentAttributeNames.concat(this.recordedAttributeNames)));
  574. }
  575. get currentAttributeNames() {
  576. return Array.from(this.element.attributes).map(attribute => attribute.name);
  577. }
  578. get recordedAttributeNames() {
  579. return Array.from(this.stringMap.keys());
  580. }
  581. }
  582. function add(map, key, value) {
  583. fetch(map, key).add(value);
  584. }
  585. function del(map, key, value) {
  586. fetch(map, key).delete(value);
  587. prune(map, key);
  588. }
  589. function fetch(map, key) {
  590. let values = map.get(key);
  591. if (!values) {
  592. values = new Set();
  593. map.set(key, values);
  594. }
  595. return values;
  596. }
  597. function prune(map, key) {
  598. const values = map.get(key);
  599. if (values != null && values.size == 0) {
  600. map.delete(key);
  601. }
  602. }
  603. class Multimap {
  604. constructor() {
  605. this.valuesByKey = new Map();
  606. }
  607. get keys() {
  608. return Array.from(this.valuesByKey.keys());
  609. }
  610. get values() {
  611. const sets = Array.from(this.valuesByKey.values());
  612. return sets.reduce((values, set) => values.concat(Array.from(set)), []);
  613. }
  614. get size() {
  615. const sets = Array.from(this.valuesByKey.values());
  616. return sets.reduce((size, set) => size + set.size, 0);
  617. }
  618. add(key, value) {
  619. add(this.valuesByKey, key, value);
  620. }
  621. delete(key, value) {
  622. del(this.valuesByKey, key, value);
  623. }
  624. has(key, value) {
  625. const values = this.valuesByKey.get(key);
  626. return values != null && values.has(value);
  627. }
  628. hasKey(key) {
  629. return this.valuesByKey.has(key);
  630. }
  631. hasValue(value) {
  632. const sets = Array.from(this.valuesByKey.values());
  633. return sets.some(set => set.has(value));
  634. }
  635. getValuesForKey(key) {
  636. const values = this.valuesByKey.get(key);
  637. return values ? Array.from(values) : [];
  638. }
  639. getKeysForValue(value) {
  640. return Array.from(this.valuesByKey)
  641. .filter(([key, values]) => values.has(value))
  642. .map(([key, values]) => key);
  643. }
  644. }
  645. class IndexedMultimap extends Multimap {
  646. constructor() {
  647. super();
  648. this.keysByValue = new Map;
  649. }
  650. get values() {
  651. return Array.from(this.keysByValue.keys());
  652. }
  653. add(key, value) {
  654. super.add(key, value);
  655. add(this.keysByValue, value, key);
  656. }
  657. delete(key, value) {
  658. super.delete(key, value);
  659. del(this.keysByValue, value, key);
  660. }
  661. hasValue(value) {
  662. return this.keysByValue.has(value);
  663. }
  664. getKeysForValue(value) {
  665. const set = this.keysByValue.get(value);
  666. return set ? Array.from(set) : [];
  667. }
  668. }
  669. class TokenListObserver {
  670. constructor(element, attributeName, delegate) {
  671. this.attributeObserver = new AttributeObserver(element, attributeName, this);
  672. this.delegate = delegate;
  673. this.tokensByElement = new Multimap;
  674. }
  675. get started() {
  676. return this.attributeObserver.started;
  677. }
  678. start() {
  679. this.attributeObserver.start();
  680. }
  681. pause(callback) {
  682. this.attributeObserver.pause(callback);
  683. }
  684. stop() {
  685. this.attributeObserver.stop();
  686. }
  687. refresh() {
  688. this.attributeObserver.refresh();
  689. }
  690. get element() {
  691. return this.attributeObserver.element;
  692. }
  693. get attributeName() {
  694. return this.attributeObserver.attributeName;
  695. }
  696. elementMatchedAttribute(element) {
  697. this.tokensMatched(this.readTokensForElement(element));
  698. }
  699. elementAttributeValueChanged(element) {
  700. const [unmatchedTokens, matchedTokens] = this.refreshTokensForElement(element);
  701. this.tokensUnmatched(unmatchedTokens);
  702. this.tokensMatched(matchedTokens);
  703. }
  704. elementUnmatchedAttribute(element) {
  705. this.tokensUnmatched(this.tokensByElement.getValuesForKey(element));
  706. }
  707. tokensMatched(tokens) {
  708. tokens.forEach(token => this.tokenMatched(token));
  709. }
  710. tokensUnmatched(tokens) {
  711. tokens.forEach(token => this.tokenUnmatched(token));
  712. }
  713. tokenMatched(token) {
  714. this.delegate.tokenMatched(token);
  715. this.tokensByElement.add(token.element, token);
  716. }
  717. tokenUnmatched(token) {
  718. this.delegate.tokenUnmatched(token);
  719. this.tokensByElement.delete(token.element, token);
  720. }
  721. refreshTokensForElement(element) {
  722. const previousTokens = this.tokensByElement.getValuesForKey(element);
  723. const currentTokens = this.readTokensForElement(element);
  724. const firstDifferingIndex = zip(previousTokens, currentTokens)
  725. .findIndex(([previousToken, currentToken]) => !tokensAreEqual(previousToken, currentToken));
  726. if (firstDifferingIndex == -1) {
  727. return [[], []];
  728. }
  729. else {
  730. return [previousTokens.slice(firstDifferingIndex), currentTokens.slice(firstDifferingIndex)];
  731. }
  732. }
  733. readTokensForElement(element) {
  734. const attributeName = this.attributeName;
  735. const tokenString = element.getAttribute(attributeName) || "";
  736. return parseTokenString(tokenString, element, attributeName);
  737. }
  738. }
  739. function parseTokenString(tokenString, element, attributeName) {
  740. return tokenString.trim().split(/\s+/).filter(content => content.length)
  741. .map((content, index) => ({ element, attributeName, content, index }));
  742. }
  743. function zip(left, right) {
  744. const length = Math.max(left.length, right.length);
  745. return Array.from({ length }, (_, index) => [left[index], right[index]]);
  746. }
  747. function tokensAreEqual(left, right) {
  748. return left && right && left.index == right.index && left.content == right.content;
  749. }
  750. class ValueListObserver {
  751. constructor(element, attributeName, delegate) {
  752. this.tokenListObserver = new TokenListObserver(element, attributeName, this);
  753. this.delegate = delegate;
  754. this.parseResultsByToken = new WeakMap;
  755. this.valuesByTokenByElement = new WeakMap;
  756. }
  757. get started() {
  758. return this.tokenListObserver.started;
  759. }
  760. start() {
  761. this.tokenListObserver.start();
  762. }
  763. stop() {
  764. this.tokenListObserver.stop();
  765. }
  766. refresh() {
  767. this.tokenListObserver.refresh();
  768. }
  769. get element() {
  770. return this.tokenListObserver.element;
  771. }
  772. get attributeName() {
  773. return this.tokenListObserver.attributeName;
  774. }
  775. tokenMatched(token) {
  776. const { element } = token;
  777. const { value } = this.fetchParseResultForToken(token);
  778. if (value) {
  779. this.fetchValuesByTokenForElement(element).set(token, value);
  780. this.delegate.elementMatchedValue(element, value);
  781. }
  782. }
  783. tokenUnmatched(token) {
  784. const { element } = token;
  785. const { value } = this.fetchParseResultForToken(token);
  786. if (value) {
  787. this.fetchValuesByTokenForElement(element).delete(token);
  788. this.delegate.elementUnmatchedValue(element, value);
  789. }
  790. }
  791. fetchParseResultForToken(token) {
  792. let parseResult = this.parseResultsByToken.get(token);
  793. if (!parseResult) {
  794. parseResult = this.parseToken(token);
  795. this.parseResultsByToken.set(token, parseResult);
  796. }
  797. return parseResult;
  798. }
  799. fetchValuesByTokenForElement(element) {
  800. let valuesByToken = this.valuesByTokenByElement.get(element);
  801. if (!valuesByToken) {
  802. valuesByToken = new Map;
  803. this.valuesByTokenByElement.set(element, valuesByToken);
  804. }
  805. return valuesByToken;
  806. }
  807. parseToken(token) {
  808. try {
  809. const value = this.delegate.parseValueForToken(token);
  810. return { value };
  811. }
  812. catch (error) {
  813. return { error };
  814. }
  815. }
  816. }
  817. class BindingObserver {
  818. constructor(context, delegate) {
  819. this.context = context;
  820. this.delegate = delegate;
  821. this.bindingsByAction = new Map;
  822. }
  823. start() {
  824. if (!this.valueListObserver) {
  825. this.valueListObserver = new ValueListObserver(this.element, this.actionAttribute, this);
  826. this.valueListObserver.start();
  827. }
  828. }
  829. stop() {
  830. if (this.valueListObserver) {
  831. this.valueListObserver.stop();
  832. delete this.valueListObserver;
  833. this.disconnectAllActions();
  834. }
  835. }
  836. get element() {
  837. return this.context.element;
  838. }
  839. get identifier() {
  840. return this.context.identifier;
  841. }
  842. get actionAttribute() {
  843. return this.schema.actionAttribute;
  844. }
  845. get schema() {
  846. return this.context.schema;
  847. }
  848. get bindings() {
  849. return Array.from(this.bindingsByAction.values());
  850. }
  851. connectAction(action) {
  852. const binding = new Binding(this.context, action);
  853. this.bindingsByAction.set(action, binding);
  854. this.delegate.bindingConnected(binding);
  855. }
  856. disconnectAction(action) {
  857. const binding = this.bindingsByAction.get(action);
  858. if (binding) {
  859. this.bindingsByAction.delete(action);
  860. this.delegate.bindingDisconnected(binding);
  861. }
  862. }
  863. disconnectAllActions() {
  864. this.bindings.forEach(binding => this.delegate.bindingDisconnected(binding));
  865. this.bindingsByAction.clear();
  866. }
  867. parseValueForToken(token) {
  868. const action = Action.forToken(token);
  869. if (action.identifier == this.identifier) {
  870. return action;
  871. }
  872. }
  873. elementMatchedValue(element, action) {
  874. this.connectAction(action);
  875. }
  876. elementUnmatchedValue(element, action) {
  877. this.disconnectAction(action);
  878. }
  879. }
  880. class ValueObserver {
  881. constructor(context, receiver) {
  882. this.context = context;
  883. this.receiver = receiver;
  884. this.stringMapObserver = new StringMapObserver(this.element, this);
  885. this.valueDescriptorMap = this.controller.valueDescriptorMap;
  886. this.invokeChangedCallbacksForDefaultValues();
  887. }
  888. start() {
  889. this.stringMapObserver.start();
  890. }
  891. stop() {
  892. this.stringMapObserver.stop();
  893. }
  894. get element() {
  895. return this.context.element;
  896. }
  897. get controller() {
  898. return this.context.controller;
  899. }
  900. getStringMapKeyForAttribute(attributeName) {
  901. if (attributeName in this.valueDescriptorMap) {
  902. return this.valueDescriptorMap[attributeName].name;
  903. }
  904. }
  905. stringMapKeyAdded(key, attributeName) {
  906. const descriptor = this.valueDescriptorMap[attributeName];
  907. if (!this.hasValue(key)) {
  908. this.invokeChangedCallback(key, descriptor.writer(this.receiver[key]), descriptor.writer(descriptor.defaultValue));
  909. }
  910. }
  911. stringMapValueChanged(value, name, oldValue) {
  912. const descriptor = this.valueDescriptorNameMap[name];
  913. if (value === null)
  914. return;
  915. if (oldValue === null) {
  916. oldValue = descriptor.writer(descriptor.defaultValue);
  917. }
  918. this.invokeChangedCallback(name, value, oldValue);
  919. }
  920. stringMapKeyRemoved(key, attributeName, oldValue) {
  921. const descriptor = this.valueDescriptorNameMap[key];
  922. if (this.hasValue(key)) {
  923. this.invokeChangedCallback(key, descriptor.writer(this.receiver[key]), oldValue);
  924. }
  925. else {
  926. this.invokeChangedCallback(key, descriptor.writer(descriptor.defaultValue), oldValue);
  927. }
  928. }
  929. invokeChangedCallbacksForDefaultValues() {
  930. for (const { key, name, defaultValue, writer } of this.valueDescriptors) {
  931. if (defaultValue != undefined && !this.controller.data.has(key)) {
  932. this.invokeChangedCallback(name, writer(defaultValue), undefined);
  933. }
  934. }
  935. }
  936. invokeChangedCallback(name, rawValue, rawOldValue) {
  937. const changedMethodName = `${name}Changed`;
  938. const changedMethod = this.receiver[changedMethodName];
  939. if (typeof changedMethod == "function") {
  940. const descriptor = this.valueDescriptorNameMap[name];
  941. const value = descriptor.reader(rawValue);
  942. let oldValue = rawOldValue;
  943. if (rawOldValue) {
  944. oldValue = descriptor.reader(rawOldValue);
  945. }
  946. changedMethod.call(this.receiver, value, oldValue);
  947. }
  948. }
  949. get valueDescriptors() {
  950. const { valueDescriptorMap } = this;
  951. return Object.keys(valueDescriptorMap).map(key => valueDescriptorMap[key]);
  952. }
  953. get valueDescriptorNameMap() {
  954. const descriptors = {};
  955. Object.keys(this.valueDescriptorMap).forEach(key => {
  956. const descriptor = this.valueDescriptorMap[key];
  957. descriptors[descriptor.name] = descriptor;
  958. });
  959. return descriptors;
  960. }
  961. hasValue(attributeName) {
  962. const descriptor = this.valueDescriptorNameMap[attributeName];
  963. const hasMethodName = `has${capitalize(descriptor.name)}`;
  964. return this.receiver[hasMethodName];
  965. }
  966. }
  967. class TargetObserver {
  968. constructor(context, delegate) {
  969. this.context = context;
  970. this.delegate = delegate;
  971. this.targetsByName = new Multimap;
  972. }
  973. start() {
  974. if (!this.tokenListObserver) {
  975. this.tokenListObserver = new TokenListObserver(this.element, this.attributeName, this);
  976. this.tokenListObserver.start();
  977. }
  978. }
  979. stop() {
  980. if (this.tokenListObserver) {
  981. this.disconnectAllTargets();
  982. this.tokenListObserver.stop();
  983. delete this.tokenListObserver;
  984. }
  985. }
  986. tokenMatched({ element, content: name }) {
  987. if (this.scope.containsElement(element)) {
  988. this.connectTarget(element, name);
  989. }
  990. }
  991. tokenUnmatched({ element, content: name }) {
  992. this.disconnectTarget(element, name);
  993. }
  994. connectTarget(element, name) {
  995. var _a;
  996. if (!this.targetsByName.has(name, element)) {
  997. this.targetsByName.add(name, element);
  998. (_a = this.tokenListObserver) === null || _a === void 0 ? void 0 : _a.pause(() => this.delegate.targetConnected(element, name));
  999. }
  1000. }
  1001. disconnectTarget(element, name) {
  1002. var _a;
  1003. if (this.targetsByName.has(name, element)) {
  1004. this.targetsByName.delete(name, element);
  1005. (_a = this.tokenListObserver) === null || _a === void 0 ? void 0 : _a.pause(() => this.delegate.targetDisconnected(element, name));
  1006. }
  1007. }
  1008. disconnectAllTargets() {
  1009. for (const name of this.targetsByName.keys) {
  1010. for (const element of this.targetsByName.getValuesForKey(name)) {
  1011. this.disconnectTarget(element, name);
  1012. }
  1013. }
  1014. }
  1015. get attributeName() {
  1016. return `data-${this.context.identifier}-target`;
  1017. }
  1018. get element() {
  1019. return this.context.element;
  1020. }
  1021. get scope() {
  1022. return this.context.scope;
  1023. }
  1024. }
  1025. class Context {
  1026. constructor(module, scope) {
  1027. this.logDebugActivity = (functionName, detail = {}) => {
  1028. const { identifier, controller, element } = this;
  1029. detail = Object.assign({ identifier, controller, element }, detail);
  1030. this.application.logDebugActivity(this.identifier, functionName, detail);
  1031. };
  1032. this.module = module;
  1033. this.scope = scope;
  1034. this.controller = new module.controllerConstructor(this);
  1035. this.bindingObserver = new BindingObserver(this, this.dispatcher);
  1036. this.valueObserver = new ValueObserver(this, this.controller);
  1037. this.targetObserver = new TargetObserver(this, this);
  1038. try {
  1039. this.controller.initialize();
  1040. this.logDebugActivity("initialize");
  1041. }
  1042. catch (error) {
  1043. this.handleError(error, "initializing controller");
  1044. }
  1045. }
  1046. connect() {
  1047. this.bindingObserver.start();
  1048. this.valueObserver.start();
  1049. this.targetObserver.start();
  1050. try {
  1051. this.controller.connect();
  1052. this.logDebugActivity("connect");
  1053. }
  1054. catch (error) {
  1055. this.handleError(error, "connecting controller");
  1056. }
  1057. }
  1058. disconnect() {
  1059. try {
  1060. this.controller.disconnect();
  1061. this.logDebugActivity("disconnect");
  1062. }
  1063. catch (error) {
  1064. this.handleError(error, "disconnecting controller");
  1065. }
  1066. this.targetObserver.stop();
  1067. this.valueObserver.stop();
  1068. this.bindingObserver.stop();
  1069. }
  1070. get application() {
  1071. return this.module.application;
  1072. }
  1073. get identifier() {
  1074. return this.module.identifier;
  1075. }
  1076. get schema() {
  1077. return this.application.schema;
  1078. }
  1079. get dispatcher() {
  1080. return this.application.dispatcher;
  1081. }
  1082. get element() {
  1083. return this.scope.element;
  1084. }
  1085. get parentElement() {
  1086. return this.element.parentElement;
  1087. }
  1088. handleError(error, message, detail = {}) {
  1089. const { identifier, controller, element } = this;
  1090. detail = Object.assign({ identifier, controller, element }, detail);
  1091. this.application.handleError(error, `Error ${message}`, detail);
  1092. }
  1093. targetConnected(element, name) {
  1094. this.invokeControllerMethod(`${name}TargetConnected`, element);
  1095. }
  1096. targetDisconnected(element, name) {
  1097. this.invokeControllerMethod(`${name}TargetDisconnected`, element);
  1098. }
  1099. invokeControllerMethod(methodName, ...args) {
  1100. const controller = this.controller;
  1101. if (typeof controller[methodName] == "function") {
  1102. controller[methodName](...args);
  1103. }
  1104. }
  1105. }
  1106. function readInheritableStaticArrayValues(constructor, propertyName) {
  1107. const ancestors = getAncestorsForConstructor(constructor);
  1108. return Array.from(ancestors.reduce((values, constructor) => {
  1109. getOwnStaticArrayValues(constructor, propertyName).forEach(name => values.add(name));
  1110. return values;
  1111. }, new Set));
  1112. }
  1113. function readInheritableStaticObjectPairs(constructor, propertyName) {
  1114. const ancestors = getAncestorsForConstructor(constructor);
  1115. return ancestors.reduce((pairs, constructor) => {
  1116. pairs.push(...getOwnStaticObjectPairs(constructor, propertyName));
  1117. return pairs;
  1118. }, []);
  1119. }
  1120. function getAncestorsForConstructor(constructor) {
  1121. const ancestors = [];
  1122. while (constructor) {
  1123. ancestors.push(constructor);
  1124. constructor = Object.getPrototypeOf(constructor);
  1125. }
  1126. return ancestors.reverse();
  1127. }
  1128. function getOwnStaticArrayValues(constructor, propertyName) {
  1129. const definition = constructor[propertyName];
  1130. return Array.isArray(definition) ? definition : [];
  1131. }
  1132. function getOwnStaticObjectPairs(constructor, propertyName) {
  1133. const definition = constructor[propertyName];
  1134. return definition ? Object.keys(definition).map(key => [key, definition[key]]) : [];
  1135. }
  1136. function bless(constructor) {
  1137. return shadow(constructor, getBlessedProperties(constructor));
  1138. }
  1139. function shadow(constructor, properties) {
  1140. const shadowConstructor = extend(constructor);
  1141. const shadowProperties = getShadowProperties(constructor.prototype, properties);
  1142. Object.defineProperties(shadowConstructor.prototype, shadowProperties);
  1143. return shadowConstructor;
  1144. }
  1145. function getBlessedProperties(constructor) {
  1146. const blessings = readInheritableStaticArrayValues(constructor, "blessings");
  1147. return blessings.reduce((blessedProperties, blessing) => {
  1148. const properties = blessing(constructor);
  1149. for (const key in properties) {
  1150. const descriptor = blessedProperties[key] || {};
  1151. blessedProperties[key] = Object.assign(descriptor, properties[key]);
  1152. }
  1153. return blessedProperties;
  1154. }, {});
  1155. }
  1156. function getShadowProperties(prototype, properties) {
  1157. return getOwnKeys(properties).reduce((shadowProperties, key) => {
  1158. const descriptor = getShadowedDescriptor(prototype, properties, key);
  1159. if (descriptor) {
  1160. Object.assign(shadowProperties, { [key]: descriptor });
  1161. }
  1162. return shadowProperties;
  1163. }, {});
  1164. }
  1165. function getShadowedDescriptor(prototype, properties, key) {
  1166. const shadowingDescriptor = Object.getOwnPropertyDescriptor(prototype, key);
  1167. const shadowedByValue = shadowingDescriptor && "value" in shadowingDescriptor;
  1168. if (!shadowedByValue) {
  1169. const descriptor = Object.getOwnPropertyDescriptor(properties, key).value;
  1170. if (shadowingDescriptor) {
  1171. descriptor.get = shadowingDescriptor.get || descriptor.get;
  1172. descriptor.set = shadowingDescriptor.set || descriptor.set;
  1173. }
  1174. return descriptor;
  1175. }
  1176. }
  1177. const getOwnKeys = (() => {
  1178. if (typeof Object.getOwnPropertySymbols == "function") {
  1179. return (object) => [
  1180. ...Object.getOwnPropertyNames(object),
  1181. ...Object.getOwnPropertySymbols(object)
  1182. ];
  1183. }
  1184. else {
  1185. return Object.getOwnPropertyNames;
  1186. }
  1187. })();
  1188. const extend = (() => {
  1189. function extendWithReflect(constructor) {
  1190. function extended() {
  1191. return Reflect.construct(constructor, arguments, new.target);
  1192. }
  1193. extended.prototype = Object.create(constructor.prototype, {
  1194. constructor: { value: extended }
  1195. });
  1196. Reflect.setPrototypeOf(extended, constructor);
  1197. return extended;
  1198. }
  1199. function testReflectExtension() {
  1200. const a = function () { this.a.call(this); };
  1201. const b = extendWithReflect(a);
  1202. b.prototype.a = function () { };
  1203. return new b;
  1204. }
  1205. try {
  1206. testReflectExtension();
  1207. return extendWithReflect;
  1208. }
  1209. catch (error) {
  1210. return (constructor) => class extended extends constructor {
  1211. };
  1212. }
  1213. })();
  1214. function blessDefinition(definition) {
  1215. return {
  1216. identifier: definition.identifier,
  1217. controllerConstructor: bless(definition.controllerConstructor)
  1218. };
  1219. }
  1220. class Module {
  1221. constructor(application, definition) {
  1222. this.application = application;
  1223. this.definition = blessDefinition(definition);
  1224. this.contextsByScope = new WeakMap;
  1225. this.connectedContexts = new Set;
  1226. }
  1227. get identifier() {
  1228. return this.definition.identifier;
  1229. }
  1230. get controllerConstructor() {
  1231. return this.definition.controllerConstructor;
  1232. }
  1233. get contexts() {
  1234. return Array.from(this.connectedContexts);
  1235. }
  1236. connectContextForScope(scope) {
  1237. const context = this.fetchContextForScope(scope);
  1238. this.connectedContexts.add(context);
  1239. context.connect();
  1240. }
  1241. disconnectContextForScope(scope) {
  1242. const context = this.contextsByScope.get(scope);
  1243. if (context) {
  1244. this.connectedContexts.delete(context);
  1245. context.disconnect();
  1246. }
  1247. }
  1248. fetchContextForScope(scope) {
  1249. let context = this.contextsByScope.get(scope);
  1250. if (!context) {
  1251. context = new Context(this, scope);
  1252. this.contextsByScope.set(scope, context);
  1253. }
  1254. return context;
  1255. }
  1256. }
  1257. class ClassMap {
  1258. constructor(scope) {
  1259. this.scope = scope;
  1260. }
  1261. has(name) {
  1262. return this.data.has(this.getDataKey(name));
  1263. }
  1264. get(name) {
  1265. return this.getAll(name)[0];
  1266. }
  1267. getAll(name) {
  1268. const tokenString = this.data.get(this.getDataKey(name)) || "";
  1269. return tokenize(tokenString);
  1270. }
  1271. getAttributeName(name) {
  1272. return this.data.getAttributeNameForKey(this.getDataKey(name));
  1273. }
  1274. getDataKey(name) {
  1275. return `${name}-class`;
  1276. }
  1277. get data() {
  1278. return this.scope.data;
  1279. }
  1280. }
  1281. class DataMap {
  1282. constructor(scope) {
  1283. this.scope = scope;
  1284. }
  1285. get element() {
  1286. return this.scope.element;
  1287. }
  1288. get identifier() {
  1289. return this.scope.identifier;
  1290. }
  1291. get(key) {
  1292. const name = this.getAttributeNameForKey(key);
  1293. return this.element.getAttribute(name);
  1294. }
  1295. set(key, value) {
  1296. const name = this.getAttributeNameForKey(key);
  1297. this.element.setAttribute(name, value);
  1298. return this.get(key);
  1299. }
  1300. has(key) {
  1301. const name = this.getAttributeNameForKey(key);
  1302. return this.element.hasAttribute(name);
  1303. }
  1304. delete(key) {
  1305. if (this.has(key)) {
  1306. const name = this.getAttributeNameForKey(key);
  1307. this.element.removeAttribute(name);
  1308. return true;
  1309. }
  1310. else {
  1311. return false;
  1312. }
  1313. }
  1314. getAttributeNameForKey(key) {
  1315. return `data-${this.identifier}-${dasherize(key)}`;
  1316. }
  1317. }
  1318. class Guide {
  1319. constructor(logger) {
  1320. this.warnedKeysByObject = new WeakMap;
  1321. this.logger = logger;
  1322. }
  1323. warn(object, key, message) {
  1324. let warnedKeys = this.warnedKeysByObject.get(object);
  1325. if (!warnedKeys) {
  1326. warnedKeys = new Set;
  1327. this.warnedKeysByObject.set(object, warnedKeys);
  1328. }
  1329. if (!warnedKeys.has(key)) {
  1330. warnedKeys.add(key);
  1331. this.logger.warn(message, object);
  1332. }
  1333. }
  1334. }
  1335. function attributeValueContainsToken(attributeName, token) {
  1336. return `[${attributeName}~="${token}"]`;
  1337. }
  1338. class TargetSet {
  1339. constructor(scope) {
  1340. this.scope = scope;
  1341. }
  1342. get element() {
  1343. return this.scope.element;
  1344. }
  1345. get identifier() {
  1346. return this.scope.identifier;
  1347. }
  1348. get schema() {
  1349. return this.scope.schema;
  1350. }
  1351. has(targetName) {
  1352. return this.find(targetName) != null;
  1353. }
  1354. find(...targetNames) {
  1355. return targetNames.reduce((target, targetName) => target
  1356. || this.findTarget(targetName)
  1357. || this.findLegacyTarget(targetName), undefined);
  1358. }
  1359. findAll(...targetNames) {
  1360. return targetNames.reduce((targets, targetName) => [
  1361. ...targets,
  1362. ...this.findAllTargets(targetName),
  1363. ...this.findAllLegacyTargets(targetName)
  1364. ], []);
  1365. }
  1366. findTarget(targetName) {
  1367. const selector = this.getSelectorForTargetName(targetName);
  1368. return this.scope.findElement(selector);
  1369. }
  1370. findAllTargets(targetName) {
  1371. const selector = this.getSelectorForTargetName(targetName);
  1372. return this.scope.findAllElements(selector);
  1373. }
  1374. getSelectorForTargetName(targetName) {
  1375. const attributeName = this.schema.targetAttributeForScope(this.identifier);
  1376. return attributeValueContainsToken(attributeName, targetName);
  1377. }
  1378. findLegacyTarget(targetName) {
  1379. const selector = this.getLegacySelectorForTargetName(targetName);
  1380. return this.deprecate(this.scope.findElement(selector), targetName);
  1381. }
  1382. findAllLegacyTargets(targetName) {
  1383. const selector = this.getLegacySelectorForTargetName(targetName);
  1384. return this.scope.findAllElements(selector).map(element => this.deprecate(element, targetName));
  1385. }
  1386. getLegacySelectorForTargetName(targetName) {
  1387. const targetDescriptor = `${this.identifier}.${targetName}`;
  1388. return attributeValueContainsToken(this.schema.targetAttribute, targetDescriptor);
  1389. }
  1390. deprecate(element, targetName) {
  1391. if (element) {
  1392. const { identifier } = this;
  1393. const attributeName = this.schema.targetAttribute;
  1394. const revisedAttributeName = this.schema.targetAttributeForScope(identifier);
  1395. this.guide.warn(element, `target:${targetName}`, `Please replace ${attributeName}="${identifier}.${targetName}" with ${revisedAttributeName}="${targetName}". ` +
  1396. `The ${attributeName} attribute is deprecated and will be removed in a future version of Stimulus.`);
  1397. }
  1398. return element;
  1399. }
  1400. get guide() {
  1401. return this.scope.guide;
  1402. }
  1403. }
  1404. class Scope {
  1405. constructor(schema, element, identifier, logger) {
  1406. this.targets = new TargetSet(this);
  1407. this.classes = new ClassMap(this);
  1408. this.data = new DataMap(this);
  1409. this.containsElement = (element) => {
  1410. return element.closest(this.controllerSelector) === this.element;
  1411. };
  1412. this.schema = schema;
  1413. this.element = element;
  1414. this.identifier = identifier;
  1415. this.guide = new Guide(logger);
  1416. }
  1417. findElement(selector) {
  1418. return this.element.matches(selector)
  1419. ? this.element
  1420. : this.queryElements(selector).find(this.containsElement);
  1421. }
  1422. findAllElements(selector) {
  1423. return [
  1424. ...this.element.matches(selector) ? [this.element] : [],
  1425. ...this.queryElements(selector).filter(this.containsElement)
  1426. ];
  1427. }
  1428. queryElements(selector) {
  1429. return Array.from(this.element.querySelectorAll(selector));
  1430. }
  1431. get controllerSelector() {
  1432. return attributeValueContainsToken(this.schema.controllerAttribute, this.identifier);
  1433. }
  1434. }
  1435. class ScopeObserver {
  1436. constructor(element, schema, delegate) {
  1437. this.element = element;
  1438. this.schema = schema;
  1439. this.delegate = delegate;
  1440. this.valueListObserver = new ValueListObserver(this.element, this.controllerAttribute, this);
  1441. this.scopesByIdentifierByElement = new WeakMap;
  1442. this.scopeReferenceCounts = new WeakMap;
  1443. }
  1444. start() {
  1445. this.valueListObserver.start();
  1446. }
  1447. stop() {
  1448. this.valueListObserver.stop();
  1449. }
  1450. get controllerAttribute() {
  1451. return this.schema.controllerAttribute;
  1452. }
  1453. parseValueForToken(token) {
  1454. const { element, content: identifier } = token;
  1455. const scopesByIdentifier = this.fetchScopesByIdentifierForElement(element);
  1456. let scope = scopesByIdentifier.get(identifier);
  1457. if (!scope) {
  1458. scope = this.delegate.createScopeForElementAndIdentifier(element, identifier);
  1459. scopesByIdentifier.set(identifier, scope);
  1460. }
  1461. return scope;
  1462. }
  1463. elementMatchedValue(element, value) {
  1464. const referenceCount = (this.scopeReferenceCounts.get(value) || 0) + 1;
  1465. this.scopeReferenceCounts.set(value, referenceCount);
  1466. if (referenceCount == 1) {
  1467. this.delegate.scopeConnected(value);
  1468. }
  1469. }
  1470. elementUnmatchedValue(element, value) {
  1471. const referenceCount = this.scopeReferenceCounts.get(value);
  1472. if (referenceCount) {
  1473. this.scopeReferenceCounts.set(value, referenceCount - 1);
  1474. if (referenceCount == 1) {
  1475. this.delegate.scopeDisconnected(value);
  1476. }
  1477. }
  1478. }
  1479. fetchScopesByIdentifierForElement(element) {
  1480. let scopesByIdentifier = this.scopesByIdentifierByElement.get(element);
  1481. if (!scopesByIdentifier) {
  1482. scopesByIdentifier = new Map;
  1483. this.scopesByIdentifierByElement.set(element, scopesByIdentifier);
  1484. }
  1485. return scopesByIdentifier;
  1486. }
  1487. }
  1488. class Router {
  1489. constructor(application) {
  1490. this.application = application;
  1491. this.scopeObserver = new ScopeObserver(this.element, this.schema, this);
  1492. this.scopesByIdentifier = new Multimap;
  1493. this.modulesByIdentifier = new Map;
  1494. }
  1495. get element() {
  1496. return this.application.element;
  1497. }
  1498. get schema() {
  1499. return this.application.schema;
  1500. }
  1501. get logger() {
  1502. return this.application.logger;
  1503. }
  1504. get controllerAttribute() {
  1505. return this.schema.controllerAttribute;
  1506. }
  1507. get modules() {
  1508. return Array.from(this.modulesByIdentifier.values());
  1509. }
  1510. get contexts() {
  1511. return this.modules.reduce((contexts, module) => contexts.concat(module.contexts), []);
  1512. }
  1513. start() {
  1514. this.scopeObserver.start();
  1515. }
  1516. stop() {
  1517. this.scopeObserver.stop();
  1518. }
  1519. loadDefinition(definition) {
  1520. this.unloadIdentifier(definition.identifier);
  1521. const module = new Module(this.application, definition);
  1522. this.connectModule(module);
  1523. }
  1524. unloadIdentifier(identifier) {
  1525. const module = this.modulesByIdentifier.get(identifier);
  1526. if (module) {
  1527. this.disconnectModule(module);
  1528. }
  1529. }
  1530. getContextForElementAndIdentifier(element, identifier) {
  1531. const module = this.modulesByIdentifier.get(identifier);
  1532. if (module) {
  1533. return module.contexts.find(context => context.element == element);
  1534. }
  1535. }
  1536. handleError(error, message, detail) {
  1537. this.application.handleError(error, message, detail);
  1538. }
  1539. createScopeForElementAndIdentifier(element, identifier) {
  1540. return new Scope(this.schema, element, identifier, this.logger);
  1541. }
  1542. scopeConnected(scope) {
  1543. this.scopesByIdentifier.add(scope.identifier, scope);
  1544. const module = this.modulesByIdentifier.get(scope.identifier);
  1545. if (module) {
  1546. module.connectContextForScope(scope);
  1547. }
  1548. }
  1549. scopeDisconnected(scope) {
  1550. this.scopesByIdentifier.delete(scope.identifier, scope);
  1551. const module = this.modulesByIdentifier.get(scope.identifier);
  1552. if (module) {
  1553. module.disconnectContextForScope(scope);
  1554. }
  1555. }
  1556. connectModule(module) {
  1557. this.modulesByIdentifier.set(module.identifier, module);
  1558. const scopes = this.scopesByIdentifier.getValuesForKey(module.identifier);
  1559. scopes.forEach(scope => module.connectContextForScope(scope));
  1560. }
  1561. disconnectModule(module) {
  1562. this.modulesByIdentifier.delete(module.identifier);
  1563. const scopes = this.scopesByIdentifier.getValuesForKey(module.identifier);
  1564. scopes.forEach(scope => module.disconnectContextForScope(scope));
  1565. }
  1566. }
  1567. const defaultSchema = {
  1568. controllerAttribute: "data-controller",
  1569. actionAttribute: "data-action",
  1570. targetAttribute: "data-target",
  1571. targetAttributeForScope: identifier => `data-${identifier}-target`
  1572. };
  1573. class Application {
  1574. constructor(element = document.documentElement, schema = defaultSchema) {
  1575. this.logger = console;
  1576. this.debug = false;
  1577. this.logDebugActivity = (identifier, functionName, detail = {}) => {
  1578. if (this.debug) {
  1579. this.logFormattedMessage(identifier, functionName, detail);
  1580. }
  1581. };
  1582. this.element = element;
  1583. this.schema = schema;
  1584. this.dispatcher = new Dispatcher(this);
  1585. this.router = new Router(this);
  1586. }
  1587. static start(element, schema) {
  1588. const application = new Application(element, schema);
  1589. application.start();
  1590. return application;
  1591. }
  1592. async start() {
  1593. await domReady();
  1594. this.logDebugActivity("application", "starting");
  1595. this.dispatcher.start();
  1596. this.router.start();
  1597. this.logDebugActivity("application", "start");
  1598. }
  1599. stop() {
  1600. this.logDebugActivity("application", "stopping");
  1601. this.dispatcher.stop();
  1602. this.router.stop();
  1603. this.logDebugActivity("application", "stop");
  1604. }
  1605. register(identifier, controllerConstructor) {
  1606. if (controllerConstructor.shouldLoad) {
  1607. this.load({ identifier, controllerConstructor });
  1608. }
  1609. }
  1610. load(head, ...rest) {
  1611. const definitions = Array.isArray(head) ? head : [head, ...rest];
  1612. definitions.forEach(definition => this.router.loadDefinition(definition));
  1613. }
  1614. unload(head, ...rest) {
  1615. const identifiers = Array.isArray(head) ? head : [head, ...rest];
  1616. identifiers.forEach(identifier => this.router.unloadIdentifier(identifier));
  1617. }
  1618. get controllers() {
  1619. return this.router.contexts.map(context => context.controller);
  1620. }
  1621. getControllerForElementAndIdentifier(element, identifier) {
  1622. const context = this.router.getContextForElementAndIdentifier(element, identifier);
  1623. return context ? context.controller : null;
  1624. }
  1625. handleError(error, message, detail) {
  1626. var _a;
  1627. this.logger.error(`%s\n\n%o\n\n%o`, message, error, detail);
  1628. (_a = window.onerror) === null || _a === void 0 ? void 0 : _a.call(window, message, "", 0, 0, error);
  1629. }
  1630. logFormattedMessage(identifier, functionName, detail = {}) {
  1631. detail = Object.assign({ application: this }, detail);
  1632. this.logger.groupCollapsed(`${identifier} #${functionName}`);
  1633. this.logger.log("details:", Object.assign({}, detail));
  1634. this.logger.groupEnd();
  1635. }
  1636. }
  1637. function domReady() {
  1638. return new Promise(resolve => {
  1639. if (document.readyState == "loading") {
  1640. document.addEventListener("DOMContentLoaded", () => resolve());
  1641. }
  1642. else {
  1643. resolve();
  1644. }
  1645. });
  1646. }
  1647. function ClassPropertiesBlessing(constructor) {
  1648. const classes = readInheritableStaticArrayValues(constructor, "classes");
  1649. return classes.reduce((properties, classDefinition) => {
  1650. return Object.assign(properties, propertiesForClassDefinition(classDefinition));
  1651. }, {});
  1652. }
  1653. function propertiesForClassDefinition(key) {
  1654. return {
  1655. [`${key}Class`]: {
  1656. get() {
  1657. const { classes } = this;
  1658. if (classes.has(key)) {
  1659. return classes.get(key);
  1660. }
  1661. else {
  1662. const attribute = classes.getAttributeName(key);
  1663. throw new Error(`Missing attribute "${attribute}"`);
  1664. }
  1665. }
  1666. },
  1667. [`${key}Classes`]: {
  1668. get() {
  1669. return this.classes.getAll(key);
  1670. }
  1671. },
  1672. [`has${capitalize(key)}Class`]: {
  1673. get() {
  1674. return this.classes.has(key);
  1675. }
  1676. }
  1677. };
  1678. }
  1679. function TargetPropertiesBlessing(constructor) {
  1680. const targets = readInheritableStaticArrayValues(constructor, "targets");
  1681. return targets.reduce((properties, targetDefinition) => {
  1682. return Object.assign(properties, propertiesForTargetDefinition(targetDefinition));
  1683. }, {});
  1684. }
  1685. function propertiesForTargetDefinition(name) {
  1686. return {
  1687. [`${name}Target`]: {
  1688. get() {
  1689. const target = this.targets.find(name);
  1690. if (target) {
  1691. return target;
  1692. }
  1693. else {
  1694. throw new Error(`Missing target element "${name}" for "${this.identifier}" controller`);
  1695. }
  1696. }
  1697. },
  1698. [`${name}Targets`]: {
  1699. get() {
  1700. return this.targets.findAll(name);
  1701. }
  1702. },
  1703. [`has${capitalize(name)}Target`]: {
  1704. get() {
  1705. return this.targets.has(name);
  1706. }
  1707. }
  1708. };
  1709. }
  1710. function ValuePropertiesBlessing(constructor) {
  1711. const valueDefinitionPairs = readInheritableStaticObjectPairs(constructor, "values");
  1712. const propertyDescriptorMap = {
  1713. valueDescriptorMap: {
  1714. get() {
  1715. return valueDefinitionPairs.reduce((result, valueDefinitionPair) => {
  1716. const valueDescriptor = parseValueDefinitionPair(valueDefinitionPair);
  1717. const attributeName = this.data.getAttributeNameForKey(valueDescriptor.key);
  1718. return Object.assign(result, { [attributeName]: valueDescriptor });
  1719. }, {});
  1720. }
  1721. }
  1722. };
  1723. return valueDefinitionPairs.reduce((properties, valueDefinitionPair) => {
  1724. return Object.assign(properties, propertiesForValueDefinitionPair(valueDefinitionPair));
  1725. }, propertyDescriptorMap);
  1726. }
  1727. function propertiesForValueDefinitionPair(valueDefinitionPair) {
  1728. const definition = parseValueDefinitionPair(valueDefinitionPair);
  1729. const { key, name, reader: read, writer: write } = definition;
  1730. return {
  1731. [name]: {
  1732. get() {
  1733. const value = this.data.get(key);
  1734. if (value !== null) {
  1735. return read(value);
  1736. }
  1737. else {
  1738. return definition.defaultValue;
  1739. }
  1740. },
  1741. set(value) {
  1742. if (value === undefined) {
  1743. this.data.delete(key);
  1744. }
  1745. else {
  1746. this.data.set(key, write(value));
  1747. }
  1748. }
  1749. },
  1750. [`has${capitalize(name)}`]: {
  1751. get() {
  1752. return this.data.has(key) || definition.hasCustomDefaultValue;
  1753. }
  1754. }
  1755. };
  1756. }
  1757. function parseValueDefinitionPair([token, typeDefinition]) {
  1758. return valueDescriptorForTokenAndTypeDefinition(token, typeDefinition);
  1759. }
  1760. function parseValueTypeConstant(constant) {
  1761. switch (constant) {
  1762. case Array: return "array";
  1763. case Boolean: return "boolean";
  1764. case Number: return "number";
  1765. case Object: return "object";
  1766. case String: return "string";
  1767. }
  1768. }
  1769. function parseValueTypeDefault(defaultValue) {
  1770. switch (typeof defaultValue) {
  1771. case "boolean": return "boolean";
  1772. case "number": return "number";
  1773. case "string": return "string";
  1774. }
  1775. if (Array.isArray(defaultValue))
  1776. return "array";
  1777. if (Object.prototype.toString.call(defaultValue) === "[object Object]")
  1778. return "object";
  1779. }
  1780. function parseValueTypeObject(typeObject) {
  1781. const typeFromObject = parseValueTypeConstant(typeObject.type);
  1782. if (typeFromObject) {
  1783. const defaultValueType = parseValueTypeDefault(typeObject.default);
  1784. if (typeFromObject !== defaultValueType) {
  1785. throw new Error(`Type "${typeFromObject}" must match the type of the default value. Given default value: "${typeObject.default}" as "${defaultValueType}"`);
  1786. }
  1787. return typeFromObject;
  1788. }
  1789. }
  1790. function parseValueTypeDefinition(typeDefinition) {
  1791. const typeFromObject = parseValueTypeObject(typeDefinition);
  1792. const typeFromDefaultValue = parseValueTypeDefault(typeDefinition);
  1793. const typeFromConstant = parseValueTypeConstant(typeDefinition);
  1794. const type = typeFromObject || typeFromDefaultValue || typeFromConstant;
  1795. if (type)
  1796. return type;
  1797. throw new Error(`Unknown value type "${typeDefinition}"`);
  1798. }
  1799. function defaultValueForDefinition(typeDefinition) {
  1800. const constant = parseValueTypeConstant(typeDefinition);
  1801. if (constant)
  1802. return defaultValuesByType[constant];
  1803. const defaultValue = typeDefinition.default;
  1804. if (defaultValue !== undefined)
  1805. return defaultValue;
  1806. return typeDefinition;
  1807. }
  1808. function valueDescriptorForTokenAndTypeDefinition(token, typeDefinition) {
  1809. const key = `${dasherize(token)}-value`;
  1810. const type = parseValueTypeDefinition(typeDefinition);
  1811. return {
  1812. type,
  1813. key,
  1814. name: camelize(key),
  1815. get defaultValue() { return defaultValueForDefinition(typeDefinition); },
  1816. get hasCustomDefaultValue() { return parseValueTypeDefault(typeDefinition) !== undefined; },
  1817. reader: readers[type],
  1818. writer: writers[type] || writers.default
  1819. };
  1820. }
  1821. const defaultValuesByType = {
  1822. get array() { return []; },
  1823. boolean: false,
  1824. number: 0,
  1825. get object() { return {}; },
  1826. string: ""
  1827. };
  1828. const readers = {
  1829. array(value) {
  1830. const array = JSON.parse(value);
  1831. if (!Array.isArray(array)) {
  1832. throw new TypeError("Expected array");
  1833. }
  1834. return array;
  1835. },
  1836. boolean(value) {
  1837. return !(value == "0" || value == "false");
  1838. },
  1839. number(value) {
  1840. return Number(value);
  1841. },
  1842. object(value) {
  1843. const object = JSON.parse(value);
  1844. if (object === null || typeof object != "object" || Array.isArray(object)) {
  1845. throw new TypeError("Expected object");
  1846. }
  1847. return object;
  1848. },
  1849. string(value) {
  1850. return value;
  1851. }
  1852. };
  1853. const writers = {
  1854. default: writeString,
  1855. array: writeJSON,
  1856. object: writeJSON
  1857. };
  1858. function writeJSON(value) {
  1859. return JSON.stringify(value);
  1860. }
  1861. function writeString(value) {
  1862. return `${value}`;
  1863. }
  1864. class Controller {
  1865. constructor(context) {
  1866. this.context = context;
  1867. }
  1868. static get shouldLoad() {
  1869. return true;
  1870. }
  1871. get application() {
  1872. return this.context.application;
  1873. }
  1874. get scope() {
  1875. return this.context.scope;
  1876. }
  1877. get element() {
  1878. return this.scope.element;
  1879. }
  1880. get identifier() {
  1881. return this.scope.identifier;
  1882. }
  1883. get targets() {
  1884. return this.scope.targets;
  1885. }
  1886. get classes() {
  1887. return this.scope.classes;
  1888. }
  1889. get data() {
  1890. return this.scope.data;
  1891. }
  1892. initialize() {
  1893. }
  1894. connect() {
  1895. }
  1896. disconnect() {
  1897. }
  1898. dispatch(eventName, { target = this.element, detail = {}, prefix = this.identifier, bubbles = true, cancelable = true } = {}) {
  1899. const type = prefix ? `${prefix}:${eventName}` : eventName;
  1900. const event = new CustomEvent(type, { detail, bubbles, cancelable });
  1901. target.dispatchEvent(event);
  1902. return event;
  1903. }
  1904. }
  1905. Controller.blessings = [ClassPropertiesBlessing, TargetPropertiesBlessing, ValuePropertiesBlessing];
  1906. Controller.targets = [];
  1907. Controller.values = {};
  1908. export { Application, AttributeObserver, Context, Controller, ElementObserver, IndexedMultimap, Multimap, StringMapObserver, TokenListObserver, ValueListObserver, add, defaultSchema, del, fetch, prune };