vendor: 8,009 bytes, lines 1-371
1/*! DSFR v1.12.1 | SPDX-License-Identifier: MIT | License-Filename: LICENSE.md | restricted use (see terms and conditions) */ 2 3class State { 4 constructor () { 5 this.modules = {}; 6 } 7 8 create (ModuleClass) { 9 const module = new ModuleClass(); 10 this.modules[module.type] = module; 11 } 12 13 getModule (type) { 14 return this.modules[type]; 15 } 16 17 add (type, item) { 18 this.modules[type].add(item); 19 } 20 21 remove (type, item) { 22 this.modules[type].remove(item); 23 } 24 25 get isActive () { 26 return this._isActive; 27 } 28 29 set isActive (value) { 30 if (value === this._isActive) return; 31 this._isActive = value; 32 const values = Object.keys(this.modules).map((e) => { 33 return this.modules[e]; 34 }); 35 if (value) { 36 for (const module of values) { 37 module.activate(); 38 } 39 } else { 40 for (const module of values) { 41 module.deactivate(); 42 } 43 } 44 } 45 46 get isLegacy () { 47 return this._isLegacy; 48 } 49 50 set isLegacy (value) { 51 if (value === this._isLegacy) return; 52 this._isLegacy = value; 53 } 54} 55 56const state = new State(); 57 58const config = { 59 prefix: 'fr', 60 namespace: 'dsfr', 61 organisation: '@gouvfr', 62 version: '1.12.1' 63}; 64 65class LogLevel { 66 constructor (level, light, dark, logger) { 67 this.level = level; 68 this.light = light; 69 this.dark = dark; 70 71 switch (logger) { 72 case 'warn': 73 this.logger = console.warn; 74 break; 75 76 case 'error': 77 this.logger = console.error; 78 break; 79 80 default: 81 this.logger = console.log; 82 } 83 } 84 85 log (...values) { 86 const message = new Message(config.namespace); 87 for (const value of values) message.add(value); 88 this.print(message); 89 } 90 91 print (message) { 92 message.setColor(this.color); 93 this.logger.apply(console, message.getMessage()); 94 } 95 96 get color () { 97 return window.matchMedia('(prefers-color-scheme: dark)').matches ? this.dark : this.light; 98 } 99} 100 101class Message { 102 constructor (domain) { 103 this.inputs = ['%c']; 104 this.styles = ['font-family:Marianne', 'line-height: 1.5']; 105 this.objects = []; 106 107 if (domain) this.add(`${domain} :`); 108 } 109 110 add (value) { 111 switch (typeof value) { 112 case 'object': 113 case 'function': 114 this.inputs.push('%o '); 115 this.objects.push(value); 116 break; 117 118 default: 119 this.inputs.push(`${value} `); 120 } 121 } 122 123 setColor (color) { 124 this.styles.push(`color:${color}`); 125 } 126 127 getMessage () { 128 return [this.inputs.join(''), this.styles.join(';'), ...this.objects]; 129 } 130} 131 132const LEVELS = { 133 log: new LogLevel(0, '#616161', '#989898'), 134 debug: new LogLevel(1, '#000091', '#8B8BFF'), 135 info: new LogLevel(2, '#007c3b', '#00ed70'), 136 warn: new LogLevel(3, '#ba4500', '#fa5c00', 'warn'), 137 error: new LogLevel(4, '#D80600', '#FF4641', 'error') 138}; 139 140class Inspector { 141 constructor () { 142 this.level = 2; 143 144 for (const id in LEVELS) { 145 const level = LEVELS[id]; 146 this[id] = (...msgs) => { 147 if (this.level <= level.level) level.log.apply(level, msgs); 148 }; 149 this[id].print = level.print.bind(level); 150 } 151 } 152 153 state () { 154 const message = new Message(); 155 message.add(state); 156 this.log.print(message); 157 } 158 159 tree () { 160 const stage = state.getModule('stage'); 161 if (!stage) return; 162 const message = new Message(); 163 this._branch(stage.root, 0, message); 164 this.log.print(message); 165 } 166 167 _branch (element, space, message) { 168 let branch = ''; 169 if (space > 0) { 170 let indent = ''; 171 for (let i = 0; i < space; i++) indent += ' '; 172 // branch += indent + '|\n'; 173 branch += indent + 'ââ '; 174 } 175 branch += `[${element.id}] ${element.html}`; 176 message.add(branch); 177 message.add({ '@': element }); 178 message.add('\n'); 179 for (const child of element.children) branch += this._branch(child, space + 1, message); 180 } 181} 182 183const inspector = new Inspector(); 184 185const startAtDomContentLoaded = (callback) => { 186 if (document.readyState !== 'loading') window.requestAnimationFrame(callback); 187 else document.addEventListener('DOMContentLoaded', callback); 188}; 189 190const startAuto = (callback) => { 191 // detect 192 startAtDomContentLoaded(callback); 193}; 194 195const Modes = { 196 AUTO: 'auto', 197 MANUAL: 'manual', 198 RUNTIME: 'runtime', 199 LOADED: 'loaded', 200 VUE: 'vue', 201 ANGULAR: 'angular', 202 REACT: 'react' 203}; 204 205class Options { 206 constructor () { 207 this._mode = Modes.AUTO; 208 this.isStarted = false; 209 this.starting = this.start.bind(this); 210 this.preventManipulation = false; 211 } 212 213 configure (settings = {}, start, query) { 214 this.startCallback = start; 215 const isProduction = settings.production && (!query || query.production !== 'false'); 216 switch (true) { 217 case query && !isNaN(query.level): 218 inspector.level = Number(query.level); 219 break; 220 221 case query && query.verbose && (query.verbose === 'true' || query.verbose === 1): 222 inspector.level = 0; 223 break; 224 225 case isProduction: 226 inspector.level = 999; 227 break; 228 229 case settings.verbose: 230 inspector.level = 0; 231 break; 232 } 233 inspector.info(`version ${config.version}`); 234 this.mode = settings.mode || Modes.AUTO; 235 } 236 237 set mode (value) { 238 switch (value) { 239 case Modes.AUTO: 240 this.preventManipulation = false; 241 startAuto(this.starting); 242 break; 243 244 case Modes.LOADED: 245 this.preventManipulation = false; 246 startAtDomContentLoaded(this.starting); 247 break; 248 249 case Modes.RUNTIME: 250 this.preventManipulation = false; 251 this.start(); 252 break; 253 254 case Modes.MANUAL: 255 this.preventManipulation = false; 256 break; 257 258 case Modes.VUE: 259 this.preventManipulation = true; 260 break; 261 262 case Modes.ANGULAR: 263 this.preventManipulation = true; 264 break; 265 266 case Modes.REACT: 267 this.preventManipulation = true; 268 break; 269 270 default: 271 inspector.error('Illegal mode'); 272 return; 273 } 274 275 this._mode = value; 276 inspector.info(`mode set to ${value}`); 277 } 278 279 get mode () { 280 return this._mode; 281 } 282 283 start () { 284 inspector.info('start'); 285 this.startCallback(); 286 } 287} 288 289const options = new Options(); 290 291class Collection { 292 constructor () { 293 this._collection = []; 294 } 295 296 forEach (callback) { 297 this._collection.forEach(callback); 298 } 299 300 map (callback) { 301 return this._collection.map(callback); 302 } 303 304 get length () { 305 return this._collection.length; 306 } 307 308 add (collectable) { 309 if (this._collection.indexOf(collectable) > -1) return false; 310 this._collection.push(collectable); 311 if (this.onAdd) this.onAdd(); 312 if (this.onPopulate && this._collection.length === 1) this.onPopulate(); 313 return true; 314 } 315 316 remove (collectable) { 317 const index = this._collection.indexOf(collectable); 318 if (index === -1) return false; 319 this._collection.splice(index, 1); 320 if (this.onRemove) this.onRemove(); 321 if (this.onEmpty && this._collection.length === 0) this.onEmpty(); 322 } 323 324 execute (...args) { 325 for (const collectable of this._collection) if (collectable) collectable.apply(null, args); 326 } 327 328 clear () { 329 this._collection.length = 0; 330 } 331 332 clone () { 333 const clone = new Collection(); 334 clone._collection = this._collection.slice(); 335 return clone; 336 } 337 338 get collection () { 339 return this._collection; 340 } 341} 342 343class Module extends Collection { 344 constructor (type) { 345 super(); 346 this.type = type; 347 this.isActive = false; 348 } 349 350 activate () {} 351 deactivate () {} 352} 353 354const ns = name => `${config.prefix}-${name}`; 355 356ns.selector = (name, notation) => { 357 if (notation === undefined) notation = '.'; 358 return `${notation}${ns(name)}`; 359}; 360 361ns.attr = (name) => `data-${ns(name)}`; 362 363ns.attr.selector = (name, value) => { 364 let result = ns.attr(name); 365 if (value !== undefined) result += `="${value}"`; 366 return `[${result}]`; 367}; 368 369ns.event = (type) => `${config.namespace}.${type}`; 370 371ns.emission = (domain, type) => `emission:${domain}.${type}`;
vendor: 9,408 bytes, lines 371-679
371 372 373const querySelectorAllArray = (element, selectors) => Array.prototype.slice.call(element.querySelectorAll(selectors)); 374 375const queryParentSelector = (element, selectors) => { 376 const parent = element.parentElement; 377 if (parent.matches(selectors)) return parent; 378 if (parent === document.documentElement) return null; 379 return queryParentSelector(parent, selectors); 380}; 381 382class Registration { 383 constructor (selector, InstanceClass, creator) { 384 this.selector = selector; 385 this.InstanceClass = InstanceClass; 386 this.creator = creator; 387 this.instances = new Collection(); 388 this.isIntroduced = false; 389 this._instanceClassName = this.InstanceClass.instanceClassName; 390 this._instanceClassNames = this.getInstanceClassNames(this.InstanceClass); 391 this._property = this._instanceClassName.substring(0, 1).toLowerCase() + this._instanceClassName.substring(1); 392 const dashed = this._instanceClassName 393 .replace(/[^a-zA-Z0-9]+/g, '-') 394 .replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2') 395 .replace(/([a-z])([A-Z])/g, '$1-$2') 396 .replace(/([0-9])([^0-9])/g, '$1-$2') 397 .replace(/([^0-9])([0-9])/g, '$1-$2') 398 .toLowerCase(); 399 this._attribute = ns.attr(`js-${dashed}`); 400 } 401 402 getInstanceClassNames (InstanceClass) { 403 const prototype = Object.getPrototypeOf(InstanceClass); 404 if (!prototype || prototype.instanceClassName === 'Instance') return [InstanceClass.instanceClassName]; 405 return [...this.getInstanceClassNames(prototype), InstanceClass.instanceClassName]; 406 } 407 408 hasInstanceClassName (instanceClassName) { 409 return this._instanceClassNames.indexOf(instanceClassName) > -1; 410 } 411 412 introduce () { 413 if (this.isIntroduced) return; 414 this.isIntroduced = true; 415 state.getModule('stage').parse(document.documentElement, this); 416 } 417 418 parse (node, nonRecursive) { 419 const nodes = []; 420 if (node.matches && node.matches(this.selector)) nodes.push(node); 421 // eslint-disable-next-line no-useless-call 422 if (!nonRecursive && node.querySelectorAll && node.querySelector(this.selector)) nodes.push.apply(nodes, querySelectorAllArray(node, this.selector)); 423 return nodes; 424 } 425 426 create (element) { 427 if (!element.node.matches(this.selector)) return; 428 const instance = new this.InstanceClass(); 429 this.instances.add(instance); 430 return instance; 431 } 432 433 remove (instance) { 434 this.instances.remove(instance); 435 } 436 437 dispose () { 438 const instances = this.instances.collection; 439 for (let i = instances.length - 1; i > -1; i--) instances[i]._dispose(); 440 this.creator = null; 441 } 442 443 get instanceClassName () { 444 return this._instanceClassName; 445 } 446 447 get instanceClassNames () { 448 return this._instanceClassNames; 449 } 450 451 get property () { 452 return this._property; 453 } 454 455 get attribute () { 456 return this._attribute; 457 } 458} 459 460class Register extends Module { 461 constructor () { 462 super('register'); 463 } 464 465 register (selector, InstanceClass, creator) { 466 const registration = new Registration(selector, InstanceClass, creator); 467 this.add(registration); 468 if (state.isActive) registration.introduce(); 469 return registration; 470 } 471 472 activate () { 473 for (const registration of this.collection) registration.introduce(); 474 } 475 476 remove (registration) { 477 registration.dispose(); 478 super.remove(registration); 479 } 480} 481 482let count = 0; 483 484class Element$1 { 485 constructor (node, id) { 486 if (!id) { 487 count++; 488 this.id = count; 489 } else this.id = id; 490 this.node = node; 491 this.attributeNames = []; 492 this.instances = []; 493 this._children = []; 494 this._parent = null; 495 this._projects = []; 496 } 497 498 get proxy () { 499 const scope = this; 500 if (!this._proxy) { 501 this._proxy = { 502 id: this.id, 503 get parent () { 504 return scope.parent ? scope.parent.proxy : null; 505 }, 506 get children () { 507 return scope.children.map((child) => child.proxy); 508 } 509 }; 510 511 for (const instance of this.instances) this._proxy[instance.registration.property] = instance.proxy; 512 } 513 return this._proxy; 514 } 515 516 get html () { 517 if (!this.node || !this.node.outerHTML) return ''; 518 const end = this.node.outerHTML.indexOf('>'); 519 return this.node.outerHTML.substring(0, end + 1); 520 } 521 522 project (registration) { 523 if (this._projects.indexOf(registration) === -1) this._projects.push(registration); 524 } 525 526 populate () { 527 const projects = this._projects.slice(); 528 this._projects.length = 0; 529 for (const registration of projects) this.create(registration); 530 } 531 532 create (registration) { 533 if (this.hasInstance(registration.instanceClassName)) { 534 // inspector.debug(`failed creation, instance of ${registration.instanceClassName} already exists on element [${this.id}]`); 535 return; 536 } 537 inspector.debug(`create instance of ${registration.instanceClassName} on element [${this.id}]`); 538 const instance = registration.create(this); 539 this.instances.push(instance); 540 instance._config(this, registration); 541 if (this._proxy) this._proxy[registration.property] = instance.proxy; 542 } 543 544 remove (instance) { 545 const index = this.instances.indexOf(instance); 546 if (index > -1) this.instances.splice(index, 1); 547 if (this._proxy) delete this._proxy[instance.registration.property]; 548 } 549 550 get parent () { 551 return this._parent; 552 } 553 554 get ascendants () { 555 return [this.parent, ...this.parent.ascendants]; 556 } 557 558 get children () { 559 return this._children; 560 } 561 562 get descendants () { 563 const descendants = [...this._children]; 564 this._children.forEach(child => descendants.push(...child.descendants)); 565 return descendants; 566 } 567 568 // TODO : emit ascendant et descendant de changement ? 569 570 addChild (child, index) { 571 if (this._children.indexOf(child) > -1) return null; 572 child._parent = this; 573 if (!isNaN(index) && index > -1 && index < this._children.length) this._children.splice(index, 0, child); 574 else this._children.push(child); 575 return child; 576 } 577 578 removeChild (child) { 579 const index = this._children.indexOf(child); 580 if (index === -1) return null; 581 child._parent = null; 582 this._children.splice(index, 1); 583 } 584 585 emit (type, data) { 586 const elements = state.getModule('stage').collection; 587 const response = []; 588 for (const element of elements) response.push(...element._emit(type, data)); 589 return response; 590 } 591 592 _emit (type, data) { 593 const response = []; 594 for (const instance of this.instances) response.push(...instance._emitter.emit(type, data)); 595 return response; 596 } 597 598 ascend (type, data) { 599 if (this._parent) return this._parent._ascend(type, data); 600 return []; 601 } 602 603 _ascend (type, data) { 604 const response = []; 605 for (const instance of this.instances) response.push(...instance._ascent.emit(type, data)); 606 if (this._parent) response.push(...this._parent._ascend(type, data)); 607 return response; 608 } 609 610 descend (type, data) { 611 const response = []; 612 for (const child of this._children) response.push(...child._descend(type, data)); 613 return response; 614 } 615 616 _descend (type, data) { 617 const response = []; 618 for (const instance of this.instances) response.push(...instance._descent.emit(type, data)); 619 for (const child of this._children) response.push(...child._descend(type, data)); 620 return response; 621 } 622 623 getInstance (instanceClassName) { 624 for (const instance of this.instances) if (instance.registration.hasInstanceClassName(instanceClassName)) return instance; 625 return null; 626 } 627 628 hasInstance (instanceClassName) { 629 return this.getInstance(instanceClassName) !== null; 630 } 631 632 getDescendantInstances (instanceClassName, stopAtInstanceClassName, stopAtFirstInstance) { 633 if (!instanceClassName) return []; 634 const instances = []; 635 for (const child of this._children) { 636 const instance = child.getInstance(instanceClassName); 637 if (instance) { 638 instances.push(instance); 639 if (stopAtFirstInstance) continue; 640 } 641 if ((!stopAtInstanceClassName || !child.hasInstance(stopAtInstanceClassName)) && child.children.length) instances.push.apply(instances, child.getDescendantInstances(instanceClassName, stopAtInstanceClassName, stopAtFirstInstance)); 642 } 643 return instances; 644 } 645 646 getAscendantInstance (instanceClassName, stopAtInstanceClassName) { 647 if (!instanceClassName || !this._parent) return null; 648 const instance = this._parent.getInstance(instanceClassName); 649 if (instance) return instance; 650 if (stopAtInstanceClassName && this._parent.hasInstance(stopAtInstanceClassName)) return null; 651 return this._parent.getAscendantInstance(instanceClassName, stopAtInstanceClassName); 652 } 653 654 dispose () { 655 for (let i = this.instances.length - 1; i >= 0; i--) { 656 const instance = this.instances[i]; 657 if (instance) instance._dispose(); 658 } 659 this.instances.length = 0; 660 state.remove('stage', this); 661 this.parent.removeChild(this); 662 this._children.length = 0; 663 inspector.debug(`remove element [${this.id}] ${this.html}`); 664 } 665 666 prepare (attributeName) { 667 if (this.attributeNames.indexOf(attributeName) === -1) this.attributeNames.push(attributeName); 668 } 669 670 examine () { 671 const attributeNames = this.attributeNames.slice(); 672 this.attributeNames.length = 0; 673 for (let i = this.instances.length - 1; i > -1; i--) this.instances[i].examine(attributeNames); 674 } 675} 676 677const RootEmission = { 678 CLICK: ns.emission('root', 'click'), 679
679KEYDOWN: ns.emission('root', 'keydown'), 680 KEYUP: ns.emission('root', 'keyup') 681}; 682 683const KeyCodes = { 684 TAB: { 685 id: 'tab', 686 value: 9 687 }, 688 ESCAPE: { 689 id: 'escape', 690 value: 27 691 }, 692 END: { 693 id: 'end', 694 value: 35 695 }, 696 HOME: { 697 id: 'home', 698 value: 36 699 }, 700 LEFT: { 701 id: 'left', 702 value: 37 703 }, 704 UP: { 705 id: 'up', 706 value: 38 707 }, 708 RIGHT: { 709 id: 'right', 710 value: 39 711 }, 712 DOWN: { 713 id: 'down', 714 value: 40 715 } 716}; 717 718const getKeyCode = (keyCode) => Object.values(KeyCodes).filter(entry => entry.value === keyCode)[0]; 719
vendor: 46,617 bytes, lines 720-2596
720class Root extends Element$1 { 721 constructor () { 722 super(document.documentElement, 'root'); 723 this.node.setAttribute(ns.attr('js'), true); 724 this.listen(); 725 } 726 727 listen () { 728 // TODO v2 => listener au niveau des éléments qui redistribuent aux instances. 729 document.documentElement.addEventListener('click', this.click.bind(this), { capture: true }); 730 document.documentElement.addEventListener('keydown', this.keydown.bind(this), { capture: true }); 731 document.documentElement.addEventListener('keyup', this.keyup.bind(this), { capture: true }); 732 } 733 734 click (e) { 735 this.emit(RootEmission.CLICK, e.target); 736 } 737 738 keydown (e) { 739 this.emit(RootEmission.KEYDOWN, getKeyCode(e.keyCode)); 740 } 741 742 keyup (e) { 743 this.emit(RootEmission.KEYUP, getKeyCode(e.keyCode)); 744 } 745} 746 747class Stage extends Module { 748 constructor () { 749 super('stage'); 750 this.root = new Root(); 751 super.add(this.root); 752 this.observer = new MutationObserver(this.mutate.bind(this)); 753 this.modifications = []; 754 this.willModify = false; 755 this.modifying = this.modify.bind(this); 756 } 757 758 hasElement (node) { 759 for (const element of this.collection) if (element.node === node) return true; 760 return false; 761 } 762 763 getElement (node) { 764 for (const element of this.collection) if (element.node === node) return element; 765 const element = new Element$1(node); 766 this.add(element); 767 inspector.debug(`add element [${element.id}] ${element.html}`); 768 return element; 769 } 770 771 getProxy (node) { 772 if (!this.hasElement(node)) return null; 773 const element = this.getElement(node); 774 return element.proxy; 775 } 776 777 add (element) { 778 super.add(element); 779 this.put(element, this.root); 780 } 781 782 put (element, branch) { 783 let index = 0; 784 for (let i = branch.children.length - 1; i > -1; i--) { 785 const child = branch.children[i]; 786 const position = element.node.compareDocumentPosition(child.node); 787 if (position & Node.DOCUMENT_POSITION_CONTAINS) { 788 this.put(element, child); 789 return; 790 } else if (position & Node.DOCUMENT_POSITION_CONTAINED_BY) { 791 branch.removeChild(child); 792 element.addChild(child, 0); 793 } else if (position & Node.DOCUMENT_POSITION_PRECEDING) { 794 index = i + 1; 795 break; 796 } 797 } 798 799 branch.addChild(element, index); 800 } 801 802 activate () { 803 this.observer.observe(document.documentElement, { childList: true, subtree: true, attributes: true }); 804 } 805 806 deactivate () { 807 this.observer.disconnect(); 808 } 809 810 mutate (mutations) { 811 const examinations = []; 812 mutations.forEach((mutation) => { 813 switch (mutation.type) { 814 case 'childList': 815 mutation.removedNodes.forEach((node) => this.dispose(node)); 816 mutation.addedNodes.forEach((node) => this.parse(node)); 817 break; 818 819 case 'attributes': 820 if (this.hasElement(mutation.target)) { 821 const element = this.getElement(mutation.target); 822 element.prepare(mutation.attributeName); 823 if (examinations.indexOf(element) === -1) examinations.push(element); 824 for (const descendant of element.descendants) if (examinations.indexOf(descendant) === -1) examinations.push(descendant); 825 } 826 if (this.modifications.indexOf(mutation.target) === -1) this.modifications.push(mutation.target); 827 break; 828 } 829 }); 830 831 examinations.forEach(element => element.examine()); 832 if (this.modifications.length && !this.willModify) { 833 this.willModify = true; 834 window.requestAnimationFrame(this.modifying); 835 } 836 } 837 838 modify () { 839 this.willModify = false; 840 const targets = this.modifications.slice(); 841 this.modifications.length = 0; 842 for (const target of targets) if (document.documentElement.contains(target)) this.parse(target); 843 } 844 845 dispose (node) { 846 const disposables = []; 847 this.forEach((element) => { 848 if (node.contains(element.node)) disposables.push(element); 849 }); 850 851 for (const disposable of disposables) { 852 disposable.dispose(); 853 this.remove(disposable); 854 } 855 } 856 857 parse (node, registration, nonRecursive) { 858 const registrations = registration ? [registration] : state.getModule('register').collection; 859 const creations = []; 860 861 for (const registration of registrations) { 862 const nodes = registration.parse(node, nonRecursive); 863 864 for (const n of nodes) { 865 const element = this.getElement(n); 866 element.project(registration); 867 if (creations.indexOf(element) === -1) creations.push(element); 868 } 869 } 870 871 for (const element of creations) element.populate(); 872 } 873} 874 875class Renderer extends Module { 876 constructor () { 877 super('render'); 878 this.rendering = this.render.bind(this); 879 this.nexts = new Collection(); 880 } 881 882 activate () { 883 window.requestAnimationFrame(this.rendering); 884 } 885 886 request (instance) { 887 this.nexts.add(instance); 888 } 889 890 render () { 891 if (!state.isActive) return; 892 window.requestAnimationFrame(this.rendering); 893 this.forEach((instance) => instance.render()); 894 if (!this.nexts.length) return; 895 const nexts = this.nexts.clone(); 896 this.nexts.clear(); 897 nexts.forEach((instance) => instance.next()); 898 } 899} 900 901class Resizer extends Module { 902 constructor () { 903 super('resize'); 904 this.requireResize = false; 905 this.resizing = this.resize.bind(this); 906 const requesting = this.request.bind(this); 907 if (document.fonts) { 908 document.fonts.ready.then(requesting); 909 } 910 window.addEventListener('resize', requesting); 911 window.addEventListener('orientationchange', requesting); 912 } 913 914 activate () { 915 this.request(); 916 } 917 918 request () { 919 if (this.requireResize) return; 920 this.requireResize = true; 921 window.requestAnimationFrame(this.resizing); 922 } 923 924 resize () { 925 if (!this.requireResize) return; 926 this.forEach((instance) => instance.resize()); 927 this.requireResize = false; 928 } 929} 930 931class ScrollLocker extends Module { 932 constructor () { 933 super('lock'); 934 this._isLocked = false; 935 this._scrollY = 0; 936 this.onPopulate = this.lock.bind(this); 937 this.onEmpty = this.unlock.bind(this); 938 } 939 940 get isLocked () { 941 return this._isLocked; 942 } 943 944 lock () { 945 if (!this._isLocked) { 946 this._isLocked = true; 947 this._scrollY = window.scrollY; 948 const scrollBarGap = window.innerWidth - document.documentElement.clientWidth; 949 document.documentElement.setAttribute(ns.attr('scrolling'), 'false'); 950 document.body.style.top = `${-this._scrollY}px`; 951 this.behavior = getComputedStyle(document.documentElement).getPropertyValue('scroll-behavior'); 952 if (this.behavior === 'smooth') document.documentElement.style.scrollBehavior = 'auto'; 953 if (scrollBarGap > 0) { 954 document.documentElement.style.setProperty('--scrollbar-width', `${scrollBarGap}px`); 955 } 956 } 957 } 958 959 unlock () { 960 if (this._isLocked) { 961 this._isLocked = false; 962 document.documentElement.removeAttribute(ns.attr('scrolling')); 963 document.body.style.top = ''; 964 window.scrollTo(0, this._scrollY); 965 if (this.behavior === 'smooth') document.documentElement.style.removeProperty('scroll-behavior'); 966 document.documentElement.style.removeProperty('--scrollbar-width'); 967 } 968 } 969 970 move (value) { 971 if (this._isLocked) { 972 this._scrollY += value; 973 document.body.style.top = `${-this._scrollY}px`; 974 } else { 975 window.scrollTo(0, window.scrollY + value); 976 } 977 } 978} 979 980class Load extends Module { 981 constructor () { 982 super('load'); 983 this.loading = this.load.bind(this); 984 } 985 986 activate () { 987 window.addEventListener('load', this.loading); 988 } 989 990 load () { 991 this.forEach((instance) => instance.load()); 992 } 993} 994 995const FONT_FAMILIES = ['Marianne', 'Spectral']; 996 997class FontSwap extends Module { 998 constructor () { 999 super('font-swap'); 1000 this.swapping = this.swap.bind(this); 1001 } 1002 1003 activate () { 1004 if (document.fonts) { 1005 document.fonts.addEventListener('loadingdone', this.swapping); 1006 } 1007 } 1008 1009 swap () { 1010 const families = FONT_FAMILIES.filter(family => document.fonts.check(`16px ${family}`)); 1011 1012 this.forEach((instance) => instance.swapFont(families)); 1013 } 1014} 1015 1016class MouseMove extends Module { 1017 constructor () { 1018 super('mouse-move'); 1019 this.requireMove = false; 1020 this._isMoving = false; 1021 this.moving = this.move.bind(this); 1022 this.requesting = this.request.bind(this); 1023 this.onPopulate = this.listen.bind(this); 1024 this.onEmpty = this.unlisten.bind(this); 1025 } 1026 1027 listen () { 1028 if (this._isMoving) return; 1029 this._isMoving = true; 1030 this.requireMove = false; 1031 document.documentElement.addEventListener('mousemove', this.requesting); 1032 } 1033 1034 unlisten () { 1035 if (!this._isMoving) return; 1036 this._isMoving = false; 1037 this.requireMove = false; 1038 document.documentElement.removeEventListener('mousemove', this.requesting); 1039 } 1040 1041 request (e) { 1042 if (!this._isMoving) return; 1043 this.point = { x: e.clientX, y: e.clientY }; 1044 if (this.requireMove) return; 1045 this.requireMove = true; 1046 window.requestAnimationFrame(this.moving); 1047 } 1048 1049 move () { 1050 if (!this.requireMove) return; 1051 this.forEach((instance) => instance.mouseMove(this.point)); 1052 this.requireMove = false; 1053 } 1054} 1055 1056class Hash extends Module { 1057 constructor () { 1058 super('hash'); 1059 this.handling = this.handle.bind(this); 1060 this.getLocationHash(); 1061 } 1062 1063 activate () { 1064 window.addEventListener('hashchange', this.handling); 1065 } 1066 1067 deactivate () { 1068 window.removeEventListener('hashchange', this.handling); 1069 } 1070 1071 _sanitize (hash) { 1072 if (hash.charAt(0) === '#') return hash.substring(1); 1073 return hash; 1074 } 1075 1076 set hash (value) { 1077 const hash = this._sanitize(value); 1078 if (this._hash !== hash) window.location.hash = hash; 1079 } 1080 1081 get hash () { 1082 return this._hash; 1083 } 1084 1085 getLocationHash () { 1086 const hash = window.location.hash; 1087 this._hash = this._sanitize(hash); 1088 } 1089 1090 handle (e) { 1091 this.getLocationHash(); 1092 this.forEach((instance) => instance.handleHash(this._hash, e)); 1093 } 1094} 1095 1096class Engine { 1097 constructor () { 1098 state.create(Register); 1099 state.create(Stage); 1100 state.create(Renderer); 1101 state.create(Resizer); 1102 state.create(ScrollLocker); 1103 state.create(Load); 1104 state.create(FontSwap); 1105 state.create(MouseMove); 1106 state.create(Hash); 1107 1108 const registerModule = state.getModule('register'); 1109 this.register = registerModule.register.bind(registerModule); 1110 } 1111 1112 get isActive () { 1113 return state.isActive; 1114 } 1115 1116 start () { 1117 inspector.debug('START'); 1118 state.isActive = true; 1119 } 1120 1121 stop () { 1122 inspector.debug('STOP'); 1123 state.isActive = false; 1124 } 1125} 1126 1127const engine = new Engine(); 1128 1129class Colors { 1130 getColor (context, use, tint, options = {}) { 1131 const option = getOption(options); 1132 const decision = `--${context}-${use}-${tint}${option}`; 1133 return getComputedStyle(document.documentElement).getPropertyValue(decision).trim() || null; 1134 } 1135} 1136 1137const getOption = (options) => { 1138 switch (true) { 1139 case options.hover: 1140 return '-hover'; 1141 case options.active: 1142 return '-active'; 1143 default: 1144 return ''; 1145 } 1146}; 1147 1148const colors = new Colors(); 1149 1150const sanitize = (className) => className.charAt(0) === '.' ? className.substr(1) : className; 1151 1152const getClassNames = (element) => { 1153 switch (true) { 1154 case !element.className: 1155 return []; 1156 1157 case typeof element.className === 'string': 1158 return element.className.split(' '); 1159 1160 case typeof element.className.baseVal === 'string': 1161 return element.className.baseVal.split(' '); 1162 } 1163 1164 return []; 1165}; 1166 1167const modifyClass = (element, className, remove) => { 1168 className = sanitize(className); 1169 const classNames = getClassNames(element); 1170 const index = classNames.indexOf(className); 1171 if (remove === true) { 1172 if (index > -1) classNames.splice(index, 1); 1173 } else if (index === -1) classNames.push(className); 1174 element.className = classNames.join(' '); 1175}; 1176 1177const addClass = (element, className) => modifyClass(element, className); 1178 1179const removeClass = (element, className) => modifyClass(element, className, true); 1180 1181const hasClass = (element, className) => getClassNames(element).indexOf(sanitize(className)) > -1; 1182 1183const ACTIONS = [ 1184 '[tabindex]:not([tabindex="-1"])', 1185 'a[href]', 1186 'button:not([disabled])', 1187 'input:not([disabled])', 1188 'select:not([disabled])', 1189 'textarea:not([disabled])', 1190 'audio[controls]', 1191 'video[controls]', 1192 '[contenteditable]:not([contenteditable="false"])', 1193 'details>summary:first-of-type', 1194 'details', 1195 'iframe' 1196]; 1197 1198const ACTIONS_SELECTOR = ACTIONS.join(); 1199 1200const queryActions = (element) => { 1201 return element.querySelectorAll(ACTIONS_SELECTOR); 1202}; 1203 1204let counter = 0; 1205 1206const uniqueId = (id) => { 1207 if (!document.getElementById(id)) return id; 1208 let element = true; 1209 const base = id; 1210 while (element) { 1211 counter++; 1212 id = `${base}-${counter}`; 1213 element = document.getElementById(id); 1214 } 1215 return id; 1216}; 1217 1218const dom = { 1219 addClass: addClass, 1220 hasClass: hasClass, 1221 removeClass: removeClass, 1222 queryParentSelector: queryParentSelector, 1223 querySelectorAllArray: querySelectorAllArray, 1224 queryActions: queryActions, 1225 uniqueId: uniqueId 1226}; 1227 1228class DataURISVG { 1229 constructor (width = 0, height = 0) { 1230 this._width = width; 1231 this._height = height; 1232 this._content = ''; 1233 } 1234 1235 get width () { 1236 return this._width; 1237 } 1238 1239 set width (value) { 1240 this._width = value; 1241 } 1242 1243 get height () { 1244 return this._height; 1245 } 1246 1247 set height (value) { 1248 this._height = value; 1249 } 1250 1251 get content () { 1252 return this._content; 1253 } 1254 1255 set content (value) { 1256 this._content = value; 1257 } 1258 1259 getDataURI (isLegacy = false) { 1260 let svg = `<svg xmlns='http://www.w3.org/2000/svg' viewbox='0 0 ${this._width} ${this._height}' width='${this._width}px' height='${this._height}px'>${this._content}</svg>`; 1261 1262 svg = svg.replace(/#/gi, '%23'); 1263 if (isLegacy) { 1264 svg = svg.replace(/</gi, '%3C'); 1265 svg = svg.replace(/>/gi, '%3E'); 1266 svg = svg.replace(/"/gi, '\''); 1267 svg = svg.replace(/{/gi, '%7B'); 1268 svg = svg.replace(/}/gi, '%7D'); 1269 } 1270 return `data:image/svg+xml;charset=utf8,${svg}`; 1271 } 1272} 1273 1274const image = { 1275 DataURISVG: DataURISVG 1276}; 1277 1278const supportLocalStorage = () => { 1279 try { 1280 return 'localStorage' in window && window.localStorage !== null; 1281 } catch (e) { 1282 return false; 1283 } 1284}; 1285 1286const supportAspectRatio = () => { 1287 if (!window.CSS) return false; 1288 return CSS.supports('aspect-ratio: 16 / 9'); 1289}; 1290 1291const support = { 1292 supportLocalStorage: supportLocalStorage, 1293 supportAspectRatio: supportAspectRatio 1294}; 1295 1296const TransitionSelector = { 1297 NONE: ns.selector('transition-none') 1298}; 1299 1300const selector = { 1301 TransitionSelector: TransitionSelector 1302}; 1303 1304/** 1305 * Copy properties from multiple sources including accessors. 1306 * source : https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#copier_des_accesseurs 1307 * 1308 * @param {object} [target] - Target object to copy into 1309 * @param {...objects} [sources] - Multiple objects 1310 * @return {object} A new object 1311 * 1312 * @example 1313 * 1314 * const obj1 = { 1315 * key: 'value' 1316 * }; 1317 * const obj2 = { 1318 * get function01 () { 1319 * return a-value; 1320 * } 1321 * set function01 () { 1322 * return a-value; 1323 * } 1324 * }; 1325 * completeAssign(obj1, obj2) 1326 */ 1327const completeAssign = (target, ...sources) => { 1328 sources.forEach(source => { 1329 const descriptors = Object.keys(source).reduce((descriptors, key) => { 1330 descriptors[key] = Object.getOwnPropertyDescriptor(source, key); 1331 return descriptors; 1332 }, {}); 1333 1334 Object.getOwnPropertySymbols(source).forEach(sym => { 1335 const descriptor = Object.getOwnPropertyDescriptor(source, sym); 1336 if (descriptor.enumerable) { 1337 descriptors[sym] = descriptor; 1338 } 1339 }); 1340 Object.defineProperties(target, descriptors); 1341 }); 1342 return target; 1343}; 1344 1345const property = { 1346 completeAssign: completeAssign 1347}; 1348 1349/** 1350 * Return an object of query params or null 1351 * 1352 * @method 1353 * @name searchParams 1354 * @param {string} url - an url 1355 * @returns {Object} object of query params or null 1356 */ 1357 1358const searchParams = (url) => { 1359 if (url && url.search) { 1360 const params = new URLSearchParams(window.location.search); 1361 const entries = params.entries(); 1362 return Object.fromEntries(entries); 1363 } 1364 return null; 1365}; 1366 1367const internals = {}; 1368const legacy = {}; 1369 1370Object.defineProperty(legacy, 'isLegacy', { 1371 get: () => state.isLegacy 1372}); 1373 1374legacy.setLegacy = () => { 1375 state.isLegacy = true; 1376}; 1377 1378internals.legacy = legacy; 1379internals.dom = dom; 1380internals.image = image; 1381internals.support = support; 1382internals.motion = selector; 1383internals.property = property; 1384internals.ns = ns; 1385internals.register = engine.register; 1386internals.state = state; 1387internals.query = searchParams(window.location); 1388 1389Object.defineProperty(internals, 'preventManipulation', { 1390 get: () => options.preventManipulation 1391}); 1392Object.defineProperty(internals, 'stage', { 1393 get: () => state.getModule('stage') 1394}); 1395 1396const api$1 = (node) => { 1397 const stage = state.getModule('stage'); 1398 return stage.getProxy(node); 1399}; 1400 1401api$1.version = config.version; 1402api$1.prefix = config.prefix; 1403api$1.organisation = config.organisation; 1404api$1.Modes = Modes; 1405 1406Object.defineProperty(api$1, 'mode', { 1407 set: (value) => { options.mode = value; }, 1408 get: () => options.mode 1409}); 1410 1411api$1.internals = internals; 1412api$1.version = config.version; 1413 1414api$1.start = engine.start; 1415api$1.stop = engine.stop; 1416 1417api$1.inspector = inspector; 1418api$1.colors = colors; 1419 1420const configuration = window[config.namespace]; 1421api$1.internals.configuration = configuration; 1422 1423options.configure(configuration, api$1.start, api$1.internals.query); 1424 1425window[config.namespace] = api$1; 1426 1427class Emitter { 1428 constructor () { 1429 this.emissions = {}; 1430 } 1431 1432 add (type, closure) { 1433 if (typeof closure !== 'function') throw new Error('closure must be a function'); 1434 if (!this.emissions[type]) this.emissions[type] = []; 1435 this.emissions[type].push(closure); 1436 } 1437 1438 remove (type, closure) { 1439 if (!this.emissions[type]) return; 1440 if (!closure) delete this.emissions[type]; 1441 else { 1442 const index = this.emissions[type].indexOf(closure); 1443 if (index > -1) this.emissions[type].splice(index); 1444 } 1445 } 1446 1447 emit (type, data) { 1448 if (!this.emissions[type]) return []; 1449 const response = []; 1450 for (const closure of this.emissions[type]) if (closure) response.push(closure(data)); 1451 return response; 1452 } 1453 1454 dispose () { 1455 this.emissions = null; 1456 } 1457} 1458 1459class Breakpoint { 1460 constructor (id, minWidth) { 1461 this.id = id; 1462 this.minWidth = minWidth; 1463 } 1464 1465 test () { 1466 return window.matchMedia(`(min-width: ${this.minWidth}em)`).matches; 1467 } 1468} 1469 1470const Breakpoints = { 1471 XS: new Breakpoint('xs', 0), 1472 SM: new Breakpoint('sm', 36), 1473 MD: new Breakpoint('md', 48), 1474 LG: new Breakpoint('lg', 62), 1475 XL: new Breakpoint('xl', 78) 1476}; 1477 1478class Instance { 1479 constructor (jsAttribute = true) { 1480 this.jsAttribute = jsAttribute; 1481 this._isRendering = false; 1482 this._isResizing = false; 1483 this._isScrollLocked = false; 1484 this._isLoading = false; 1485 this._isSwappingFont = false; 1486 this._isEnabled = true; 1487 this._isDisposed = false; 1488 this._listeners = {}; 1489 this._handlingClick = this.handleClick.bind(this); 1490 this._hashes = []; 1491 this._hash = ''; 1492 this._keyListenerTypes = []; 1493 this._keys = []; 1494 this.handlingKey = this.handleKey.bind(this); 1495 this._emitter = new Emitter(); 1496 this._ascent = new Emitter(); 1497 this._descent = new Emitter(); 1498 this._registrations = []; 1499 this._nexts = []; 1500 } 1501 1502 static get instanceClassName () { 1503 return 'Instance'; 1504 } 1505 1506 _config (element, registration) { 1507 this.element = element; 1508 this.registration = registration; 1509 this.node = element.node; 1510 this.id = element.node.id; 1511 if (this.jsAttribute) this.setAttribute(registration.attribute, true); 1512 this.init(); 1513 } 1514 1515 init () {} 1516 1517 get proxy () { 1518 const scope = this; 1519 const proxy = { 1520 render: () => scope.render(), 1521 resize: () => scope.resize() 1522 }; 1523 1524 const proxyAccessors = { 1525 get node () { 1526 return this.node; 1527 }, 1528 get isEnabled () { 1529 return scope.isEnabled; 1530 }, 1531 set isEnabled (value) { 1532 scope.isEnabled = value; 1533 } 1534 }; 1535 1536 return completeAssign(proxy, proxyAccessors); 1537 } 1538 1539 log (...values) { 1540 values.unshift(`${this.registration.instanceClassName} #${this.id} - `); 1541 inspector.log.apply(inspector, values); 1542 } 1543 1544 debug (...values) { 1545 values.unshift(`${this.registration.instanceClassName} #${this.id} - `); 1546 inspector.debug.apply(inspector, values); 1547 } 1548 1549 info (...values) { 1550 values.unshift(`${this.registration.instanceClassName} #${this.id} - `); 1551 inspector.info.apply(inspector, values); 1552 } 1553 1554 warn (...values) { 1555 values.unshift(`${this.registration.instanceClassName} #${this.id} - `); 1556 inspector.warn.apply(inspector, values); 1557 } 1558 1559 error (...values) { 1560 values.unshift(`${this.registration.instanceClassName} #${this.id} - `); 1561 inspector.error.apply(inspector, values); 1562 } 1563 1564 register (selector, InstanceClass) { 1565 const registration = state.getModule('register').register(selector, InstanceClass, this); 1566 this._registrations.push(registration); 1567 } 1568 1569 getRegisteredInstances (instanceClassName) { 1570 for (const registration of this._registrations) if (registration.hasInstanceClassName(instanceClassName)) return registration.instances.collection; 1571 return []; 1572 } 1573 1574 dispatch (type, detail, bubbles, cancelable) { 1575 const event = new CustomEvent(type, { detail: detail, bubble: bubbles === true, cancelable: cancelable === true }); 1576 this.node.dispatchEvent(event); 1577 } 1578 1579 // TODO v2 => listener au niveau des éléments qui redistribuent aux instances. 1580 listen (type, closure, options) { 1581 if (!this._listeners[type]) this._listeners[type] = []; 1582 const listeners = this._listeners[type]; 1583 // if (listeners.some(listener => listener.closure === closure)) return; 1584 const listener = new Listener(this.node, type, closure, options); 1585 listeners.push(listener); 1586 listener.listen(); 1587 } 1588 1589 unlisten (type, closure, options) { 1590 if (!type) { 1591 for (const type in this._listeners) this.unlisten(type); 1592 return; 1593 } 1594 1595 const listeners = this._listeners[type]; 1596 1597 if (!listeners) return; 1598 1599 if (!closure) { 1600 listeners.forEach(listener => this.unlisten(type, listener.closure)); 1601 return; 1602 } 1603 1604 const removal = listeners.filter(listener => listener.closure === closure && listener.matchOptions(options)); 1605 removal.forEach(listener => listener.unlisten()); 1606 this._listeners[type] = listeners.filter(listener => removal.indexOf(listener) === -1); 1607 } 1608 1609 listenClick (options) { 1610 this.listen('click', this._handlingClick, options); 1611 } 1612 1613 unlistenClick (options) { 1614 this.unlisten('click', this._handlingClick, options); 1615 } 1616 1617 handleClick (e) {} 1618 1619 set hash (value) { 1620 state.getModule('hash').hash = value; 1621 } 1622 1623 get hash () { 1624 return state.getModule('hash').hash; 1625 } 1626 1627 listenHash (hash, add) { 1628 if (!this._hashes) return; 1629 if (this._hashes.length === 0) state.add('hash', this); 1630 const action = new HashAction(hash, add); 1631 this._hashes = this._hashes.filter(action => action.hash !== hash); 1632 this._hashes.push(action); 1633 } 1634 1635 unlistenHash (hash) { 1636 if (!this._hashes) return; 1637 this._hashes = this._hashes.filter(action => action.hash !== hash); 1638 if (this._hashes.length === 0) state.remove('hash', this); 1639 } 1640 1641 handleHash (hash, e) { 1642 if (!this._hashes) return; 1643 for (const action of this._hashes) action.handle(hash, e); 1644 } 1645 1646 listenKey (keyCode, closure, preventDefault = false, stopPropagation = false, type = 'down') { 1647 if (this._keyListenerTypes.indexOf(type) === -1) { 1648 this.listen(`key${type}`, this.handlingKey); 1649 this._keyListenerTypes.push(type); 1650 } 1651 1652 this._keys.push(new KeyAction(type, keyCode, closure, preventDefault, stopPropagation)); 1653 } 1654 1655 unlistenKey (code, closure) { 1656 this._keys = this._keys.filter((key) => key.code !== code || key.closure !== closure); 1657 1658 this._keyListenerTypes.forEach(type => { 1659 if (!this._keys.some(key => key.type === type)) this.unlisten(`key${type}`, this.handlingKey); 1660 }); 1661 } 1662 1663 handleKey (e) { 1664 for (const key of this._keys) key.handle(e); 1665 } 1666 1667 get isEnabled () { return this._isEnabled; } 1668 1669 set isEnabled (value) { 1670 this._isEnabled = value; 1671 } 1672 1673 get isRendering () { return this._isRendering; } 1674 1675 set isRendering (value) { 1676 if (this._isRendering === value) return; 1677 if (value) state.add('render', this); 1678 else state.remove('render', this); 1679 this._isRendering = value; 1680 } 1681 1682 render () {} 1683 1684 request (closure) { 1685 this._nexts.push(closure); 1686 state.getModule('render').request(this); 1687 } 1688 1689 next () { 1690 const nexts = this._nexts.slice(); 1691 this._nexts.length = 0; 1692 for (const closure of nexts) if (closure) closure(); 1693 } 1694 1695 get isResizing () { return this._isResizing; } 1696 1697 set isResizing (value) { 1698 if (this._isResizing === value) return; 1699 if (value) { 1700 state.add('resize', this); 1701 this.resize(); 1702 } else state.remove('resize', this); 1703 this._isResizing = value; 1704 } 1705 1706 resize () {} 1707 1708 isBreakpoint (breakpoint) { 1709 switch (true) { 1710 case typeof breakpoint === 'string': 1711 return Breakpoints[breakpoint.toUpperCase()].test(); 1712 1713 default: 1714 return breakpoint.test(); 1715 } 1716 } 1717 1718 get isScrollLocked () { 1719 return this._isScrollLocked; 1720 } 1721 1722 set isScrollLocked (value) { 1723 if (this._isScrollLocked === value) return; 1724 if (value) state.add('lock', this); 1725 else state.remove('lock', this); 1726 this._isScrollLocked = value; 1727 } 1728 1729 get isLoading () { 1730 return this._isLoading; 1731 } 1732 1733 set isLoading (value) { 1734 if (this._isLoading === value) return; 1735 if (value) state.add('load', this); 1736 else state.remove('load', this); 1737 this._isLoading = value; 1738 } 1739 1740 load () {} 1741 1742 get isSwappingFont () { 1743 return this._isSwappingFont; 1744 } 1745 1746 set isSwappingFont (value) { 1747 if (this._isSwappingFont === value) return; 1748 if (value) state.add('font-swap', this); 1749 else state.remove('font-swap', this); 1750 this._isSwappingFont = value; 1751 } 1752 1753 swapFont () {} 1754 1755 get isMouseMoving () { return this._isMouseMoving; } 1756 1757 set isMouseMoving (value) { 1758 if (this._isMouseMoving === value) return; 1759 if (value) { 1760 state.add('mouse-move', this); 1761 } else { 1762 state.remove('mouse-move', this); 1763 } 1764 this._isMouseMoving = value; 1765 } 1766 1767 mouseMove (point) {} 1768 1769 examine (attributeNames) { 1770 if (!this.node.matches(this.registration.selector)) { 1771 this._dispose(); 1772 return; 1773 } 1774 1775 this.mutate(attributeNames); 1776 } 1777 1778 mutate (attributeNames) {} 1779 1780 retrieveNodeId (node, append) { 1781 if (node.id) return node.id; 1782 const id = uniqueId(`${this.id}-${append}`); 1783 this.warn(`add id '${id}' to ${append}`); 1784 node.setAttribute('id', id); 1785 return id; 1786 } 1787 1788 get isDisposed () { 1789 return this._isDisposed; 1790 } 1791 1792 _dispose () { 1793 this.debug(`dispose instance of ${this.registration.instanceClassName} on element [${this.element.id}]`); 1794 this.removeAttribute(this.registration.attribute); 1795 this.unlisten(); 1796 state.remove('hash', this); 1797 this._hashes = null; 1798 this._keys = null; 1799 this.isRendering = false; 1800 this.isResizing = false; 1801 this._nexts = null; 1802 state.getModule('render').nexts.remove(this); 1803 this.isScrollLocked = false; 1804 this.isLoading = false; 1805 this.isSwappingFont = false; 1806 this.isMouseMoving = false; 1807 this._emitter.dispose(); 1808 this._emitter = null; 1809 this._ascent.dispose(); 1810 this._ascent = null; 1811 this._descent.dispose(); 1812 this._descent = null; 1813 this.element.remove(this); 1814 for (const registration of this._registrations) state.remove('register', registration); 1815 this._registrations = null; 1816 this.registration.remove(this); 1817 this._isDisposed = true; 1818 this.dispose(); 1819 } 1820 1821 dispose () {} 1822 1823 emit (type, data) { 1824 return this.element.emit(type, data); 1825 } 1826 1827 addEmission (type, closure) { 1828 this._emitter.add(type, closure); 1829 } 1830 1831 removeEmission (type, closure) { 1832 this._emitter.remove(type, closure); 1833 } 1834 1835 ascend (type, data) { 1836 return this.element.ascend(type, data); 1837 } 1838 1839 addAscent (type, closure) { 1840 this._ascent.add(type, closure); 1841 } 1842 1843 removeAscent (type, closure) { 1844 this._ascent.remove(type, closure); 1845 } 1846 1847 descend (type, data) { 1848 return this.element.descend(type, data); 1849 } 1850 1851 addDescent (type, closure) { 1852 this._descent.add(type, closure); 1853 } 1854 1855 removeDescent (type, closure) { 1856 this._descent.remove(type, closure); 1857 } 1858 1859 get style () { 1860 return this.node.style; 1861 } 1862 1863 addClass (className) { 1864 addClass(this.node, className); 1865 } 1866 1867 removeClass (className) { 1868 removeClass(this.node, className); 1869 } 1870 1871 hasClass (className) { 1872 return hasClass(this.node, className); 1873 } 1874 1875 get classNames () { 1876 return getClassNames(this.node); 1877 } 1878 1879 remove () { 1880 this.node.parentNode.removeChild(this.node); 1881 } 1882 1883 setAttribute (attributeName, value) { 1884 this.node.setAttribute(attributeName, value); 1885 } 1886 1887 getAttribute (attributeName) { 1888 return this.node.getAttribute(attributeName); 1889 } 1890 1891 hasAttribute (attributeName) { 1892 return this.node.hasAttribute(attributeName); 1893 } 1894 1895 removeAttribute (attributeName) { 1896 this.node.removeAttribute(attributeName); 1897 } 1898 1899 setProperty (propertyName, value) { 1900 this.node.style.setProperty(propertyName, value); 1901 } 1902 1903 removeProperty (propertyName) { 1904 this.node.style.removeProperty(propertyName); 1905 } 1906 1907 focus () { 1908 this.node.focus(); 1909 } 1910 1911 blur () { 1912 this.node.blur(); 1913 } 1914 1915 focusClosest () { 1916 const closest = this._focusClosest(this.node.parentNode); 1917 if (closest) closest.focus(); 1918 } 1919 1920 _focusClosest (parent) { 1921 if (!parent) return null; 1922 const actions = [...queryActions(parent)]; 1923 if (actions.length <= 1) { 1924 return this._focusClosest(parent.parentNode); 1925 } else { 1926 const index = actions.indexOf(this.node); 1927 return actions[index + (index < actions.length - 1 ? 1 : -1)]; 1928 } 1929 } 1930 1931 get hasFocus () { 1932 return this.node === document.activeElement; 1933 } 1934 1935 scrollIntoView () { 1936 const rect = this.getRect(); 1937 1938 const scroll = state.getModule('lock'); 1939 1940 if (rect.top < 0) { 1941 scroll.move(rect.top - 50); 1942 } 1943 1944 if (rect.bottom > window.innerHeight) { 1945 scroll.move(rect.bottom - window.innerHeight + 50); 1946 } 1947 } 1948 1949 matches (selectors) { 1950 return this.node.matches(selectors); 1951 } 1952 1953 querySelector (selectors) { 1954 return this.node.querySelector(selectors); 1955 } 1956 1957 querySelectorAll (selectors) { 1958 return querySelectorAllArray(this.node, selectors); 1959 } 1960 1961 queryParentSelector (selectors) { 1962 return queryParentSelector(this.node, selectors); 1963 } 1964 1965 getRect () { 1966 const rect = this.node.getBoundingClientRect(); 1967 rect.center = rect.left + rect.width * 0.5; 1968 rect.middle = rect.top + rect.height * 0.5; 1969 return rect; 1970 } 1971 1972 get isLegacy () { 1973 return state.isLegacy; 1974 } 1975} 1976 1977class KeyAction { 1978 constructor (type, keyCode, closure, preventDefault, stopPropagation) { 1979 this.type = type; 1980 this.eventType = `key${type}`; 1981 this.keyCode = keyCode; 1982 this.closure = closure; 1983 this.preventDefault = preventDefault === true; 1984 this.stopPropagation = stopPropagation === true; 1985 } 1986 1987 handle (e) { 1988 if (e.type !== this.eventType) return; 1989 if (e.keyCode === this.keyCode.value) { 1990 this.closure(e); 1991 if (this.preventDefault) { 1992 e.preventDefault(); 1993 } 1994 if (this.stopPropagation) { 1995 e.stopPropagation(); 1996 } 1997 } 1998 } 1999} 2000 2001class Listener { 2002 constructor (node, type, closure, options) { 2003 this._node = node; 2004 this._type = type; 2005 this._closure = closure; 2006 this._options = options; 2007 } 2008 2009 get closure () { 2010 return this._closure; 2011 } 2012 2013 listen () { 2014 this._node.addEventListener(this._type, this._closure, this._options); 2015 } 2016 2017 matchOptions (options = null) { 2018 switch (true) { 2019 case options === null: 2020 case typeof this._options === 'boolean' && typeof options === 'boolean' && this._options === options: 2021 return true; 2022 2023 case Object.keys(this._options).length !== Object.keys(options).length: 2024 return false; 2025 2026 case Object.keys(options).every(key => this._options[key] === options[key]): 2027 return true; 2028 } 2029 2030 return false; 2031 } 2032 2033 unlisten () { 2034 this._node.removeEventListener(this._type, this._closure, this._options); 2035 } 2036} 2037 2038class HashAction { 2039 constructor (hash, add) { 2040 this.hash = hash; 2041 this.add = add; 2042 } 2043 2044 handle (hash, e) { 2045 if (this.hash === hash) this.add(e); 2046 } 2047} 2048 2049const DisclosureEvent = { 2050 DISCLOSE: ns.event('disclose'), 2051 CONCEAL: ns.event('conceal') 2052}; 2053 2054const DisclosureEmission = { 2055 RESET: ns.emission('disclosure', 'reset'), 2056 ADDED: ns.emission('disclosure', 'added'), 2057 RETRIEVE: ns.emission('disclosure', 'retrieve'), 2058 REMOVED: ns.emission('disclosure', 'removed'), 2059 GROUP: ns.emission('disclosure', 'group'), 2060 UNGROUP: ns.emission('disclosure', 'ungroup'), 2061 SPOTLIGHT: ns.emission('disclosure', 'spotlight') 2062}; 2063 2064class Disclosure extends Instance { 2065 constructor (type, selector, DisclosureButtonInstanceClass, disclosuresGroupInstanceClassName) { 2066 super(); 2067 this.type = type; 2068 this._selector = selector; 2069 this.DisclosureButtonInstanceClass = DisclosureButtonInstanceClass; 2070 this.disclosuresGroupInstanceClassName = disclosuresGroupInstanceClassName; 2071 this.modifier = this._selector + '--' + this.type.id; 2072 this._isPristine = true; 2073 this._isRetrievingPrimaries = false; 2074 this._hasRetrieved = false; 2075 this._primaryButtons = []; 2076 } 2077 2078 static get instanceClassName () { 2079 return 'Disclosure'; 2080 } 2081 2082 init () { 2083 this.addDescent(DisclosureEmission.RESET, this.reset.bind(this)); 2084 this.addDescent(DisclosureEmission.GROUP, this.update.bind(this)); 2085 this.addDescent(DisclosureEmission.UNGROUP, this.update.bind(this)); 2086 this.addAscent(DisclosureEmission.SPOTLIGHT, this.disclose.bind(this)); 2087 this.register(`[aria-controls="${this.id}"]`, this.DisclosureButtonInstanceClass); 2088 this.ascend(DisclosureEmission.ADDED); 2089 this.listenHash(this.id, this._spotlight.bind(this)); 2090 this.update(); 2091 } 2092 2093 get isEnabled () { return super.isEnabled; } 2094 2095 set isEnabled (value) { 2096 if (this.isEnabled === value) return; 2097 super.isEnabled = value; 2098 if (value) this.ascend(DisclosureEmission.ADDED); 2099 else this.ascend(DisclosureEmission.REMOVED); 2100 } 2101 2102 get isPristine () { 2103 return this._isPristine; 2104 } 2105 2106 get proxy () { 2107 const scope = this; 2108 const proxy = Object.assign(super.proxy, { 2109 disclose: scope.disclose.bind(scope), 2110 focus: scope.focus.bind(scope) 2111 }); 2112 2113 if (this.type.canConceal) proxy.conceal = scope.conceal.bind(scope); 2114 2115 const proxyAccessors = { 2116 get buttons () { 2117 return scope.buttons.map((button) => button.proxy); 2118 }, 2119 get group () { 2120 const group = scope.group; 2121 return group ? group.proxy : null; 2122 }, 2123 get isDisclosed () { 2124 return scope.isDisclosed; 2125 } 2126 }; 2127 2128 return completeAssign(proxy, proxyAccessors); 2129 } 2130 2131 get buttons () { 2132 return this.getRegisteredInstances(this.DisclosureButtonInstanceClass.instanceClassName); 2133 } 2134 2135 update () { 2136 this.getGroup(); 2137 this.retrievePrimaries(); 2138 } 2139 2140 getGroup () { 2141 if (!this.disclosuresGroupInstanceClassName) { 2142 this._group = null; 2143 return; 2144 } 2145 2146 const group = this.element.getAscendantInstance(this.disclosuresGroupInstanceClassName, this.constructor.instanceClassName); 2147 if (!group || !group.validate(this)) { 2148 this._group = null; 2149 return; 2150 } 2151 2152 this._group = group; 2153 } 2154 2155 get group () { 2156 return this._group; 2157 } 2158 2159 disclose (withhold) { 2160 if (this.isDisclosed === true || !this.isEnabled) return false; 2161 this._isPristine = false; 2162 this.isDisclosed = true; 2163 if (!withhold && this.group) this.group.current = this; 2164 return true; 2165 } 2166 2167 conceal (withhold, preventFocus = true) { 2168 if (this.isDisclosed === false) return false; 2169 if (!this.type.canConceal && this.group && this.group.current === this) return false; 2170 this.isDisclosed = false; 2171 if (!withhold && this.group && this.group.current === this) this.group.current = null; 2172 if (!preventFocus) this.focus(); 2173 if (!this._isPristine) this.descend(DisclosureEmission.RESET); 2174 return true; 2175 } 2176 2177 get isDisclosed () { 2178 return this._isDisclosed; 2179 } 2180 2181 set isDisclosed (value) { 2182 if (this._isDisclosed === value || (!this.isEnabled && value === true)) return; 2183 this.dispatch(value ? DisclosureEvent.DISCLOSE : DisclosureEvent.CONCEAL, this.type); 2184 this._isDisclosed = value; 2185 if (value) this.addClass(this.modifier); 2186 else this.removeClass(this.modifier); 2187 for (let i = 0; i < this.buttons.length; i++) this.buttons[i].apply(value); 2188 } 2189 2190 get isInitiallyDisclosed () { 2191 return this.primaryButtons.some(button => button.isInitiallyDisclosed); 2192 } 2193 2194 hasRetrieved () { 2195 return this._hasRetrieved; 2196 } 2197 2198 reset () {} 2199 2200 toggle (canDisclose) { 2201 if (!this.type.canConceal) this.disclose(); 2202 else { 2203 switch (true) { 2204 case !canDisclose: 2205 case this.isDisclosed: 2206 this.conceal(false, false); 2207 break; 2208 2209 default: 2210 this.disclose(); 2211 } 2212 } 2213 } 2214 2215 get buttonHasFocus () { 2216 return this.buttons.some((button) => { return button.hasFocus; }); 2217 } 2218 2219 get hasFocus () { 2220 if (super.hasFocus) return true; 2221 if (this.buttonHasFocus) return true; 2222 return this.querySelectorAll(':focus').length > 0; 2223 } 2224 2225 focus () { 2226 if (this._primaryButtons.length > 0) this._primaryButtons[0].focus(); 2227 } 2228 2229 get primaryButtons () { 2230 return this._primaryButtons; 2231 } 2232 2233 retrievePrimaries () { 2234 if (this._isRetrievingPrimaries) return; 2235 this._isRetrievingPrimaries = true; 2236 this.request(this._retrievePrimaries.bind(this)); 2237 } 2238 2239 _retrievePrimaries () { 2240 this._isRetrievingPrimaries = false; 2241 this._primaryButtons = this._electPrimaries(this.buttons); 2242 2243 if (this._hasRetrieved || this._primaryButtons.length === 0) return; 2244 this.retrieved(); 2245 this._hasRetrieved = true; 2246 2247 this.applyAbility(true); 2248 2249 if (this.group) { 2250 this.group.retrieve(); 2251 return; 2252 } 2253 2254 if (this._isPristine && this.isEnabled && !this.group) { 2255 switch (true) { 2256 case this.hash === this.id: 2257 this._spotlight(); 2258 break; 2259 2260 case this.isInitiallyDisclosed: 2261 this.disclose(); 2262 break; 2263 } 2264 } 2265 } 2266 2267 retrieved () {} 2268 2269 _spotlight () { 2270 this.disclose(); 2271 this.request(() => { this.ascend(DisclosureEmission.SPOTLIGHT); }); 2272 } 2273 2274 _electPrimaries (candidates) { 2275 return candidates.filter(button => button.canDisclose && !this.node.contains(button.node)); 2276 } 2277 2278 applyAbility (withhold = false) { 2279 const isEnabled = !this._primaryButtons.every(button => button.isDisabled); 2280 2281 if (this.isEnabled === isEnabled) return; 2282 2283 this.isEnabled = isEnabled; 2284 2285 if (withhold) return; 2286 2287 if (!this.isEnabled && this.isDisclosed) { 2288 if (this.group) this.ascend(DisclosureEmission.REMOVED); 2289 else if (this.type.canConceal) this.conceal(); 2290 } 2291 2292 if (this.isEnabled) { 2293 if (this.group) this.ascend(DisclosureEmission.ADDED); 2294 if (this.hash === this.id) { 2295 this._spotlight(); 2296 } 2297 } 2298 } 2299 2300 dispose () { 2301 this._group = null; 2302 this._primaryButtons = null; 2303 super.dispose(); 2304 this.ascend(DisclosureEmission.REMOVED); 2305 } 2306} 2307 2308class DisclosureButton extends Instance { 2309 constructor (type) { 2310 super(); 2311 this.type = type; 2312 this.attributeName = type.ariaState ? 'aria-' + type.id : ns.attr(type.id); 2313 this._canDisclose = false; 2314 } 2315 2316 static get instanceClassName () { 2317 return 'DisclosureButton'; 2318 } 2319 2320 get isPrimary () { 2321 return this.registration.creator.primaryButtons.includes(this); 2322 } 2323 2324 get canDisclose () { 2325 return this._canDisclose; 2326 } 2327 2328 get isDisabled () { 2329 return this.type.canDisable && this.hasAttribute('disabled'); 2330 } 2331 2332 init () { 2333 this._canDisclose = this.hasAttribute(this.attributeName); 2334 this._isInitiallyDisclosed = this.isDisclosed; 2335 this._isContained = this.registration.creator.node.contains(this.node); 2336 this.controlsId = this.getAttribute('aria-controls'); 2337 this.registration.creator.retrievePrimaries(); 2338 this.listenClick(); 2339 } 2340 2341 get proxy () { 2342 const scope = this; 2343 return Object.assign(super.proxy, { 2344 focus: scope.focus.bind(scope) 2345 }); 2346 } 2347 2348 handleClick (e) { 2349 if (this.registration.creator) this.registration.creator.toggle(this.canDisclose); 2350 } 2351 2352 mutate (attributeNames) { 2353 this._canDisclose = this.hasAttribute(this.attributeName); 2354 this.registration.creator.applyAbility(); 2355 if (!this._isApplying && this.isPrimary && attributeNames.indexOf(this.attributeName) > -1 && this.registration.creator) { 2356 if (this.isDisclosed) this.registration.creator.disclose(); 2357 else if (this.type.canConceal) this.registration.creator.conceal(); 2358 } 2359 } 2360 2361 apply (value) { 2362 if (!this.canDisclose) return; 2363 this._isApplying = true; 2364 this.setAttribute(this.attributeName, value); 2365 this.request(() => { this._isApplying = false; }); 2366 } 2367 2368 get isDisclosed () { 2369 return this.getAttribute(this.attributeName) === 'true'; 2370 } 2371 2372 get isInitiallyDisclosed () { 2373 return this._isInitiallyDisclosed; 2374 } 2375 2376 focus () { 2377 super.focus(); 2378 this.scrollIntoView(); 2379 } 2380 2381 measure (rect) { 2382 const buttonRect = this.rect; 2383 this._dx = rect.x - buttonRect.x; 2384 this._dy = rect.y - buttonRect.y; 2385 } 2386 2387 get dx () { 2388 return this._dx; 2389 } 2390 2391 get dy () { 2392 return this._dy; 2393 } 2394} 2395 2396const DisclosureSelector = { 2397 PREVENT_CONCEAL: ns.attr.selector('prevent-conceal'), 2398 GROUP: ns.attr('group') 2399}; 2400 2401class DisclosuresGroup extends Instance { 2402 constructor (disclosureInstanceClassName, jsAttribute) { 2403 super(jsAttribute); 2404 this.disclosureInstanceClassName = disclosureInstanceClassName; 2405 this._members = []; 2406 this._index = -1; 2407 this._isRetrieving = false; 2408 this._hasRetrieved = false; 2409 this._isGrouped = true; 2410 } 2411 2412 static get instanceClassName () { 2413 return 'DisclosuresGroup'; 2414 } 2415 2416 init () { 2417 this.addAscent(DisclosureEmission.ADDED, this.update.bind(this)); 2418 this.addAscent(DisclosureEmission.RETRIEVE, this.retrieve.bind(this)); 2419 this.addAscent(DisclosureEmission.REMOVED, this.update.bind(this)); 2420 this.descend(DisclosureEmission.GROUP); 2421 this._isGrouped = this.getAttribute(DisclosureSelector.GROUP) !== 'false'; 2422 this.update(); 2423 } 2424 2425 get proxy () { 2426 const scope = this; 2427 2428 const proxyAccessors = { 2429 set index (value) { 2430 scope.index = value; 2431 }, 2432 get index () { 2433 return scope.index; 2434 }, 2435 get length () { 2436 return scope.length; 2437 }, 2438 get current () { 2439 const current = scope.current; 2440 return current ? current.proxy : null; 2441 }, 2442 get members () { 2443 return scope.members.map((member) => member.proxy); 2444 }, 2445 get hasFocus () { 2446 return scope.hasFocus; 2447 }, 2448 set isGrouped (value) { 2449 scope.isGrouped = value; 2450 }, 2451 get isGrouped () { 2452 return scope.isGrouped; 2453 } 2454 }; 2455 2456 return completeAssign(super.proxy, proxyAccessors); 2457 } 2458 2459 validate (member) { 2460 return true; 2461 } 2462 2463 getMembers () { 2464 const members = this.element.getDescendantInstances(this.disclosureInstanceClassName, this.constructor.instanceClassName, true); 2465 this._members = members.filter(this.validate.bind(this)).filter(member => member.isEnabled); 2466 const invalids = members.filter(member => !this._members.includes(member)); 2467 invalids.forEach(invalid => invalid.conceal()); 2468 } 2469 2470 retrieve (bypassPrevention = false) { 2471 if (this._isRetrieving || (this._hasRetrieved && !bypassPrevention)) return; 2472 this._isRetrieving = true; 2473 this.request(this._retrieve.bind(this)); 2474 } 2475 2476 _retrieve () { 2477 this.getMembers(); 2478 this._isRetrieving = false; 2479 this._hasRetrieved = true; 2480 if (this.hash) { 2481 for (let i = 0; i < this.length; i++) { 2482 const member = this.members[i]; 2483 if (this.hash === member.id) { 2484 this.index = i; 2485 this.request(() => { this.ascend(DisclosureEmission.SPOTLIGHT); }); 2486 return i; 2487 } 2488 } 2489 } 2490 2491 for (let i = 0; i < this.length; i++) { 2492 const member = this.members[i]; 2493 if (member.isInitiallyDisclosed) { 2494 this.index = i; 2495 return i; 2496 } 2497 } 2498 2499 return this.getIndex(); 2500 } 2501 2502 update () { 2503 this.getMembers(); 2504 if (this._hasRetrieved) this.getIndex(); 2505 } 2506 2507 get members () { 2508 return this._members; 2509 } 2510 2511 get length () { 2512 return this.members ? this.members.length : 0; 2513 } 2514 2515 getIndex (defaultIndex = -1) { 2516 this._index = undefined; 2517 let index = defaultIndex; 2518 for (let i = 0; i < this.length; i++) { 2519 if (this.members[i].isDisclosed) { 2520 index = i; 2521 break; 2522 } 2523 } 2524 2525 this.index = index; 2526 return index; 2527 } 2528 2529 get index () { 2530 return this._index; 2531 } 2532 2533 set index (value) { 2534 if (value < -1 || value >= this.length || value === this._index) return; 2535 this._index = value; 2536 for (let i = 0; i < this.length; i++) { 2537 const member = this.members[i]; 2538 if (value === i) { 2539 if (!member.isDisclosed) member.disclose(true); 2540 } else { 2541 if ((this.isGrouped || !this.canUngroup) && member.isDisclosed) member.conceal(true); 2542 } 2543 } 2544 this.apply(); 2545 } 2546 2547 get current () { 2548 if (this._index === -1 || isNaN(this._index)) return null; 2549 return this._members[this._index] || null; 2550 } 2551 2552 set current (member) { 2553 this.index = this.members.indexOf(member); 2554 } 2555 2556 get hasFocus () { 2557 const current = this.current; 2558 if (current) return current.hasFocus; 2559 return false; 2560 } 2561 2562 set isGrouped (value) { 2563 const isGrouped = !!value; 2564 if (this._isGrouped === isGrouped) return; 2565 this._isGrouped = isGrouped; 2566 this.setAttribute(DisclosureSelector.GROUP, !!value); 2567 this.update(); 2568 } 2569 2570 get isGrouped () { 2571 return this._isGrouped; 2572 } 2573 2574 get canUngroup () { 2575 return false; 2576 } 2577 2578 mutate (attributesNames) { 2579 if (attributesNames.includes(DisclosureSelector.GROUP)) { 2580 this.isGrouped = this.getAttribute(DisclosureSelector.GROUP) !== 'false'; 2581 } 2582 } 2583 2584 apply () {} 2585 2586 dispose () { 2587 super.dispose(); 2588 this.descend(DisclosureEmission.UNGROUP); 2589 this._members = null; 2590 } 2591} 2592 2593const DisclosureType = { 2594 EXPAND: { 2595 id: 'expanded', 2596 ariaState: true,
vendor: 13,495 bytes, lines 2597-3137
2597 ariaControls: true, 2598 canConceal: true, 2599 canDisable: true 2600 }, 2601 SELECT: { 2602 id: 'selected', 2603 ariaState: true, 2604 ariaControls: true, 2605 canConceal: false, 2606 canDisable: true 2607 }, 2608 OPENED: { 2609 id: 'opened', 2610 ariaState: false, 2611 ariaControls: true, 2612 canConceal: true, 2613 canDisable: false 2614 } 2615}; 2616 2617class CollapseButton extends DisclosureButton { 2618 constructor () { 2619 super(DisclosureType.EXPAND); 2620 } 2621 2622 static get instanceClassName () { 2623 return 'CollapseButton'; 2624 } 2625} 2626 2627const CollapseSelector = { 2628 COLLAPSE: ns.selector('collapse'), 2629 COLLAPSING: ns.selector('collapsing') 2630}; 2631 2632/** 2633 * Tab coorespond au panel d'un élement Tabs (tab panel) 2634 * Tab étend disclosure qui ajoute/enleve le modifier --selected, 2635 * et ajoute/eleve l'attribut hidden, sur le panel 2636 */ 2637class Collapse extends Disclosure { 2638 constructor () { 2639 super(DisclosureType.EXPAND, CollapseSelector.COLLAPSE, CollapseButton, 'CollapsesGroup'); 2640 } 2641 2642 static get instanceClassName () { 2643 return 'Collapse'; 2644 } 2645 2646 init () { 2647 super.init(); 2648 this.listen('transitionend', this.endCollapsing.bind(this)); 2649 } 2650 2651 endCollapsing (e) { 2652 if (!this._isCollpasing) return; 2653 if (this._timeout) clearTimeout(this._timeout); 2654 this._timeout = null; 2655 this._isCollpasing = false; 2656 this.removeClass(CollapseSelector.COLLAPSING); 2657 if (!this.isDisclosed) { 2658 if (this.isLegacy) this.style.maxHeight = ''; 2659 } 2660 } 2661 2662 unbound () { 2663 if (this.isLegacy) this.style.maxHeight = 'none'; 2664 } 2665 2666 disclose (withhold) { 2667 if (this.isDisclosed === true || !this.isEnabled) return false; 2668 this.unbound(); 2669 this.collapsing(() => super.disclose(withhold)); 2670 } 2671 2672 conceal (withhold, preventFocus) { 2673 if (this.isDisclosed === false) return false; 2674 this.collapsing(() => super.conceal(withhold, preventFocus)); 2675 } 2676 2677 collapsing (request) { 2678 this._isCollpasing = true; 2679 if (this._timeout) clearTimeout(this._timeout); 2680 this.addClass(CollapseSelector.COLLAPSING); 2681 this.adjust(); 2682 this.request(() => { 2683 request(); 2684 this._timeout = setTimeout(this.endCollapsing.bind(this), 500); 2685 }); 2686 } 2687 2688 adjust () { 2689 this.setProperty('--collapser', 'none'); 2690 const height = this.node.offsetHeight; 2691 this.setProperty('--collapse', -height + 'px'); 2692 this.setProperty('--collapser', ''); 2693 } 2694 2695 reset () { 2696 if (!this.isPristine) this.isDisclosed = false; 2697 } 2698 2699 _electPrimaries (candidates) { 2700 const primary = this.element.parent.instances.map(instance => instance.collapsePrimary).filter(button => button !== undefined && candidates.indexOf(button) > -1); 2701 if (primary.length === 1) return primary; 2702 candidates = super._electPrimaries(candidates); 2703 if (candidates.length === 1) return candidates; 2704 const before = candidates.filter(candidate => candidate.dy >= 0); 2705 if (before.length > 0) candidates = before; 2706 if (candidates.length === 1) return candidates; 2707 const min = Math.min(...candidates.map(candidate => candidate.dy)); 2708 const mins = candidates.filter(candidate => candidate.dy === min); 2709 if (mins.length > 0) candidates = mins; 2710 if (candidates.length === 1) return candidates; 2711 candidates.sort((a, b) => Math.abs(b.dx) - Math.abs(a.dx)); 2712 return candidates; 2713 } 2714} 2715 2716class CollapsesGroup extends DisclosuresGroup { 2717 constructor () { 2718 super('Collapse'); 2719 } 2720 2721 static get instanceClassName () { 2722 return 'CollapsesGroup'; 2723 } 2724 2725 get canUngroup () { 2726 return true; 2727 } 2728} 2729 2730const EquisizedEmission = { 2731 CHANGE: ns('equisized') 2732}; 2733 2734class Equisized extends Instance { 2735 static get instanceClassName () { 2736 return 'Equisized'; 2737 } 2738 2739 init () { 2740 this.ascend(EquisizedEmission.CHANGE); 2741 } 2742 2743 measure () { 2744 if (this.isLegacy) this.style.width = 'auto'; 2745 return this.getRect().width; 2746 } 2747 2748 adjust (width) { 2749 if (this.isLegacy) this.style.width = `${width}px`; 2750 } 2751 2752 dispose () { 2753 this.ascend(EquisizedEmission.CHANGE); 2754 } 2755} 2756 2757class EquisizedsGroup extends Instance { 2758 static get instanceClassName () { 2759 return 'EquisizedsGroup'; 2760 } 2761 2762 init () { 2763 this.isResizing = true; 2764 this.isLoading = true; 2765 this.addAscent(EquisizedEmission.CHANGE, this.resize.bind(this)); 2766 } 2767 2768 load () { 2769 this.resize(); 2770 } 2771 2772 resize () { 2773 const equisizeds = this.element.getDescendantInstances('Equisized'); 2774 if (!this.isLegacy) this.style.setProperty('--equisized-width', 'auto'); 2775 2776 const width = Math.max(...equisizeds.map(equisized => equisized.measure())); 2777 if (this.isLegacy) equisizeds.forEach(equisized => equisized.adjust(width)); 2778 else this.style.setProperty('--equisized-width', `${width}px`); 2779 } 2780} 2781 2782const ToggleEvent = { 2783 TOGGLE: ns.event('toggle') 2784}; 2785 2786class Toggle extends Instance { 2787 static get instanceClassName () { 2788 return 'Toggle'; 2789 } 2790 2791 init () { 2792 this.pressed = this.pressed === 'true'; 2793 this.listenClick(); 2794 } 2795 2796 handleClick () { 2797 this.toggle(); 2798 } 2799 2800 toggle () { 2801 this.pressed = this.pressed !== 'true'; 2802 } 2803 2804 get pressed () { 2805 return this.getAttribute('aria-pressed'); 2806 } 2807 2808 set pressed (value) { 2809 this.setAttribute('aria-pressed', value ? 'true' : 'false'); 2810 this.dispatch(ToggleEvent.TOGGLE, value); 2811 } 2812 2813 get proxy () { 2814 const scope = this; 2815 const proxy = Object.assign(super.proxy, { 2816 toggle: scope.toggle.bind(scope) 2817 }); 2818 2819 const proxyAccessors = { 2820 get pressed () { 2821 return scope.pressed; 2822 }, 2823 set pressed (value) { 2824 scope.pressed = value; 2825 } 2826 }; 2827 2828 return completeAssign(proxy, proxyAccessors); 2829 } 2830} 2831 2832const RootSelector = { 2833 ROOT: ':root' 2834}; 2835 2836const setAttributes = (el, attrs) => { 2837 Object.keys(attrs).forEach(key => el.setAttribute(key, attrs[key])); 2838}; 2839 2840class InjectSvg extends Instance { 2841 static get instanceClassName () { 2842 return 'InjectSvg'; 2843 } 2844 2845 init () { 2846 if (this.node) { 2847 this.img = this.node.querySelector('img'); 2848 } 2849 2850 if (!this.isLegacy) { 2851 this.replace(); 2852 } 2853 } 2854 2855 get proxy () { 2856 const scope = this; 2857 return Object.assign(super.proxy, { 2858 replace: scope.replace.bind(scope), 2859 restore: scope.restore.bind(scope) 2860 }); 2861 } 2862 2863 fetch () { 2864 if (this.img) { 2865 this.imgID = this.img.getAttribute('id'); 2866 this.imgClass = this.img.getAttribute('class'); 2867 this.imgURL = this.img.getAttribute('src'); 2868 2869 fetch(this.imgURL) 2870 .then(data => data.text()) 2871 .then(response => { 2872 const parser = new DOMParser(); 2873 const xmlDoc = parser.parseFromString(response, 'text/html'); 2874 this.svg = xmlDoc.querySelector('svg'); 2875 2876 if (this.svg) { 2877 this.replace(); 2878 } 2879 }); 2880 } 2881 } 2882 2883 replace () { 2884 if (!this.svg) { 2885 this.fetch(); 2886 return; 2887 } 2888 2889 if (this.imgID && typeof this.imgID !== 'undefined') { 2890 this.svg.setAttribute('id', this.imgID); 2891 } 2892 2893 // gestion de la dépréciation 2894 let name = this.imgURL.match(/[ \w-]+\./)[0]; 2895 if (name) { 2896 name = name.slice(0, -1); 2897 2898 if (['dark', 'light', 'system'].includes(name)) { 2899 this.svg.innerHTML = this.svg.innerHTML.replaceAll('id="artwork-', `id="${name}-artwork-`); 2900 this.svg.innerHTML = this.svg.innerHTML.replaceAll('"#artwork-', `"#${name}-artwork-`); 2901 } 2902 } 2903 2904 if (this.imgClass && typeof this.imgClass !== 'undefined') { 2905 this.svg.setAttribute('class', this.imgClass); 2906 } 2907 2908 if (this.svg.hasAttribute('xmlns:a')) { 2909 this.svg.removeAttribute('xmlns:a'); 2910 } 2911 2912 this.node.setAttribute('data-fr-inject-svg', true); 2913 const svgAttributes = { 2914 'aria-hidden': true, 2915 focusable: false 2916 }; 2917 setAttributes(this.svg, svgAttributes); 2918 this.node.replaceChild(this.svg, this.img); 2919 } 2920 2921 restore () { 2922 if (this.img && this.svg) { 2923 this.node.setAttribute('data-fr-inject-svg', false); 2924 this.node.replaceChild(this.img, this.svg); 2925 } 2926 } 2927} 2928 2929const InjectSvgSelector = { 2930 INJECT_SVG: `[${ns.attr('inject-svg')}]` 2931}; 2932 2933class Artwork extends Instance { 2934 static get instanceClassName () { 2935 return 'Artwork'; 2936 } 2937 2938 init () { 2939 if (this.isLegacy) { 2940 this.replace(); 2941 } 2942 } 2943 2944 get proxy () { 2945 const scope = this; 2946 return Object.assign(super.proxy, { 2947 replace: scope.replace.bind(scope) 2948 }); 2949 } 2950 2951 fetch () { 2952 this.xlink = this.node.getAttribute('href'); 2953 const splitUrl = this.xlink.split('#'); 2954 this.svgUrl = splitUrl[0]; 2955 this.svgName = splitUrl[1]; 2956 2957 const xhr = new XMLHttpRequest(); 2958 xhr.onload = () => { 2959 const parser = new DOMParser(); 2960 const xmlDoc = parser.parseFromString(xhr.responseText, 'text/html'); 2961 this.realSvgContent = xmlDoc.getElementById(this.svgName); 2962 if (this.realSvgContent) { 2963 if (this.realSvgContent.tagName === 'symbol') { 2964 this.use = xmlDoc.querySelector('use[href="#' + this.svgName + '"]'); 2965 if (this.use) this.node.parentNode.insertBefore(this.use, this.node); 2966 } else { 2967 // deprecated svg structure 2968 this.realSvgContent.classList.add(this.node.classList); 2969 } 2970 2971 this.replace(); 2972 } 2973 }; 2974 xhr.open('GET', this.svgUrl); 2975 xhr.send(); 2976 } 2977 2978 replace () { 2979 if (!this.realSvgContent) { 2980 this.fetch(); 2981 return; 2982 } 2983 2984 this.node.parentNode.replaceChild(this.realSvgContent, this.node); 2985 } 2986} 2987 2988const ArtworkSelector = { 2989 ARTWORK_USE: `${ns.selector('artwork')} use` 2990}; 2991 2992const AssessSelector = { 2993 ASSESS_FILE: `${ns.attr.selector('assess-file')}`, 2994 DETAIL: `${ns.attr.selector('assess-file')} [class$="__detail"], ${ns.attr.selector('assess-file')} [class*="__detail "]` 2995}; 2996 2997const AssessEmission = { 2998 UPDATE: ns.emission('assess', 'update'), 2999 ADDED: ns.emission('assess', 'added') 3000}; 3001 3002class AssessFile extends Instance { 3003 static get instanceClassName () { 3004 return 'AssessFile'; 3005 } 3006 3007 init () { 3008 this.lang = this.getLang(this.node); 3009 this.href = this.getAttribute('href'); 3010 this.hreflang = this.getAttribute('hreflang'); 3011 this.file = {}; 3012 this.gather(); 3013 this.addAscent(AssessEmission.ADDED, this.update.bind(this)); 3014 this.addDescent(AssessEmission.ADDED, this.update.bind(this)); 3015 } 3016 3017 getFileLength () { 3018 if (this.href === undefined) { 3019 this.length = -1; 3020 return; 3021 } 3022 3023 fetch(this.href, { method: 'HEAD', mode: 'cors' }).then(response => { 3024 this.length = response.headers.get('content-length') || -1; 3025 if (this.length === -1) { 3026 inspector.warn('File size unknown: ' + this.href + '\nUnable to get HTTP header: "content-length"'); 3027 } 3028 this.gather(); 3029 }); 3030 } 3031 3032 mutate (attributeNames) { 3033 if (attributeNames.indexOf('href') !== -1) { 3034 this.href = this.getAttribute('href'); 3035 this.getFileLength(); 3036 } 3037 3038 if (attributeNames.indexOf('hreflang') !== -1) { 3039 this.hreflang = this.getAttribute('hreflang'); 3040 this.gather(); 3041 } 3042 } 3043 3044 gather () { 3045 // TODO V2: implémenter async 3046 if (this.isLegacy) this.length = -1; 3047 3048 if (!this.length) { 3049 this.getFileLength(); 3050 return; 3051 } 3052 3053 this.details = []; 3054 3055 if (this.href) { 3056 const extension = this.parseExtension(this.href); 3057 if (extension) this.details.push(extension.toUpperCase()); 3058 } 3059 3060 if (this.length !== -1) { 3061 this.details.push(this.bytesToSize(this.length)); 3062 } 3063 3064 if (this.hreflang) { 3065 this.details.push(this.getLangDisplayName(this.hreflang)); 3066 } 3067 3068 this.update(); 3069 } 3070 3071 update () { 3072 if (!this.details) return; 3073 this.descend(AssessEmission.UPDATE, this.details); 3074 this.ascend(AssessEmission.UPDATE, this.details); 3075 } 3076 3077 getLang (elem) { 3078 // todo: ajouter un listener global de changement de langue 3079 if (elem.lang) return elem.lang; 3080 if (document.documentElement === elem) return window.navigator.language; 3081 return this.getLang(elem.parentElement); 3082 } 3083 3084 parseExtension (url) { 3085 const regexExtension = /\.(\w{1,9})(?:$|[?#])/; 3086 return url.match(regexExtension)[0].replace('.', ''); 3087 } 3088 3089 getLangDisplayName (locale) { 3090 if (this.isLegacy) return locale; 3091 const displayNames = new Intl.DisplayNames([this.lang], { type: 'language' }); 3092 const name = displayNames.of(locale); 3093 return name.charAt(0).toUpperCase() + name.slice(1); 3094 } 3095 3096 bytesToSize (bytes) { 3097 if (bytes === -1) return null; 3098 3099 let sizeUnits = ['octets', 'ko', 'Mo', 'Go', 'To']; 3100 if (this.getAttribute(ns.attr('assess-file')) === 'bytes') { 3101 sizeUnits = ['bytes', 'KB', 'MB', 'GB', 'TB']; 3102 } 3103 3104 const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1000)), 10); 3105 if (i === 0) return `${bytes} ${sizeUnits[i]}`; 3106 3107 const size = bytes / (1000 ** i); 3108 const roundedSize = Math.round((size + Number.EPSILON) * 100) / 100; // arrondi a 2 décimal 3109 const stringSize = String(roundedSize).replace('.', ','); 3110 3111 return `${stringSize} ${sizeUnits[i]}`; 3112 } 3113} 3114 3115class AssessDetail extends Instance { 3116 static get instanceClassName () { 3117 return 'AssessDetail'; 3118 } 3119 3120 init () { 3121 this.addDescent(AssessEmission.UPDATE, this.update.bind(this)); 3122 this.ascend(AssessEmission.ADDED); 3123 } 3124 3125 update (details) { 3126 this.node.innerHTML = details.join(' - '); 3127 } 3128} 3129 3130const ratiosImg = ['32x9', '16x9', '3x2', '4x3', '1x1', '3x4', '2x3']; 3131const ratiosVid = ['16x9', '4x3', '1x1']; 3132 3133const ratioSelector = (name, modifiers) => { 3134 return modifiers.map(modifier => ns.selector(`${name}--${modifier}`)).join(','); 3135}; 3136 3137const deprecatedRatioSelector = `${ns.selector('responsive-img')}, ${ratioSelector('responsive-img', ratiosImg)}, ${ns.selector('responsive-vid')}
3137, ${ratioSelector('responsive-vid', ratiosVid)}`; 3138 3139const RatioSelector = { 3140 RATIO: `${ns.selector('ratio')}, ${ratioSelector('ratio', ratiosImg)}, ${deprecatedRatioSelector}` 3141}; 3142 3143const api = window[config.namespace]; 3144 3145class Ratio extends Instance { 3146 static get instanceClassName () { 3147 return 'Ratio'; 3148 } 3149 3150 init () { 3151 if (!api.internals.support.supportAspectRatio()) { 3152 this.ratio = 16 / 9; 3153 for (const className in this.classNames) { 3154 if (this.registration.selector.indexOf(this.classNames[className]) > 0) { 3155 const ratio = this.classNames[className].split('ratio-'); 3156 if (ratio[1]) { 3157 this.ratio = ratio[1].split('x')[0] / ratio[1].split('x')[1]; 3158 } 3159 } 3160 } 3161 this.isRendering = true; 3162 this.update(); 3163 } 3164 } 3165 3166 render () { 3167 const width = this.getRect().width; 3168 if (width !== this.currentWidth) this.update(); 3169 } 3170 3171 update () { 3172 this.currentWidth = this.getRect().width; 3173 this.style.height = this.currentWidth / this.ratio + 'px'; 3174 } 3175} 3176 3177const PlaceSelector = { 3178 TOP: ns.selector('placement--top'), 3179 RIGHT: ns.selector('placement--right'), 3180 BOTTOM: ns.selector('placement--bottom'), 3181 LEFT: ns.selector('placement--left') 3182}; 3183 3184const AlignSelector = { 3185 START: ns.selector('placement--start'), 3186 CENTER: ns.selector('placement--center'), 3187 END: ns.selector('placement--end') 3188}; 3189 3190const PlacementPosition = { 3191 TOP: 'place_top', 3192 RIGHT: 'place_right', 3193 BOTTOM: 'place_bottom', 3194 LEFT: 'place_left' 3195}; 3196 3197const PlacementAlign = { 3198 START: 'align_start', 3199 CENTER: 'align_center', 3200 END: 'align_end' 3201}; 3202 3203const PlacementMode = { 3204 AUTO: 'placement_auto', 3205 MANUAL: 'placement_manual' 3206}; 3207 3208class Placement extends Instance { 3209 constructor (mode = PlacementMode.AUTO, places = [PlacementPosition.BOTTOM, PlacementPosition.TOP, PlacementPosition.LEFT, PlacementPosition.RIGHT], aligns = [PlacementAlign.CENTER, PlacementAlign.START, PlacementAlign.END], safeAreaMargin = 16) { 3210 super(); 3211 this._mode = mode; 3212 this._places = places; 3213 this._aligns = aligns; 3214 this._safeAreaMargin = safeAreaMargin; 3215 this._isShown = false; 3216 } 3217 3218 static get instanceClassName () { 3219 return 'Placement'; 3220 } 3221 3222 init () { 3223 this.isResizing = true; 3224 } 3225 3226 get proxy () { 3227 const scope = this; 3228 const proxy = Object.assign(super.proxy, { 3229 show: scope.show.bind(scope), 3230 hide: scope.hide.bind(scope) 3231 }); 3232 3233 const proxyAccessors = { 3234 get mode () { 3235 return scope.mode; 3236 }, 3237 set mode (value) { 3238 scope.mode = value; 3239 }, 3240 get place () { 3241 return scope.place; 3242 }, 3243 set place (value) { 3244 scope.place = value; 3245 }, 3246 get align () { 3247 return scope.align; 3248 }, 3249 set align (value) { 3250 scope.align = value; 3251 }, 3252 get isShown () { 3253 return scope.isShown; 3254 }, 3255 set isShown (value) { 3256 scope.isShown = value; 3257 } 3258 }; 3259 3260 return completeAssign(proxy, proxyAccessors); 3261 } 3262 3263 get mode () { 3264 return this._mode; 3265 } 3266 3267 set mode (value) { 3268 this._mode = value; 3269 } 3270 3271 get place () { 3272 return this._place; 3273 } 3274 3275 set place (value) { 3276 if (this._place === value) return; 3277 switch (this._place) { 3278 case PlacementPosition.TOP: 3279 this.removeClass(PlaceSelector.TOP); 3280 break; 3281 3282 case PlacementPosition.RIGHT: 3283 this.removeClass(PlaceSelector.RIGHT); 3284 break; 3285 3286 case PlacementPosition.BOTTOM: 3287 this.removeClass(PlaceSelector.BOTTOM); 3288 break; 3289 3290 case PlacementPosition.LEFT: 3291 this.removeClass(PlaceSelector.LEFT); 3292 break; 3293 } 3294 this._place = value; 3295 switch (this._place) { 3296 case PlacementPosition.TOP: 3297 this.addClass(PlaceSelector.TOP); 3298 break; 3299 3300 case PlacementPosition.RIGHT: 3301 this.addClass(PlaceSelector.RIGHT); 3302 break; 3303 3304 case PlacementPosition.BOTTOM: 3305 this.addClass(PlaceSelector.BOTTOM); 3306 break; 3307 3308 case PlacementPosition.LEFT: 3309 this.addClass(PlaceSelector.LEFT); 3310 break; 3311 } 3312 } 3313 3314 get align () { 3315 return this._align; 3316 } 3317 3318 set align (value) { 3319 if (this._align === value) return; 3320 switch (this._align) { 3321 case PlacementAlign.START: 3322 this.removeClass(AlignSelector.START); 3323 break; 3324 3325 case PlacementAlign.CENTER: 3326 this.removeClass(AlignSelector.CENTER); 3327 break; 3328 3329 case PlacementAlign.END: 3330 this.removeClass(AlignSelector.END); 3331 break; 3332 } 3333 this._align = value; 3334 switch (this._align) { 3335 case PlacementAlign.START: 3336 this.addClass(AlignSelector.START); 3337 break; 3338 3339 case PlacementAlign.CENTER: 3340 this.addClass(AlignSelector.CENTER); 3341 break; 3342 3343 case PlacementAlign.END: 3344 this.addClass(AlignSelector.END); 3345 break; 3346 } 3347 } 3348 3349 show () { 3350 this.isShown = true; 3351 } 3352 3353 hide () { 3354 this.isShown = false;
3355 } 3356 3357 get isShown () { 3358 return this._isShown; 3359 } 3360 3361 set isShown (value) { 3362 if (this._isShown === value || !this.isEnabled) return; 3363 this.isRendering = value; 3364 this._isShown = value; 3365 } 3366 3367 setReferent (referent) { 3368 this._referent = referent; 3369 } 3370 3371 resize () { 3372 this.safeArea = { 3373 top: this._safeAreaMargin, 3374 right: window.innerWidth - this._safeAreaMargin, 3375 bottom: window.innerHeight - this._safeAreaMargin, 3376 left: this._safeAreaMargin, 3377 center: window.innerWidth * 0.5, 3378 middle: window.innerHeight * 0.5 3379 }; 3380 } 3381 3382 render () { 3383 if (!this._referent) return; 3384 this.rect = this.getRect(); 3385 this.referentRect = this._referent.getRect(); 3386 3387 if (this.mode === PlacementMode.AUTO) { 3388 this.place = this.getPlace(); 3389 switch (this.place) { 3390 case PlacementPosition.TOP: 3391 case PlacementPosition.BOTTOM: 3392 this.align = this.getHorizontalAlign(); 3393 break; 3394 3395 case PlacementPosition.LEFT: 3396 case PlacementPosition.RIGHT: 3397 this.align = this.getVerticalAlign(); 3398 } 3399 } 3400 3401 let x, y; 3402 3403 switch (this.place) { 3404 case PlacementPosition.TOP: 3405 y = this.referentRect.top - this.rect.height; 3406 break; 3407 3408 case PlacementPosition.RIGHT: 3409 x = this.referentRect.right; 3410 break; 3411 3412 case PlacementPosition.BOTTOM: 3413 y = this.referentRect.bottom; 3414 break; 3415 3416 case PlacementPosition.LEFT: 3417 x = this.referentRect.left - this.rect.width; 3418 break; 3419 } 3420 3421 switch (this.place) { 3422 case PlacementPosition.TOP: 3423 case PlacementPosition.BOTTOM: 3424 switch (this.align) { 3425 case PlacementAlign.CENTER: 3426 x = this.referentRect.center - this.rect.width * 0.5; 3427 break; 3428 3429 case PlacementAlign.START: 3430 x = this.referentRect.left; 3431 break; 3432 3433 case PlacementAlign.END: 3434 x = this.referentRect.right - this.rect.width; 3435 break; 3436 } 3437 break; 3438 3439 case PlacementPosition.RIGHT: 3440 case PlacementPosition.LEFT: 3441 switch (this.align) { 3442 case PlacementAlign.CENTER: 3443 y = this.referentRect.middle - this.rect.height * 0.5; 3444 break; 3445 3446 case PlacementAlign.START: 3447 y = this.referentRect.top; 3448 break; 3449 3450 case PlacementAlign.END: 3451 y = this.referentRect.bottom - this.rect.height; 3452 break; 3453 } 3454 break; 3455 } 3456 3457 if (this._x !== x || this._y !== y) { 3458 this._x = (x + 0.5) | 0; 3459 this._y = (y + 0.5) | 0; 3460 this.node.style.transform = `translate(${this._x}px,${this._y}px)`; 3461 } 3462 } 3463 3464 getPlace () { 3465 for (const place of this._places) { 3466 switch (place) { 3467 case PlacementPosition.TOP: 3468 if (this.referentRect.top - this.rect.height > this.safeArea.top) return PlacementPosition.TOP; 3469 break; 3470 3471 case PlacementPosition.RIGHT: 3472 if (this.referentRect.right + this.rect.width < this.safeArea.right) return PlacementPosition.RIGHT; 3473 break; 3474 3475 case PlacementPosition.BOTTOM: 3476 if (this.referentRect.bottom + this.rect.height < this.safeArea.bottom) return PlacementPosition.BOTTOM; 3477 break; 3478 3479 case PlacementPosition.LEFT: 3480 if (this.referentRect.left - this.rect.width > this.safeArea.left) return PlacementPosition.LEFT; 3481 break; 3482 } 3483 } 3484 3485 return this._places[0]; 3486 } 3487 3488 getHorizontalAlign () { 3489 for (const align of this._aligns) { 3490 switch (align) { 3491 case PlacementAlign.CENTER: 3492 if (this.referentRect.center - this.rect.width * 0.5 > this.safeArea.left && this.referentRect.center + this.rect.width * 0.5 < this.safeArea.right) return PlacementAlign.CENTER; 3493 break; 3494 3495 case PlacementAlign.START: 3496 if (this.referentRect.left + this.rect.width < this.safeArea.right) return PlacementAlign.START; 3497 break; 3498 3499 case PlacementAlign.END: 3500 if (this.referentRect.right - this.rect.width > this.safeArea.left) return PlacementAlign.END; 3501 break; 3502 } 3503 } 3504 3505 return this._aligns[0]; 3506 } 3507 3508 getVerticalAlign () {
vendor: 11,752 bytes, lines 3509-3936
3509 for (const align of this._aligns) { 3510 switch (align) { 3511 case PlacementAlign.CENTER: 3512 if (this.referentRect.middle - this.rect.height * 0.5 > this.safeArea.top && this.referentRect.middle + this.rect.height * 0.5 < this.safeArea.bottom) return PlacementAlign.CENTER; 3513 break; 3514 3515 case PlacementAlign.START: 3516 if (this.referentRect.top + this.rect.height < this.safeArea.bottom) return PlacementAlign.START; 3517 break; 3518 3519 case PlacementAlign.END: 3520 if (this.referentRect.bottom - this.rect.height > this.safeArea.top) return PlacementAlign.END; 3521 break; 3522 } 3523 } 3524 3525 return this._aligns[0]; 3526 } 3527 3528 dispose () { 3529 this._referent = null; 3530 super.dispose(); 3531 } 3532} 3533 3534class PlacementReferent extends Instance { 3535 constructor () { 3536 super(); 3537 this._isShown = false; 3538 } 3539 3540 static get instanceClassName () { 3541 return 'PlacementReferent'; 3542 } 3543 3544 init () { 3545 this.registration.creator.setReferent(this); 3546 this._placement = this.registration.creator; 3547 } 3548 3549 get placement () { 3550 return this._placement; 3551 } 3552 3553 get isShown () { 3554 return this._isShown; 3555 } 3556 3557 set isShown (value) { 3558 if (this._isShown === value || !this.isEnabled) return; 3559 this._isShown = value; 3560 if (value) this.registration.creator.show(); 3561 else this.registration.creator.hide(); 3562 } 3563 3564 show () { 3565 this.isShown = true; 3566 } 3567 3568 hide () { 3569 this.isShown = false; 3570 } 3571} 3572 3573api$1.core = { 3574 Instance: Instance, 3575 Breakpoints: Breakpoints, 3576 KeyCodes: KeyCodes, 3577 Disclosure: Disclosure, 3578 DisclosureButton: DisclosureButton, 3579 DisclosuresGroup: DisclosuresGroup, 3580 DisclosureType: DisclosureType, 3581 DisclosureEvent: DisclosureEvent, 3582 DisclosureSelector: DisclosureSelector, 3583 DisclosureEmission: DisclosureEmission, 3584 Collapse: Collapse, 3585 CollapseButton: CollapseButton, 3586 CollapsesGroup: CollapsesGroup, 3587 CollapseSelector: CollapseSelector, 3588 RootSelector: RootSelector, 3589 RootEmission: RootEmission, 3590 Equisized: Equisized, 3591 EquisizedEmission: EquisizedEmission, 3592 Toggle: Toggle, 3593 EquisizedsGroup: EquisizedsGroup, 3594 InjectSvg: InjectSvg, 3595 InjectSvgSelector: InjectSvgSelector, 3596 Artwork: Artwork, 3597 ArtworkSelector: ArtworkSelector, 3598 AssessFile: AssessFile, 3599 AssessDetail: AssessDetail, 3600 AssessEmission: AssessEmission, 3601 AssessSelector: AssessSelector, 3602 Ratio: Ratio, 3603 RatioSelector: RatioSelector, 3604 Placement: Placement, 3605 PlacementReferent: PlacementReferent, 3606 PlacementAlign: PlacementAlign, 3607 PlacementPosition: PlacementPosition, 3608 PlacementMode: PlacementMode 3609}; 3610 3611api$1.internals.register(api$1.core.CollapseSelector.COLLAPSE, api$1.core.Collapse); 3612api$1.internals.register(api$1.core.InjectSvgSelector.INJECT_SVG, api$1.core.InjectSvg); 3613api$1.internals.register(api$1.core.RatioSelector.RATIO, api$1.core.Ratio); 3614api$1.internals.register(api$1.core.AssessSelector.ASSESS_FILE, api$1.core.AssessFile); 3615api$1.internals.register(api$1.core.AssessSelector.DETAIL, api$1.core.AssessDetail); 3616 3617const SchemeValue = { 3618 SYSTEM: 'system', 3619 LIGHT: 'light', 3620 DARK: 'dark' 3621}; 3622 3623const SchemeAttribute = { 3624 THEME: api.internals.ns.attr('theme'), 3625 SCHEME: api.internals.ns.attr('scheme'), 3626 TRANSITION: api.internals.ns.attr('transition') 3627}; 3628 3629const SchemeTheme = { 3630 LIGHT: 'light', 3631 DARK: 'dark' 3632}; 3633 3634const SchemeEmission = { 3635 SCHEME: api.internals.ns.emission('scheme', 'scheme'), 3636 THEME: api.internals.ns.emission('scheme', 'theme'), 3637 ASK: api.internals.ns.emission('scheme', 'ask') 3638}; 3639 3640const SchemeEvent = { 3641 SCHEME: api.internals.ns.event('scheme'), 3642 THEME: api.internals.ns.event('theme') 3643}; 3644 3645class Scheme extends api.core.Instance { 3646 constructor () { 3647 super(false); 3648 } 3649 3650 static get instanceClassName () { 3651 return 'Scheme'; 3652 } 3653 3654 init () { 3655 this.changing = this.change.bind(this); 3656 3657 if (this.hasAttribute(SchemeAttribute.TRANSITION)) { 3658 this.removeAttribute(SchemeAttribute.TRANSITION); 3659 this.request(this.restoreTransition.bind(this)); 3660 } 3661 3662 const scheme = api.internals.support.supportLocalStorage() ? localStorage.getItem('scheme') : ''; 3663 const schemeAttr = this.getAttribute(SchemeAttribute.SCHEME); 3664 3665 switch (scheme) { 3666 case SchemeValue.DARK: 3667 case SchemeValue.LIGHT: 3668 case SchemeValue.SYSTEM: 3669 this.scheme = scheme; 3670 break; 3671 3672 default: 3673 switch (schemeAttr) { 3674 case SchemeValue.DARK: 3675 this.scheme = SchemeValue.DARK; 3676 break; 3677 3678 case SchemeValue.LIGHT: 3679 this.scheme = SchemeValue.LIGHT; 3680 break; 3681 3682 default: 3683 this.scheme = SchemeValue.SYSTEM; 3684 } 3685 } 3686 3687 this.addAscent(SchemeEmission.ASK, this.ask.bind(this)); 3688 this.addAscent(SchemeEmission.SCHEME, this.apply.bind(this)); 3689 } 3690 3691 get proxy () { 3692 const scope = this; 3693 3694 const proxyAccessors = { 3695 get scheme () { 3696 return scope.scheme; 3697 }, 3698 set scheme (value) { 3699 scope.scheme = value; 3700 } 3701 }; 3702 3703 return api.internals.property.completeAssign(super.proxy, proxyAccessors); 3704 } 3705 3706 restoreTransition () { 3707 this.setAttribute(SchemeAttribute.TRANSITION, ''); 3708 } 3709 3710 ask () { 3711 this.descend(SchemeEmission.SCHEME, this.scheme); 3712 } 3713 3714 apply (value) { 3715 this.scheme = value; 3716 } 3717 3718 get scheme () { 3719 return this._scheme; 3720 } 3721 3722 set scheme (value) { 3723 if (this._scheme === value) return; 3724 this._scheme = value; 3725 switch (value) { 3726 case SchemeValue.SYSTEM: 3727 this.listenPreferences(); 3728 break; 3729 3730 case SchemeValue.DARK: 3731 this.unlistenPreferences(); 3732 this.theme = SchemeTheme.DARK; 3733 break; 3734 3735 case SchemeValue.LIGHT: 3736 this.unlistenPreferences(); 3737 this.theme = SchemeTheme.LIGHT; 3738 break; 3739 3740 default: 3741 this.scheme = SchemeValue.SYSTEM; 3742 return; 3743 } 3744 3745 this.descend(SchemeEmission.SCHEME, value); 3746 if (api.internals.support.supportLocalStorage()) { 3747 localStorage.setItem('scheme', value); 3748 } 3749 this.setAttribute(SchemeAttribute.SCHEME, value); 3750 this.dispatch(SchemeEvent.SCHEME, { scheme: this._scheme }); 3751 } 3752 3753 get theme () { 3754 return this._theme; 3755 } 3756 3757 set theme (value) { 3758 if (this._theme === value) return; 3759 switch (value) { 3760 case SchemeTheme.LIGHT: 3761 case SchemeTheme.DARK: 3762 this._theme = value; 3763 this.setAttribute(SchemeAttribute.THEME, value); 3764 this.descend(SchemeEmission.THEME, value); 3765 this.dispatch(SchemeEvent.THEME, { theme: this._theme }); 3766 document.documentElement.style.colorScheme = value === SchemeTheme.DARK ? 'dark' : ''; 3767 break; 3768 } 3769 } 3770 3771 listenPreferences () { 3772 if (this.isListening) return; 3773 this.isListening = true; 3774 this.mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); 3775 if (this.mediaQuery.addEventListener) this.mediaQuery.addEventListener('change', this.changing); 3776 this.change(); 3777 } 3778 3779 unlistenPreferences () { 3780 if (!this.isListening) return; 3781 this.isListening = false; 3782 this.mediaQuery.removeEventListener('change', this.changing); 3783 this.mediaQuery = null; 3784 } 3785 3786 change () { 3787 if (!this.isListening) return; 3788 this.theme = this.mediaQuery.matches ? SchemeTheme.DARK : SchemeTheme.LIGHT; 3789 } 3790 3791 mutate (attributeNames) { 3792 if (attributeNames.indexOf(SchemeAttribute.SCHEME) > -1) this.scheme = this.getAttribute(SchemeAttribute.SCHEME); 3793 if (attributeNames.indexOf(SchemeAttribute.THEME) > -1) this.theme = this.getAttribute(SchemeAttribute.THEME); 3794 } 3795 3796 dispose () { 3797 this.unlistenPreferences(); 3798 } 3799} 3800 3801const SchemeSelector = { 3802 SCHEME: `:root${api.internals.ns.attr.selector('theme')}, :root${api.internals.ns.attr.selector('scheme')}`, 3803 SWITCH_THEME: api.internals.ns.selector('switch-theme'), 3804 RADIO_BUTTONS: `input[name="${api.internals.ns('radios-theme')}"]` 3805}; 3806 3807api.scheme = { 3808 Scheme: Scheme, 3809 SchemeValue: SchemeValue, 3810 SchemeSelector: SchemeSelector, 3811 SchemeEmission: SchemeEmission, 3812 SchemeTheme: SchemeTheme, 3813 SchemeEvent: SchemeEvent 3814}; 3815 3816api.internals.register(api.scheme.SchemeSelector.SCHEME, api.scheme.Scheme); 3817 3818const ACCORDION = api.internals.ns.selector('accordion'); 3819const COLLAPSE$2 = api.internals.ns.selector('collapse'); 3820 3821const AccordionSelector = { 3822 GROUP: api.internals.ns.selector('accordions-group'), 3823 ACCORDION: ACCORDION, 3824 COLLAPSE: `${ACCORDION} > ${COLLAPSE$2}, ${ACCORDION} > *:not(${ACCORDION}):not(${COLLAPSE$2}) > ${COLLAPSE$2}, ${ACCORDION} > *:not(${ACCORDION}):not(${COLLAPSE$2}) > *:not(${ACCORDION}):not(${COLLAPSE$2}) > ${COLLAPSE$2}`, 3825 COLLAPSE_LEGACY: `${ACCORDION} ${COLLAPSE$2}`, 3826 BUTTON: `${ACCORDION}__btn` 3827}; 3828 3829class Accordion extends api.core.Instance { 3830 static get instanceClassName () { 3831 return 'Accordion'; 3832 } 3833 3834 get collapsePrimary () { 3835 const buttons = this.element.children.map(child => child.getInstance('CollapseButton')).filter(button => button !== null && button.hasClass(AccordionSelector.BUTTON)); 3836 return buttons[0]; 3837 } 3838} 3839 3840class AccordionsGroup extends api.core.CollapsesGroup { 3841 static get instanceClassName () { 3842 return 'AccordionsGroup'; 3843 } 3844 3845 validate (member) { 3846 const match = member.node.matches(api.internals.legacy.isLegacy ? AccordionSelector.COLLAPSE_LEGACY : AccordionSelector.COLLAPSE); 3847 return super.validate(member) && match; 3848 } 3849} 3850 3851api.accordion = { 3852 Accordion: Accordion, 3853 AccordionSelector: AccordionSelector, 3854 AccordionsGroup: AccordionsGroup 3855}; 3856 3857api.internals.register(api.accordion.AccordionSelector.GROUP, api.accordion.AccordionsGroup); 3858api.internals.register(api.accordion.AccordionSelector.ACCORDION, api.accordion.Accordion); 3859 3860const ButtonSelector = { 3861 EQUISIZED_BUTTON: `${api.internals.ns.selector('btns-group--equisized')} ${api.internals.ns.selector('btn')}`, 3862 EQUISIZED_GROUP: api.internals.ns.selector('btns-group--equisized') 3863}; 3864 3865api.button = { 3866 ButtonSelector: ButtonSelector 3867}; 3868 3869api.internals.register(api.button.ButtonSelector.EQUISIZED_BUTTON, api.core.Equisized); 3870api.internals.register(api.button.ButtonSelector.EQUISIZED_GROUP, api.core.EquisizedsGroup); 3871 3872class CardDownload extends api.core.Instance { 3873 static get instanceClassName () { 3874 return 'CardDownload'; 3875 } 3876 3877 init () { 3878 this.addAscent(api.core.AssessEmission.UPDATE, details => { 3879 this.descend(api.core.AssessEmission.UPDATE, details); 3880 }); 3881 this.addAscent(api.core.AssessEmission.ADDED, () => { 3882 this.descend(api.core.AssessEmission.ADDED); 3883 }); 3884 } 3885} 3886 3887const CardSelector = { 3888 DOWNLOAD: api.internals.ns.selector('card--download'), 3889 DOWNLOAD_DETAIL: `${api.internals.ns.selector('card--download')} ${api.internals.ns.selector('card__end')} ${api.internals.ns.selector('card__detail')}` 3890}; 3891 3892api.card = { 3893 CardSelector: CardSelector, 3894 CardDownload: CardDownload 3895}; 3896 3897api.internals.register(api.card.CardSelector.DOWNLOAD, api.card.CardDownload); 3898api.internals.register(api.card.CardSelector.DOWNLOAD_DETAIL, api.core.AssessDetail); 3899 3900const CheckboxSelector = { 3901 INPUT: `${api.internals.ns.selector('checkbox-group')} input[type="checkbox"]` 3902}; 3903 3904const CheckboxEmission = { 3905 CHANGE: api.internals.ns.emission('checkbox', 'change'), 3906 RETRIEVE: api.internals.ns.emission('checkbox', 'retrieve') 3907}; 3908 3909class CheckboxInput extends api.core.Instance { 3910 static get instanceClassName () { 3911 return 'CheckboxInput'; 3912 } 3913 3914 constructor () { 3915 super(); 3916 this._handlingChange = this.handleChange.bind(this); 3917 } 3918 3919 init () { 3920 this.node.addEventListener('change', this._handlingChange); 3921 this.addDescent(CheckboxEmission.RETRIEVE, this._handlingChange); 3922 this.handleChange(); 3923 } 3924 3925 get isChecked () { 3926 return this.node.checked; 3927 } 3928 3929 handleChange () { 3930 this.ascend(CheckboxEmission.CHANGE, this.node); 3931 } 3932} 3933 3934api.checkbox = { 3935 CheckboxSelector: CheckboxSelector, 3936 CheckboxEmission: CheckboxEmission,
vendor: 4,407 bytes, lines 3937-4094
3937 CheckboxInput: CheckboxInput 3938}; 3939 3940api.internals.register(api.checkbox.CheckboxSelector.INPUT, api.checkbox.CheckboxInput); 3941 3942const SegmentedSelector = { 3943 SEGMENTED: api.internals.ns.selector('segmented'), 3944 SEGMENTED_ELEMENTS: api.internals.ns.selector('segmented__elements'), 3945 SEGMENTED_ELEMENT: api.internals.ns.selector('segmented__element input'), 3946 SEGMENTED_LEGEND: api.internals.ns.selector('segmented__legend') 3947}; 3948 3949const SegmentedEmission = { 3950 ADDED: api.internals.ns.emission('segmented', 'added'), 3951 REMOVED: api.internals.ns.emission('segmented', 'removed') 3952}; 3953 3954class Segmented extends api.core.Instance { 3955 static get instanceClassName () { 3956 return 'Segmented'; 3957 } 3958 3959 init () { 3960 this.elements = this.node.querySelector(SegmentedSelector.SEGMENTED_ELEMENTS); 3961 this.legend = this.node.querySelector(SegmentedSelector.SEGMENTED_LEGEND); 3962 this.addAscent(SegmentedEmission.ADDED, this.resize.bind(this)); 3963 this.addAscent(SegmentedEmission.REMOVED, this.resize.bind(this)); 3964 this._isLegendInline = this.legend && this.legend.classList.contains(`${api.prefix}-segmented__legend--inline`); 3965 this.isResizing = true; 3966 } 3967 3968 resize () { 3969 const SEGMENTED_VERTICAL = `${api.prefix}-segmented--vertical`; 3970 const LEGEND_INLINE = `${api.prefix}-segmented__legend--inline`; 3971 const gapOffset = 16; 3972 3973 this.removeClass(SEGMENTED_VERTICAL); 3974 3975 if (this._isLegendInline) { 3976 this.legend.classList.add(LEGEND_INLINE); 3977 3978 if (this.node.offsetWidth > this.node.parentNode.offsetWidth || (this.elements.scrollWidth + this.legend.offsetWidth + gapOffset) > this.node.parentNode.offsetWidth) { 3979 this.legend.classList.remove(LEGEND_INLINE); 3980 } 3981 } 3982 3983 if (this.elements.offsetWidth > this.node.parentNode.offsetWidth || this.elements.scrollWidth > this.node.parentNode.offsetWidth) { 3984 this.addClass(SEGMENTED_VERTICAL); 3985 } else { 3986 this.removeClass(SEGMENTED_VERTICAL); 3987 } 3988 } 3989} 3990 3991class SegmentedElement extends api.core.Instance { 3992 static get instanceClassName () { 3993 return 'SegmentedElement'; 3994 } 3995 3996 init () { 3997 this.ascend(SegmentedEmission.ADDED); 3998 } 3999 4000 dispose () { 4001 this.ascend(SegmentedEmission.REMOVED); 4002 } 4003} 4004 4005api.segmented = { 4006 SegmentedSelector: SegmentedSelector, 4007 SegmentedEmission: SegmentedEmission, 4008 SegmentedElement: SegmentedElement, 4009 Segmented: Segmented 4010}; 4011 4012api.internals.register(api.segmented.SegmentedSelector.SEGMENTED, api.segmented.Segmented); 4013api.internals.register(api.segmented.SegmentedSelector.SEGMENTED_ELEMENT, api.segmented.SegmentedElement); 4014 4015const BreadcrumbSelector = { 4016 BREADCRUMB: api.internals.ns.selector('breadcrumb'), 4017 BUTTON: api.internals.ns.selector('breadcrumb__button') 4018}; 4019 4020class Breadcrumb extends api.core.Instance { 4021 constructor () { 4022 super(); 4023 this.count = 0; 4024 this.focusing = this.focus.bind(this); 4025 } 4026 4027 static get instanceClassName () { 4028 return 'Breadcrumb'; 4029 } 4030 4031 init () { 4032 this.getCollapse(); 4033 this.isResizing = true; 4034 } 4035 4036 get proxy () { 4037 const scope = this; 4038 return Object.assign(super.proxy, { 4039 focus: scope.focus.bind(scope), 4040 disclose: scope.collapse.disclose.bind(scope.collapse) 4041 }); 4042 } 4043 4044 getCollapse () { 4045 const collapse = this.collapse; 4046 if (collapse) { 4047 collapse.listen(api.core.DisclosureEvent.DISCLOSE, this.focusing); 4048 } else { 4049 this.addAscent(api.core.DisclosureEmission.ADDED, this.getCollapse.bind(this)); 4050 } 4051 } 4052 4053 resize () { 4054 const collapse = this.collapse; 4055 const links = this.links; 4056 if (!collapse || !links.length) return; 4057 4058 if (this.isBreakpoint(api.core.Breakpoints.MD)) { 4059 if (collapse.buttonHasFocus) links[0].focus(); 4060 } else { 4061 if (links.indexOf(document.activeElement) > -1) collapse.focus(); 4062 } 4063 } 4064 4065 get links () { 4066 return [...this.querySelectorAll('a[href]')]; 4067 } 4068 4069 get collapse () { 4070 return this.element.getDescendantInstances(api.core.Collapse.instanceClassName, null, true)[0]; 4071 } 4072 4073 focus () { 4074 this.count = 0; 4075 this._focus(); 4076 } 4077 4078 _focus () { 4079 const link = this.links[0]; 4080 if (!link) return; 4081 link.focus(); 4082 this.request(this.verify.bind(this)); 4083 } 4084 4085 verify () { 4086 this.count++; 4087 if (this.count > 100) return; 4088 const link = this.links[0]; 4089 if (!link) return; 4090 if (document.activeElement !== link) this._focus(); 4091 } 4092 4093 get collapsePrimary () { 4094 const buttons = this.element.children.map(child =>
vendor: 4,524 bytes, lines 4094-4286
4094 child.getInstance('CollapseButton')).filter(button => button !== null && button.hasClass(BreadcrumbSelector.BUTTON)); 4095 return buttons[0]; 4096 } 4097} 4098 4099api.breadcrumb = { 4100 BreadcrumbSelector: BreadcrumbSelector, 4101 Breadcrumb: Breadcrumb 4102}; 4103 4104api.internals.register(api.breadcrumb.BreadcrumbSelector.BREADCRUMB, api.breadcrumb.Breadcrumb); 4105 4106const TooltipSelector = { 4107 TOOLTIP: api.internals.ns.selector('tooltip'), 4108 SHOWN: api.internals.ns.selector('tooltip--shown'), 4109 BUTTON: api.internals.ns.selector('btn--tooltip') 4110}; 4111 4112const TooltipReferentState = { 4113 FOCUS: 1 << 0, 4114 HOVER: 1 << 1 4115}; 4116 4117class TooltipReferent extends api.core.PlacementReferent { 4118 constructor () { 4119 super(); 4120 this._state = 0; 4121 } 4122 4123 static get instanceClassName () { 4124 return 'TooltipReferent'; 4125 } 4126 4127 init () { 4128 super.init(); 4129 this.listen('focusin', this.focusIn.bind(this)); 4130 this.listen('focusout', this.focusOut.bind(this)); 4131 if (!this.matches(TooltipSelector.BUTTON)) { 4132 const mouseover = this.mouseover.bind(this); 4133 this.listen('mouseover', mouseover); 4134 this.placement.listen('mouseover', mouseover); 4135 const mouseout = this.mouseout.bind(this); 4136 this.listen('mouseout', mouseout); 4137 this.placement.listen('mouseout', mouseout); 4138 } 4139 this.addEmission(api.core.RootEmission.KEYDOWN, this._keydown.bind(this)); 4140 this.listen('click', this._click.bind(this)); 4141 this.addEmission(api.core.RootEmission.CLICK, this._clickOut.bind(this)); 4142 } 4143 4144 _click () { 4145 this.focus(); 4146 } 4147 4148 _clickOut (target) { 4149 if (!this.node.contains(target)) this.blur(); 4150 } 4151 4152 _keydown (keyCode) { 4153 switch (keyCode) { 4154 case api.core.KeyCodes.ESCAPE: 4155 this.blur(); 4156 this.close(); 4157 break; 4158 } 4159 } 4160 4161 close () { 4162 this.state = 0; 4163 } 4164 4165 get state () { 4166 return this._state; 4167 } 4168 4169 set state (value) { 4170 if (this._state === value) return; 4171 this.isShown = value > 0; 4172 this._state = value; 4173 } 4174 4175 focusIn () { 4176 this.state |= TooltipReferentState.FOCUS; 4177 } 4178 4179 focusOut () { 4180 this.state &= ~TooltipReferentState.FOCUS; 4181 } 4182 4183 mouseover () { 4184 this.state |= TooltipReferentState.HOVER; 4185 } 4186 4187 mouseout () { 4188 this.state &= ~TooltipReferentState.HOVER; 4189 } 4190} 4191 4192const TooltipEvent = { 4193 SHOW: ns.event('show'), 4194 HIDE: ns.event('hide') 4195}; 4196 4197const TooltipState = { 4198 HIDDEN: 'hidden', 4199 SHOWN: 'shown', 4200 HIDING: 'hiding' 4201}; 4202 4203class Tooltip extends api.core.Placement { 4204 constructor () { 4205 super(api.core.PlacementMode.AUTO, [api.core.PlacementPosition.TOP, api.core.PlacementPosition.BOTTOM], [api.core.PlacementAlign.CENTER, api.core.PlacementAlign.START, api.core.PlacementAlign.END]); 4206 this.modifier = ''; 4207 this._state = TooltipState.HIDDEN; 4208 } 4209 4210 static get instanceClassName () { 4211 return 'Tooltip'; 4212 } 4213 4214 init () { 4215 super.init(); 4216 this.register(`[aria-describedby="${this.id}"]`, TooltipReferent); 4217 this.listen('transitionend', this.transitionEnd.bind(this)); 4218 } 4219 4220 transitionEnd () { 4221 if (this._state === TooltipState.HIDING) { 4222 this._state = TooltipState.HIDDEN; 4223 this.isShown = false; 4224 } 4225 } 4226 4227 get isShown () { 4228 return super.isShown; 4229 } 4230 4231 set isShown (value) { 4232 if (!this.isEnabled) return; 4233 switch (true) { 4234 case value: 4235 this._state = TooltipState.SHOWN; 4236 this.addClass(TooltipSelector.SHOWN); 4237 this.dispatch(TooltipEvent.SHOW); 4238 super.isShown = true; 4239 break; 4240 4241 case this.isShown && !value && this._state === TooltipState.SHOWN: 4242 this._state = TooltipState.HIDING; 4243 this.removeClass(TooltipSelector.SHOWN); 4244 break; 4245 4246 case this.isShown && !value && this._state === TooltipState.HIDDEN: 4247 this.dispatch(TooltipEvent.HIDE); 4248 super.isShown = false; 4249 break; 4250 } 4251 } 4252 4253 render () { 4254 super.render(); 4255 let x = this.referentRect.center - this.rect.center; 4256 const limit = this.rect.width * 0.5 - 8; 4257 if (x < -limit) x = -limit; 4258 if (x > limit) x = limit; 4259 this.setProperty('--arrow-x', `${x.toFixed(2)}px`); 4260 } 4261} 4262 4263api.tooltip = { 4264 Tooltip: Tooltip, 4265 TooltipSelector: TooltipSelector, 4266 TooltipEvent: TooltipEvent 4267}; 4268 4269api.internals.register(api.tooltip.TooltipSelector.TOOLTIP, api.tooltip.Tooltip); 4270 4271class ToggleInput extends api.core.Instance { 4272 static get instanceClassName () { 4273 return 'ToggleInput'; 4274 } 4275 4276 get isChecked () { 4277 return this.node.checked; 4278 } 4279} 4280 4281class ToggleStatusLabel extends api.core.Instance { 4282 static get instanceClassName () { 4283 return 'ToggleStatusLabel'; 4284 } 4285 4286 init () {
vendor: 4,567 bytes, lines 4287-4448
4287 this.register(`input[id="${this.getAttribute('for')}"]`, ToggleInput); 4288 this.update(); 4289 this.isSwappingFont = true; 4290 } 4291 4292 get proxy () { 4293 const scope = this; 4294 return Object.assign(super.proxy, { 4295 update: scope.update.bind(scope) 4296 }); 4297 } 4298 4299 get input () { 4300 return this.getRegisteredInstances('ToggleInput')[0]; 4301 } 4302 4303 update () { 4304 this.node.style.removeProperty('--toggle-status-width'); 4305 const checked = this.input.isChecked; 4306 4307 const style = getComputedStyle(this.node, ':before'); 4308 let maxWidth = parseFloat(style.width); 4309 this.input.node.checked = !checked; 4310 4311 const style2 = getComputedStyle(this.node, ':before'); 4312 const width = parseFloat(style2.width); 4313 if (width > maxWidth) maxWidth = width; 4314 this.input.node.checked = checked; 4315 4316 this.node.style.setProperty('--toggle-status-width', (maxWidth / 16) + 'rem'); 4317 } 4318 4319 swapFont (families) { 4320 this.update(); 4321 } 4322} 4323 4324const ToggleSelector = { 4325 STATUS_LABEL: `${api.internals.ns.selector('toggle__label')}${api.internals.ns.attr.selector('checked-label')}${api.internals.ns.attr.selector('unchecked-label')}` 4326}; 4327 4328// import { ToggleInput } from './script/toggle/toggle-input.js'; 4329 4330api.toggle = { 4331 ToggleStatusLabel: ToggleStatusLabel, 4332 ToggleSelector: ToggleSelector 4333}; 4334 4335api.internals.register(api.toggle.ToggleSelector.STATUS_LABEL, api.toggle.ToggleStatusLabel); 4336 4337const ITEM$1 = api.internals.ns.selector('sidemenu__item'); 4338const COLLAPSE$1 = api.internals.ns.selector('collapse'); 4339 4340const SidemenuSelector = { 4341 LIST: api.internals.ns.selector('sidemenu__list'), 4342 COLLAPSE: `${ITEM$1} > ${COLLAPSE$1}, ${ITEM$1} > *:not(${ITEM$1}):not(${COLLAPSE$1}) > ${COLLAPSE$1}, ${ITEM$1} > *:not(${ITEM$1}):not(${COLLAPSE$1}) > *:not(${ITEM$1}):not(${COLLAPSE$1}) > ${COLLAPSE$1}`, 4343 COLLAPSE_LEGACY: `${ITEM$1} ${COLLAPSE$1}`, 4344 ITEM: api.internals.ns.selector('sidemenu__item'), 4345 BUTTON: api.internals.ns.selector('sidemenu__btn') 4346}; 4347 4348class SidemenuList extends api.core.CollapsesGroup { 4349 static get instanceClassName () { 4350 return 'SidemenuList'; 4351 } 4352 4353 validate (member) { 4354 return super.validate(member) && member.node.matches(api.internals.legacy.isLegacy ? SidemenuSelector.COLLAPSE_LEGACY : SidemenuSelector.COLLAPSE); 4355 } 4356} 4357 4358class SidemenuItem extends api.core.Instance { 4359 static get instanceClassName () { 4360 return 'SidemenuItem'; 4361 } 4362 4363 get collapsePrimary () { 4364 const buttons = this.element.children.map(child => child.getInstance('CollapseButton')).filter(button => button !== null && button.hasClass(SidemenuSelector.BUTTON)); 4365 return buttons[0]; 4366 } 4367} 4368 4369api.sidemenu = { 4370 SidemenuList: SidemenuList, 4371 SidemenuItem: SidemenuItem, 4372 SidemenuSelector: SidemenuSelector 4373}; 4374 4375api.internals.register(api.sidemenu.SidemenuSelector.LIST, api.sidemenu.SidemenuList); 4376api.internals.register(api.sidemenu.SidemenuSelector.ITEM, api.sidemenu.SidemenuItem); 4377 4378const ModalSelector = { 4379 MODAL: api.internals.ns.selector('modal'), 4380 SCROLL_DIVIDER: api.internals.ns.selector('scroll-divider'), 4381 BODY: api.internals.ns.selector('modal__body'), 4382 TITLE: api.internals.ns.selector('modal__title') 4383}; 4384 4385class ModalButton extends api.core.DisclosureButton { 4386 constructor () { 4387 super(api.core.DisclosureType.OPENED); 4388 } 4389 4390 static get instanceClassName () { 4391 return 'ModalButton'; 4392 } 4393} 4394 4395const ModalAttribute = { 4396 CONCEALING_BACKDROP: api.internals.ns.attr('concealing-backdrop') 4397}; 4398 4399class Modal extends api.core.Disclosure { 4400 constructor () { 4401 super(api.core.DisclosureType.OPENED, ModalSelector.MODAL, ModalButton, 'ModalsGroup'); 4402 this._isActive = false; 4403 this.scrolling = this.resize.bind(this, false); 4404 this.resizing = this.resize.bind(this, true); 4405 } 4406 4407 static get instanceClassName () { 4408 return 'Modal'; 4409 } 4410 4411 init () { 4412 super.init(); 4413 this._isDialog = this.node.tagName === 'DIALOG'; 4414 this.isScrolling = false; 4415 this.listenClick(); 4416 this.addEmission(api.core.RootEmission.KEYDOWN, this._keydown.bind(this)); 4417 } 4418 4419 _keydown (keyCode) { 4420 switch (keyCode) { 4421 case api.core.KeyCodes.ESCAPE: 4422 this._escape(); 4423 break; 4424 } 4425 } 4426 4427 // TODO v2 : passer les tagName d'action en constante 4428 _escape () { 4429 const tagName = document.activeElement ? document.activeElement.tagName : undefined; 4430 4431 switch (tagName) { 4432 case 'INPUT': 4433 case 'LABEL': 4434 case 'TEXTAREA': 4435 case 'SELECT': 4436 case 'AUDIO': 4437 case 'VIDEO': 4438 break; 4439 4440 default: 4441 if (this.isDisclosed) { 4442 this.conceal(); 4443 this.focus(); 4444 } 4445 } 4446 } 4447 4448 retrieved () {
vendor: 17,945 bytes, lines 4449-5088
4449 this._ensureAccessibleName(); 4450 } 4451 4452 get body () { 4453 return this.element.getDescendantInstances('ModalBody', 'Modal')[0]; 4454 } 4455 4456 handleClick (e) { 4457 if (e.target === this.node && this.getAttribute(ModalAttribute.CONCEALING_BACKDROP) !== 'false') this.conceal(); 4458 } 4459 4460 disclose (withhold) { 4461 if (!super.disclose(withhold)) return false; 4462 if (this.body) this.body.activate(); 4463 this.isScrollLocked = true; 4464 this.setAttribute('aria-modal', 'true'); 4465 this.setAttribute('open', 'true'); 4466 if (!this._isDialog) { 4467 this.activateModal(); 4468 } 4469 return true; 4470 } 4471 4472 conceal (withhold, preventFocus) { 4473 if (!super.conceal(withhold, preventFocus)) return false; 4474 this.isScrollLocked = false; 4475 this.removeAttribute('aria-modal'); 4476 this.removeAttribute('open'); 4477 if (this.body) this.body.deactivate(); 4478 if (!this._isDialog) { 4479 this.deactivateModal(); 4480 } 4481 return true; 4482 } 4483 4484 get isDialog () { 4485 return this._isDialog; 4486 } 4487 4488 set isDialog (value) { 4489 this._isDialog = value; 4490 } 4491 4492 activateModal () { 4493 if (this._isActive) return; 4494 this._isActive = true; 4495 this._hasDialogRole = this.getAttribute('role') === 'dialog'; 4496 if (!this._hasDialogRole) this.setAttribute('role', 'dialog'); 4497 } 4498 4499 deactivateModal () { 4500 if (!this._isActive) return; 4501 this._isActive = false; 4502 if (!this._hasDialogRole) this.removeAttribute('role'); 4503 } 4504 4505 _setAccessibleName (node, append) { 4506 const id = this.retrieveNodeId(node, append); 4507 this.warn(`add reference to ${append} for accessible name (aria-labelledby)`); 4508 this.setAttribute('aria-labelledby', id); 4509 } 4510 4511 _ensureAccessibleName () { 4512 if (this.hasAttribute('aria-labelledby') || this.hasAttribute('aria-label')) return; 4513 this.warn('missing accessible name'); 4514 const title = this.node.querySelector(ModalSelector.TITLE); 4515 const primary = this.primaryButtons[0]; 4516 4517 switch (true) { 4518 case title !== null: 4519 this._setAccessibleName(title, 'title'); 4520 break; 4521 4522 case primary !== undefined: 4523 this.warn('missing required title, fallback to primary button'); 4524 this._setAccessibleName(primary, 'primary'); 4525 break; 4526 } 4527 } 4528} 4529 4530const unordereds = [ 4531 '[tabindex="0"]', 4532 'a[href]', 4533 'button:not([disabled])', 4534 'input:not([disabled])', 4535 'select:not([disabled])', 4536 'textarea:not([disabled])', 4537 'audio[controls]', 4538 'video[controls]', 4539 '[contenteditable]:not([contenteditable="false"])', 4540 'details>summary:first-of-type', 4541 'details', 4542 'iframe' 4543]; 4544 4545const UNORDEREDS = unordereds.join(); 4546 4547const ordereds = [ 4548 '[tabindex]:not([tabindex="-1"]):not([tabindex="0"])' 4549]; 4550 4551const ORDEREDS = ordereds.join(); 4552 4553const isFocusable = (element, container) => { 4554 if (!(element instanceof Element)) return false; 4555 const style = window.getComputedStyle(element); 4556 if (!style) return false; 4557 if (style.visibility === 'hidden') return false; 4558 if (container === undefined) container = element; 4559 4560 while (container.contains(element)) { 4561 if (style.display === 'none') return false; 4562 element = element.parentElement; 4563 } 4564 4565 return true; 4566}; 4567 4568class FocusTrap { 4569 constructor (onTrap, onUntrap) { 4570 this.element = null; 4571 this.activeElement = null; 4572 this.onTrap = onTrap; 4573 this.onUntrap = onUntrap; 4574 this.waiting = this.wait.bind(this); 4575 this.handling = this.handle.bind(this); 4576 this.focusing = this.maintainFocus.bind(this); 4577 this.current = null; 4578 } 4579 4580 get trapped () { return this.element !== null; } 4581 4582 trap (element) { 4583 if (this.trapped) this.untrap(); 4584 4585 this.element = element; 4586 this.isTrapping = true; 4587 this.wait(); 4588 4589 if (this.onTrap) this.onTrap(); 4590 } 4591 4592 wait () { 4593 if (!isFocusable(this.element)) { 4594 window.requestAnimationFrame(this.waiting); 4595 return; 4596 } 4597 4598 this.trapping(); 4599 } 4600 4601 trapping () { 4602 if (!this.isTrapping) return; 4603 this.isTrapping = false; 4604 const focusables = this.focusables; 4605 if (focusables.length && focusables.indexOf(document.activeElement) === -1) focusables[0].focus(); 4606 this.element.setAttribute('aria-modal', true); 4607 window.addEventListener('keydown', this.handling); 4608 document.body.addEventListener('focus', this.focusing, true); 4609 } 4610 4611 stun (node) { 4612 for (const child of node.children) { 4613 if (child === this.element) continue; 4614 if (child.contains(this.element)) { 4615 this.stun(child); 4616 continue; 4617 } 4618 this.stunneds.push(new Stunned(child)); 4619 } 4620 } 4621 4622 maintainFocus (event) { 4623 if (!this.element.contains(event.target)) { 4624 const focusables = this.focusables; 4625 if (focusables.length === 0) return; 4626 const first = focusables[0]; 4627 event.preventDefault(); 4628 first.focus(); 4629 } 4630 } 4631 4632 handle (e) { 4633 if (e.keyCode !== 9) return; 4634 4635 const focusables = this.focusables; 4636 if (focusables.length === 0) return; 4637 4638 const first = focusables[0]; 4639 const last = focusables[focusables.length - 1]; 4640 4641 const index = focusables.indexOf(document.activeElement); 4642 4643 if (e.shiftKey) { 4644 if (!this.element.contains(document.activeElement) || index < 1) { 4645 e.preventDefault(); 4646 last.focus(); 4647 } else if (document.activeElement.tabIndex > 0 || focusables[index - 1].tabIndex > 0) { 4648 e.preventDefault(); 4649 focusables[index - 1].focus(); 4650 } 4651 } else { 4652 if (!this.element.contains(document.activeElement) || index === focusables.length - 1 || index === -1) { 4653 e.preventDefault(); 4654 first.focus(); 4655 } else if (document.activeElement.tabIndex > 0) { 4656 e.preventDefault(); 4657 focusables[index + 1].focus(); 4658 } 4659 } 4660 } 4661 4662 get focusables () { 4663 let unordereds = api.internals.dom.querySelectorAllArray(this.element, UNORDEREDS); 4664 4665 /** 4666 * filtrage des radiobutttons de même name (la navigations d'un groupe de radio se fait à la flèche et non pas au tab 4667 **/ 4668 const radios = api.internals.dom.querySelectorAllArray(document.documentElement, 'input[type="radio"]'); 4669 4670 if (radios.length) { 4671 const groups = {}; 4672 4673 for (const radio of radios) { 4674 const name = radio.getAttribute('name'); 4675 if (groups[name] === undefined) groups[name] = new RadioButtonGroup(name); 4676 groups[name].push(radio); 4677 } 4678 4679 unordereds = unordereds.filter((unordered) => { 4680 if (unordered.tagName.toLowerCase() !== 'input' || unordered.getAttribute('type').toLowerCase() !== 'radio') return true; 4681 const name = unordered.getAttribute('name'); 4682 return groups[name].keep(unordered); 4683 }); 4684 } 4685 4686 const ordereds = api.internals.dom.querySelectorAllArray(this.element, ORDEREDS); 4687 4688 ordereds.sort((a, b) => a.tabIndex - b.tabIndex); 4689 4690 const noDuplicates = unordereds.filter((element) => ordereds.indexOf(element) === -1); 4691 const concateneds = ordereds.concat(noDuplicates); 4692 return concateneds.filter((element) => element.tabIndex !== '-1' && isFocusable(element, this.element)); 4693 } 4694 4695 untrap () { 4696 if (!this.trapped) return; 4697 this.isTrapping = false; 4698 4699 this.element.removeAttribute('aria-modal'); 4700 window.removeEventListener('keydown', this.handling); 4701 document.body.removeEventListener('focus', this.focusing, true); 4702 4703 this.element = null; 4704 4705 if (this.onUntrap) this.onUntrap(); 4706 } 4707 4708 dispose () { 4709 this.untrap(); 4710 } 4711} 4712 4713class Stunned { 4714 constructor (element) { 4715 this.element = element; 4716 // this.hidden = element.getAttribute('aria-hidden'); 4717 this.inert = element.getAttribute('inert'); 4718 4719 // this.element.setAttribute('aria-hidden', true); 4720 this.element.setAttribute('inert', ''); 4721 } 4722 4723 unstun () { 4724 /* 4725 if (this.hidden === null) this.element.removeAttribute('aria-hidden'); 4726 else this.element.setAttribute('aria-hidden', this.hidden); 4727 */ 4728 4729 if (this.inert === null) this.element.removeAttribute('inert'); 4730 else this.element.setAttribute('inert', this.inert); 4731 } 4732} 4733 4734class RadioButtonGroup { 4735 constructor (name) { 4736 this.name = name; 4737 this.buttons = []; 4738 } 4739 4740 push (button) { 4741 this.buttons.push(button); 4742 if (button === document.activeElement || button.checked || this.selected === undefined) this.selected = button; 4743 } 4744 4745 keep (button) { 4746 return this.selected === button; 4747 } 4748} 4749 4750class ModalsGroup extends api.core.DisclosuresGroup { 4751 constructor () { 4752 super('Modal', false); 4753 this.focusTrap = new FocusTrap(); 4754 } 4755 4756 static get instanceClassName () { 4757 return 'ModalsGroup'; 4758 } 4759 4760 apply (value, initial) { 4761 super.apply(value, initial); 4762 if (this.current === null) this.focusTrap.untrap(); 4763 else this.focusTrap.trap(this.current.node); 4764 } 4765} 4766 4767const OFFSET = 32; // 32px => 8v => 2rem 4768 4769class ModalBody extends api.core.Instance { 4770 static get instanceClassName () { 4771 return 'ModalBody'; 4772 } 4773 4774 init () { 4775 this.listen('scroll', this.divide.bind(this)); 4776 } 4777 4778 activate () { 4779 this.isResizing = true; 4780 this.resize(); 4781 } 4782 4783 deactivate () { 4784 this.isResizing = false; 4785 } 4786 4787 divide () { 4788 if (this.node.scrollHeight > this.node.clientHeight) { 4789 if (this.node.offsetHeight + this.node.scrollTop >= this.node.scrollHeight) { 4790 this.removeClass(ModalSelector.SCROLL_DIVIDER); 4791 } else { 4792 this.addClass(ModalSelector.SCROLL_DIVIDER); 4793 } 4794 } else { 4795 this.removeClass(ModalSelector.SCROLL_DIVIDER); 4796 } 4797 } 4798 4799 resize () { 4800 this.adjust(); 4801 this.request(this.adjust.bind(this)); 4802 } 4803 4804 adjust () { 4805 const offset = OFFSET * (this.isBreakpoint(api.core.Breakpoints.MD) ? 2 : 1); 4806 if (this.isLegacy) this.style.maxHeight = `${window.innerHeight - offset}px`; 4807 else this.style.setProperty('--modal-max-height', `${window.innerHeight - offset}px`); 4808 this.divide(); 4809 } 4810} 4811 4812api.modal = { 4813 Modal: Modal, 4814 ModalButton: ModalButton, 4815 ModalBody: ModalBody, 4816 ModalsGroup: ModalsGroup, 4817 ModalSelector: ModalSelector 4818}; 4819 4820api.internals.register(api.modal.ModalSelector.MODAL, api.modal.Modal); 4821api.internals.register(api.modal.ModalSelector.BODY, api.modal.ModalBody); 4822api.internals.register(api.core.RootSelector.ROOT, api.modal.ModalsGroup); 4823 4824const PasswordEmission = { 4825 TOGGLE: api.internals.ns.emission('password', 'toggle'), 4826 ADJUST: api.internals.ns.emission('password', 'adjust') 4827}; 4828 4829class PasswordToggle extends api.core.Instance { 4830 static get instanceClassName () { 4831 return 'PasswordToggle'; 4832 } 4833 4834 init () { 4835 this.listenClick(); 4836 this.ascend(PasswordEmission.ADJUST, this.width); 4837 this.isSwappingFont = true; 4838 this._isChecked = this.isChecked; 4839 } 4840 4841 get width () { 4842 const style = getComputedStyle(this.node.parentNode); 4843 return parseInt(style.width); 4844 } 4845 4846 get isChecked () { 4847 return this.node.checked; 4848 } 4849 4850 set isChecked (value) { 4851 this._isChecked = value; 4852 this.ascend(PasswordEmission.TOGGLE, value); 4853 } 4854 4855 handleClick () { 4856 this.isChecked = !this._isChecked; 4857 } 4858 4859 swapFont (families) { 4860 this.ascend(PasswordEmission.ADJUST, this.width); 4861 } 4862} 4863 4864class Password extends api.core.Instance { 4865 static get instanceClassName () { 4866 return 'Password'; 4867 } 4868 4869 init () { 4870 this.addAscent(PasswordEmission.TOGGLE, this.toggle.bind(this)); 4871 this.addAscent(PasswordEmission.ADJUST, this.adjust.bind(this)); 4872 } 4873 4874 toggle (value) { 4875 this.descend(PasswordEmission.TOGGLE, value); 4876 } 4877 4878 adjust (value) { 4879 this.descend(PasswordEmission.ADJUST, value); 4880 } 4881} 4882 4883const PasswordSelector = { 4884 PASSWORD: api.internals.ns.selector('password'), 4885 INPUT: api.internals.ns.selector('password__input'), 4886 LABEL: api.internals.ns.selector('password__label'), 4887 TOOGLE: `${api.internals.ns.selector('password__checkbox')} input[type="checkbox"]` 4888}; 4889 4890class PasswordInput extends api.core.Instance { 4891 static get instanceClassName () { 4892 return 'PasswordInput'; 4893 } 4894 4895 init () { 4896 this.addDescent(PasswordEmission.TOGGLE, this.toggle.bind(this)); 4897 this._isRevealed = this.hasAttribute('type') === 'password'; 4898 this.listen('keydown', this.capslock.bind(this)); // for capslock enabled 4899 this.listen('keyup', this.capslock.bind(this)); // for capslock desabled 4900 } 4901 4902 toggle (value) { 4903 this.isRevealed = value; 4904 this.setAttribute('type', value ? 'text' : 'password'); 4905 } 4906 4907 get isRevealed () { 4908 return this._isRevealed; 4909 } 4910 4911 capslock (event) { 4912 if (event && typeof event.getModifierState !== 'function') return; 4913 if (event.getModifierState('CapsLock')) { 4914 this.node.parentNode.setAttribute(api.internals.ns.attr('capslock'), ''); 4915 } else { 4916 this.node.parentNode.removeAttribute(api.internals.ns.attr('capslock')); 4917 } 4918 } 4919 4920 set isRevealed (value) { 4921 this._isRevealed = value; 4922 this.setAttribute('type', value ? 'text' : 'password'); 4923 } 4924} 4925 4926class PasswordLabel extends api.core.Instance { 4927 static get instanceClassName () { 4928 return 'PasswordLabel'; 4929 } 4930 4931 init () { 4932 this.addDescent(PasswordEmission.ADJUST, this.adjust.bind(this)); 4933 } 4934 4935 adjust (value) { 4936 const valueREM = Math.ceil(value / 16); 4937 this.node.style.paddingRight = valueREM + 'rem'; 4938 } 4939} 4940 4941api.password = { 4942 Password: Password, 4943 PasswordToggle: PasswordToggle, 4944 PasswordSelector: PasswordSelector, 4945 PasswordInput: PasswordInput, 4946 PasswordLabel: PasswordLabel 4947}; 4948 4949api.internals.register(api.password.PasswordSelector.INPUT, api.password.PasswordInput); 4950api.internals.register(api.password.PasswordSelector.PASSWORD, api.password.Password); 4951api.internals.register(api.password.PasswordSelector.TOOGLE, api.password.PasswordToggle); 4952api.internals.register(api.password.PasswordSelector.LABEL, api.password.PasswordLabel); 4953 4954const ITEM = api.internals.ns.selector('nav__item'); 4955const COLLAPSE = api.internals.ns.selector('collapse'); 4956 4957const NavigationSelector = { 4958 NAVIGATION: api.internals.ns.selector('nav'), 4959 COLLAPSE: `${ITEM} > ${COLLAPSE}, ${ITEM} > *:not(${ITEM}):not(${COLLAPSE}) > ${COLLAPSE}, ${ITEM} > *:not(${ITEM}):not(${COLLAPSE}) > *:not(${ITEM}):not(${COLLAPSE}) > ${COLLAPSE}`, 4960 COLLAPSE_LEGACY: `${ITEM} ${COLLAPSE}`, 4961 ITEM: ITEM, 4962 ITEM_RIGHT: `${ITEM}--align-right`, 4963 MENU: api.internals.ns.selector('menu'), 4964 BUTTON: api.internals.ns.selector('nav__btn'), 4965 TRANSLATE_BUTTON: api.internals.ns.selector('translate__btn') 4966}; 4967 4968class NavigationItem extends api.core.Instance { 4969 constructor () { 4970 super(); 4971 this._isRightAligned = false; 4972 } 4973 4974 static get instanceClassName () { 4975 return 'NavigationItem'; 4976 } 4977 4978 init () { 4979 this.addAscent(api.core.DisclosureEmission.ADDED, this.calculate.bind(this)); 4980 this.addAscent(api.core.DisclosureEmission.REMOVED, this.calculate.bind(this)); 4981 this.isResizing = true; 4982 this.calculate(); 4983 } 4984 4985 resize () { 4986 this.calculate(); 4987 } 4988 4989 calculate () { 4990 const collapse = this.element.getDescendantInstances(api.core.Collapse.instanceClassName, null, true)[0]; 4991 if (collapse && this.isBreakpoint(api.core.Breakpoints.LG) && collapse.element.node.matches(NavigationSelector.MENU)) { 4992 const right = this.element.node.parentElement.getBoundingClientRect().right; // todo: ne fonctionne que si la nav fait 100% du container 4993 const width = collapse.element.node.getBoundingClientRect().width; 4994 const left = this.element.node.getBoundingClientRect().left; 4995 this.isRightAligned = left + width > right; 4996 } else this.isRightAligned = false; 4997 } 4998 4999 get isRightAligned () { 5000 return this._isRightAligned; 5001 } 5002 5003 set isRightAligned (value) { 5004 if (this._isRightAligned === value) return; 5005 this._isRightAligned = value; 5006 if (value) api.internals.dom.addClass(this.element.node, NavigationSelector.ITEM_RIGHT); 5007 else api.internals.dom.removeClass(this.element.node, NavigationSelector.ITEM_RIGHT); 5008 } 5009 5010 get collapsePrimary () { 5011 const buttons = this.element.children.map(child => child.getInstance('CollapseButton')).filter(button => button !== null && (button.hasClass(NavigationSelector.BUTTON) || button.hasClass(NavigationSelector.TRANSLATE_BUTTON))); 5012 return buttons[0]; 5013 } 5014} 5015 5016const NavigationMousePosition = { 5017 NONE: -1, 5018 INSIDE: 0, 5019 OUTSIDE: 1 5020}; 5021 5022class Navigation extends api.core.CollapsesGroup { 5023 static get instanceClassName () { 5024 return 'Navigation'; 5025 } 5026 5027 init () { 5028 super.init(); 5029 this.clicked = false; 5030 this.out = false; 5031 this.addEmission(api.core.RootEmission.CLICK, this._handleRootClick.bind(this)); 5032 this.listen('mousedown', this.handleMouseDown.bind(this)); 5033 this.listenClick({ capture: true }); 5034 this.isResizing = true; 5035 } 5036 5037 validate (member) { 5038 return super.validate(member) && member.element.node.matches(api.internals.legacy.isLegacy ? NavigationSelector.COLLAPSE_LEGACY : NavigationSelector.COLLAPSE); 5039 } 5040 5041 handleMouseDown (e) { 5042 if (!this.isBreakpoint(api.core.Breakpoints.LG) || this.index === -1 || !this.current) return; 5043 this.position = this.current.node.contains(e.target) ? NavigationMousePosition.INSIDE : NavigationMousePosition.OUTSIDE; 5044 this.requestPosition(); 5045 } 5046 5047 handleClick (e) { 5048 if (e.target.matches('a, button') && !e.target.matches('[aria-controls]') && !e.target.matches(api.core.DisclosureSelector.PREVENT_CONCEAL)) { 5049 this.index = -1; 5050 } 5051 } 5052 5053 _handleRootClick (target) { 5054 if (!this.isBreakpoint(api.core.Breakpoints.LG)) return; 5055 if (!this.node.contains(target)) { 5056 this.out = true; 5057 this.requestPosition(); 5058 } 5059 } 5060 5061 requestPosition () { 5062 if (this.isRequesting) return; 5063 this.isRequesting = true; 5064 this.request(this.getPosition.bind(this)); 5065 } 5066 5067 getPosition () { 5068 if (this.out) { 5069 switch (this.position) { 5070 case NavigationMousePosition.OUTSIDE: 5071 this.index = -1; 5072 break; 5073 5074 case NavigationMousePosition.INSIDE: 5075 if (this.current && !this.current.node.contains(document.activeElement)) this.current.focus(); 5076 break; 5077 5078 default: 5079 if (this.index > -1 && !this.current.hasFocus) this.index = -1; 5080 } 5081 } 5082 5083 this.request(this.requested.bind(this)); 5084 } 5085 5086 requested () { 5087 this.position = NavigationMousePosition.NONE; 5088 this.out = false;
vendor: 9,981 bytes, lines 5089-5461
5089 this.isRequesting = false; 5090 } 5091 5092 get index () { return super.index; } 5093 5094 set index (value) { 5095 if (value === -1 && this.current && this.current.hasFocus) this.current.focus(); 5096 super.index = value; 5097 } 5098 5099 get canUngroup () { 5100 return !this.isBreakpoint(api.core.Breakpoints.LG); 5101 } 5102 5103 resize () { 5104 this.update(); 5105 } 5106} 5107 5108api.navigation = { 5109 Navigation: Navigation, 5110 NavigationItem: NavigationItem, 5111 NavigationMousePosition: NavigationMousePosition, 5112 NavigationSelector: NavigationSelector 5113}; 5114 5115api.internals.register(api.navigation.NavigationSelector.NAVIGATION, api.navigation.Navigation); 5116api.internals.register(api.navigation.NavigationSelector.ITEM, api.navigation.NavigationItem); 5117 5118/** 5119 * TabButton correspond au bouton cliquable qui change le panel 5120 * TabButton étend de DisclosureButton qui ajoute/enelve l'attribut aria-selected, 5121 * Et change l'attributte tabindex a 0 si le boutton est actif (value=true), -1 s'il n'est pas actif (value=false) 5122 */ 5123class TabButton extends api.core.DisclosureButton { 5124 constructor () { 5125 super(api.core.DisclosureType.SELECT); 5126 } 5127 5128 static get instanceClassName () { 5129 return 'TabButton'; 5130 } 5131 5132 handleClick (e) { 5133 super.handleClick(e); 5134 this.focus(); 5135 } 5136 5137 apply (value) { 5138 super.apply(value); 5139 if (this.isPrimary) { 5140 this.setAttribute('tabindex', value ? '0' : '-1'); 5141 if (value) { 5142 if (this.list) this.list.focalize(this); 5143 } 5144 } 5145 } 5146 5147 get list () { 5148 return this.element.getAscendantInstance('TabsList', 'TabsGroup'); 5149 } 5150} 5151 5152const TabSelector = { 5153 TAB: api.internals.ns.selector('tabs__tab'), 5154 GROUP: api.internals.ns.selector('tabs'), 5155 PANEL: api.internals.ns.selector('tabs__panel'), 5156 LIST: api.internals.ns.selector('tabs__list'), 5157 SHADOW: api.internals.ns.selector('tabs__shadow'), 5158 SHADOW_LEFT: api.internals.ns.selector('tabs__shadow--left'), 5159 SHADOW_RIGHT: api.internals.ns.selector('tabs__shadow--right'), 5160 PANEL_START: api.internals.ns.selector('tabs__panel--direction-start'), 5161 PANEL_END: api.internals.ns.selector('tabs__panel--direction-end') 5162}; 5163 5164const TabPanelDirection = { 5165 START: 'direction-start', 5166 END: 'direction-end', 5167 NONE: 'none' 5168}; 5169 5170/** 5171 * Tab coorespond au panel d'un élement Tabs (tab panel) 5172 * Tab étend disclosure qui ajoute/enleve le modifier --selected, 5173 * et ajoute/eleve l'attribut hidden, sur le panel 5174 */ 5175class TabPanel extends api.core.Disclosure { 5176 constructor () { 5177 super(api.core.DisclosureType.SELECT, TabSelector.PANEL, TabButton, 'TabsGroup'); 5178 this._direction = TabPanelDirection.NONE; 5179 this._isPreventingTransition = false; 5180 } 5181 5182 static get instanceClassName () { 5183 return 'TabPanel'; 5184 } 5185 5186 get direction () { 5187 return this._direction; 5188 } 5189 5190 set direction (value) { 5191 if (value === this._direction) return; 5192 switch (this._direction) { 5193 case TabPanelDirection.START: 5194 this.removeClass(TabSelector.PANEL_START); 5195 break; 5196 5197 case TabPanelDirection.END: 5198 this.removeClass(TabSelector.PANEL_END); 5199 break; 5200 5201 case TabPanelDirection.NONE: 5202 break; 5203 5204 default: 5205 return; 5206 } 5207 5208 this._direction = value; 5209 5210 switch (this._direction) { 5211 case TabPanelDirection.START: 5212 this.addClass(TabSelector.PANEL_START); 5213 break; 5214 5215 case TabPanelDirection.END: 5216 this.addClass(TabSelector.PANEL_END); 5217 break; 5218 } 5219 } 5220 5221 get isPreventingTransition () { 5222 return this._isPreventingTransition; 5223 } 5224 5225 set isPreventingTransition (value) { 5226 if (this._isPreventingTransition === value) return; 5227 if (value) this.addClass(api.internals.motion.TransitionSelector.NONE); 5228 else this.removeClass(api.internals.motion.TransitionSelector.NONE); 5229 this._isPreventingTransition = value === true; 5230 } 5231 5232 translate (direction, initial) { 5233 this.isPreventingTransition = initial; 5234 this.direction = direction; 5235 } 5236 5237 reset () { 5238 if (this.group) this.group.retrieve(true); 5239 } 5240 5241 _electPrimaries (candidates) { 5242 if (!this.group || !this.group.list) return []; 5243 return super._electPrimaries(candidates).filter(candidate => this.group.list.node.contains(candidate.node)); 5244 } 5245} 5246 5247const TabKeys = { 5248 LEFT: 'tab_keys_left', 5249 RIGHT: 'tab_keys_right', 5250 HOME: 'tab_keys_home', 5251 END: 'tab_keys_end' 5252}; 5253 5254const TabEmission = { 5255 PRESS_KEY: api.internals.ns.emission('tab', 'press_key'), 5256 LIST_HEIGHT: api.internals.ns.emission('tab', 'list_height') 5257}; 5258 5259/** 5260* TabGroup est la classe étendue de DiscosuresGroup 5261* Correspond à un objet Tabs avec plusieurs tab-button & Tab (panel) 5262*/ 5263class TabsGroup extends api.core.DisclosuresGroup { 5264 constructor () { 5265 super('TabPanel'); 5266 } 5267 5268 static get instanceClassName () { 5269 return 'TabsGroup'; 5270 } 5271 5272 init () { 5273 super.init(); 5274 5275 this.listen('transitionend', this.transitionend.bind(this)); 5276 this.addAscent(TabEmission.PRESS_KEY, this.pressKey.bind(this)); 5277 this.addAscent(TabEmission.LIST_HEIGHT, this.setListHeight.bind(this)); 5278 this.isRendering = true; 5279 } 5280 5281 getIndex (defaultIndex = 0) { 5282 super.getIndex(defaultIndex); 5283 } 5284 5285 get list () { 5286 return this.element.getDescendantInstances('TabsList', 'TabsGroup', true)[0]; 5287 } 5288 5289 setListHeight (value) { 5290 this.listHeight = value; 5291 } 5292 5293 transitionend (e) { 5294 this.isPreventingTransition = true; 5295 } 5296 5297 get buttonHasFocus () { 5298 return this.members.some(member => member.buttonHasFocus); 5299 } 5300 5301 pressKey (key) { 5302 switch (key) { 5303 case TabKeys.LEFT: 5304 this.pressLeft(); 5305 break; 5306 5307 case TabKeys.RIGHT: 5308 this.pressRight(); 5309 break; 5310 5311 case TabKeys.HOME: 5312 this.pressHome(); 5313 break; 5314 5315 case TabKeys.END: 5316 this.pressEnd(); 5317 break; 5318 } 5319 } 5320 5321 /** 5322 * Selectionne l'element suivant de la liste si on est sur un bouton 5323 * Si on est à la fin on retourne au début 5324 */ 5325 pressRight () { 5326 if (this.buttonHasFocus) { 5327 if (this.index < this.length - 1) { 5328 this.index++; 5329 } else { 5330 this.index = 0; 5331 } 5332 5333 this.focus(); 5334 } 5335 }; 5336 5337 /** 5338 * Selectionne l'element précédent de la liste si on est sur un bouton 5339 * Si on est au debut retourne a la fin 5340 */ 5341 pressLeft () { 5342 if (this.buttonHasFocus) { 5343 if (this.index > 0) { 5344 this.index--; 5345 } else { 5346 this.index = this.length - 1; 5347 } 5348 5349 this.focus(); 5350 } 5351 }; 5352 5353 /** 5354 * Selectionne le permier element de la liste si on est sur un bouton 5355 */ 5356 pressHome () { 5357 if (this.buttonHasFocus) { 5358 this.index = 0; 5359 this.focus(); 5360 } 5361 }; 5362 5363 /** 5364 * Selectionne le dernier element de la liste si on est sur un bouton 5365 */ 5366 pressEnd () { 5367 if (this.buttonHasFocus) { 5368 this.index = this.length - 1; 5369 this.focus(); 5370 } 5371 }; 5372 5373 focus () { 5374 if (this.current) { 5375 this.current.focus(); 5376 } 5377 } 5378 5379 apply () { 5380 for (let i = 0; i < this._index; i++) this.members[i].translate(TabPanelDirection.START); 5381 if (this.current) this.current.translate(TabPanelDirection.NONE); 5382 for (let i = this._index + 1; i < this.length; i++) this.members[i].translate(TabPanelDirection.END); 5383 this.isPreventingTransition = false; 5384 } 5385 5386 get isPreventingTransition () { 5387 return this._isPreventingTransition; 5388 } 5389 5390 set isPreventingTransition (value) { 5391 if (this._isPreventingTransition === value) return; 5392 if (value) this.addClass(api.internals.motion.TransitionSelector.NONE); 5393 else this.removeClass(api.internals.motion.TransitionSelector.NONE); 5394 this._isPreventingTransition = value === true; 5395 } 5396 5397 render () { 5398 if (this.current === null) return; 5399 this.node.scrollTop = 0; 5400 this.node.scrollLeft = 0; 5401 const paneHeight = Math.round(this.current.node.offsetHeight); 5402 if (this.panelHeight === paneHeight) return; 5403 this.panelHeight = paneHeight; 5404 this.style.setProperty('--tabs-height', (this.panelHeight + this.listHeight) + 'px'); 5405 } 5406} 5407 5408const FOCALIZE_OFFSET = 16; 5409const SCROLL_OFFSET$1 = 16; // valeur en px du scroll avant laquelle le shadow s'active ou se desactive 5410 5411class TabsList extends api.core.Instance { 5412 static get instanceClassName () { 5413 return 'TabsList'; 5414 } 5415 5416 init () { 5417 this.listen('scroll', this.scroll.bind(this)); 5418 this.listenKey(api.core.KeyCodes.RIGHT, this.ascend.bind(this, TabEmission.PRESS_KEY, TabKeys.RIGHT), true, true); 5419 this.listenKey(api.core.KeyCodes.LEFT, this.ascend.bind(this, TabEmission.PRESS_KEY, TabKeys.LEFT), true, true); 5420 this.listenKey(api.core.KeyCodes.HOME, this.ascend.bind(this, TabEmission.PRESS_KEY, TabKeys.HOME), true, true); 5421 this.listenKey(api.core.KeyCodes.END, this.ascend.bind(this, TabEmission.PRESS_KEY, TabKeys.END), true, true); 5422 this.isResizing = true; 5423 } 5424 5425 focalize (btn) { 5426 const btnRect = btn.getRect(); 5427 const listRect = this.getRect(); 5428 const actualScroll = this.node.scrollLeft; 5429 if (btnRect.left < listRect.left) this.node.scrollTo(actualScroll - listRect.left + btnRect.left - FOCALIZE_OFFSET, 0); 5430 else if (btnRect.right > listRect.right) this.node.scrollTo(actualScroll - listRect.right + btnRect.right + FOCALIZE_OFFSET, 0); 5431 } 5432 5433 get isScrolling () { 5434 return this._isScrolling; 5435 } 5436 5437 set isScrolling (value) { 5438 if (this._isScrolling === value) return; 5439 this._isScrolling = value; 5440 this.apply(); 5441 } 5442 5443 apply () { 5444 if (this._isScrolling) { 5445 this.addClass(TabSelector.SHADOW); 5446 this.scroll(); 5447 } else { 5448 this.removeClass(TabSelector.SHADOW_RIGHT); 5449 this.removeClass(TabSelector.SHADOW_LEFT); 5450 this.removeClass(TabSelector.SHADOW); 5451 } 5452 } 5453 5454 /* ajoute la classe fr-table__shadow-left ou fr-table__shadow-right sur fr-table en fonction d'une valeur de scroll et du sens (right, left) */ 5455 scroll () { 5456 const scrollLeft = this.node.scrollLeft; 5457 const isMin = scrollLeft <= SCROLL_OFFSET$1; 5458 const max = this.node.scrollWidth - this.node.clientWidth - SCROLL_OFFSET$1; 5459 5460 const isMax = Math.abs(scrollLeft) >= max; 5461 const isRtl = document.documentElement.getAttribute('dir')
vendor: 15,059 bytes, lines 5461-6046
5461 === 'rtl'; 5462 const minSelector = isRtl ? TabSelector.SHADOW_RIGHT : TabSelector.SHADOW_LEFT; 5463 const maxSelector = isRtl ? TabSelector.SHADOW_LEFT : TabSelector.SHADOW_RIGHT; 5464 5465 if (isMin) { 5466 this.removeClass(minSelector); 5467 } else { 5468 this.addClass(minSelector); 5469 } 5470 5471 if (isMax) { 5472 this.removeClass(maxSelector); 5473 } else { 5474 this.addClass(maxSelector); 5475 } 5476 } 5477 5478 resize () { 5479 this.isScrolling = this.node.scrollWidth > this.node.clientWidth + SCROLL_OFFSET$1; 5480 const height = this.getRect().height; 5481 this.setProperty('--tabs-list-height', `${height}px`); 5482 this.ascend(TabEmission.LIST_HEIGHT, height); 5483 } 5484 5485 dispose () { 5486 this.isScrolling = false; 5487 } 5488} 5489 5490api.tab = { 5491 TabPanel: TabPanel, 5492 TabButton: TabButton, 5493 TabsGroup: TabsGroup, 5494 TabsList: TabsList, 5495 TabSelector: TabSelector, 5496 TabEmission: TabEmission 5497}; 5498 5499api.internals.register(api.tab.TabSelector.PANEL, api.tab.TabPanel); 5500api.internals.register(api.tab.TabSelector.GROUP, api.tab.TabsGroup); 5501api.internals.register(api.tab.TabSelector.LIST, api.tab.TabsList); 5502 5503const TagEvent = { 5504 DISMISS: api.internals.ns.event('dismiss') 5505}; 5506 5507class TagDismissible extends api.core.Instance { 5508 static get instanceClassName () { 5509 return 'TagDismissible'; 5510 } 5511 5512 init () { 5513 this.listenClick(); 5514 } 5515 5516 handleClick () { 5517 this.focusClosest(); 5518 5519 switch (api.mode) { 5520 case api.Modes.ANGULAR: 5521 case api.Modes.REACT: 5522 case api.Modes.VUE: 5523 this.request(this.verify.bind(this)); 5524 break; 5525 5526 default: 5527 this.remove(); 5528 } 5529 5530 this.dispatch(TagEvent.DISMISS); 5531 } 5532 5533 verify () { 5534 if (document.body.contains(this.node)) this.warn(`a TagDismissible has just been dismissed and should be removed from the dom. In ${api.mode} mode, the api doesn't handle dom modification. An event ${TagEvent.DISMISS} is dispatched by the element to trigger the removal`); 5535 } 5536} 5537 5538const TagSelector = { 5539 PRESSABLE: `${api.internals.ns.selector('tag')}[aria-pressed]`, 5540 DISMISSIBLE: `${api.internals.ns.selector('tag--dismiss')}` 5541}; 5542 5543api.tag = { 5544 TagDismissible: TagDismissible, 5545 TagSelector: TagSelector, 5546 TagEvent: TagEvent 5547}; 5548 5549api.internals.register(api.tag.TagSelector.PRESSABLE, api.core.Toggle); 5550api.internals.register(api.tag.TagSelector.DISMISSIBLE, api.tag.TagDismissible); 5551 5552const TRANSCRIPTION = api.internals.ns.selector('transcription'); 5553 5554const TranscriptionSelector = { 5555 TRANSCRIPTION: TRANSCRIPTION, 5556 BUTTON: `${TRANSCRIPTION}__btn` 5557}; 5558 5559class Transcription extends api.core.Instance { 5560 static get instanceClassName () { 5561 return 'Transcription'; 5562 } 5563 5564 get collapsePrimary () { 5565 const buttons = this.element.children.map(child => child.getInstance('CollapseButton')).filter(button => button !== null && button.hasClass(TranscriptionSelector.BUTTON)); 5566 return buttons[0]; 5567 } 5568} 5569 5570api.transcription = { 5571 Transcription: Transcription, 5572 TranscriptionSelector: TranscriptionSelector 5573}; 5574 5575api.internals.register(api.transcription.TranscriptionSelector.TRANSCRIPTION, api.transcription.Transcription); 5576 5577class TileDownload extends api.core.Instance { 5578 static get instanceClassName () { 5579 return 'TileDownload'; 5580 } 5581 5582 init () { 5583 this.addAscent(api.core.AssessEmission.UPDATE, details => { 5584 this.descend(api.core.AssessEmission.UPDATE, details); 5585 }); 5586 this.addAscent(api.core.AssessEmission.ADDED, () => { 5587 this.descend(api.core.AssessEmission.ADDED); 5588 }); 5589 } 5590} 5591 5592const TileSelector = { 5593 DOWNLOAD: api.internals.ns.selector('tile--download'), 5594 DOWNLOAD_DETAIL: `${api.internals.ns.selector('tile--download')} ${api.internals.ns.selector('tile__detail')}` 5595}; 5596 5597api.tile = { 5598 TileSelector: TileSelector, 5599 TileDownload: TileDownload 5600}; 5601 5602api.internals.register(api.tile.TileSelector.DOWNLOAD, api.tile.TileDownload); 5603api.internals.register(api.tile.TileSelector.DOWNLOAD_DETAIL, api.core.AssessDetail); 5604 5605const RangeSelector = { 5606 RANGE: api.internals.ns.selector('range'), 5607 RANGE_SM: api.internals.ns.selector('range--sm'), 5608 RANGE_STEP: api.internals.ns.selector('range--step'), 5609 RANGE_DOUBLE: api.internals.ns.selector('range--double'), 5610 RANGE_DOUBLE_STEP: api.internals.ns.selector('range--double') + api.internals.ns.selector('range--step'), 5611 RANGE_INPUT: api.internals.ns.selector('range input[type=range]:nth-of-type(1)'), 5612 RANGE_INPUT2: `${api.internals.ns.selector('range--double')} input[type=range]:nth-of-type(2)`, 5613 RANGE_OUTPUT: api.internals.ns.selector('range__output'), 5614 RANGE_MIN: api.internals.ns.selector('range__min'), 5615 RANGE_MAX: api.internals.ns.selector('range__max'), 5616 RANGE_PREFIX: api.internals.ns.attr('prefix'), 5617 RANGE_SUFFIX: api.internals.ns.attr('suffix') 5618}; 5619 5620const RangeEmission = { 5621 VALUE: api.internals.ns.emission('range', 'value'), 5622 VALUE2: api.internals.ns.emission('range', 'value2'), 5623 OUTPUT: api.internals.ns.emission('range', 'output'), 5624 CONSTRAINTS: api.internals.ns.emission('range', 'constraints'), 5625 MIN: api.internals.ns.emission('range', 'min'), 5626 MAX: api.internals.ns.emission('range', 'max'), 5627 STEP: api.internals.ns.emission('range', 'step'), 5628 PREFIX: api.internals.ns.emission('range', 'prefix'), 5629 SUFFIX: api.internals.ns.emission('range', 'suffix'), 5630 DISABLED: api.internals.ns.emission('range', 'disabled'), 5631 ENABLE_POINTER: api.internals.ns.emission('range', 'enable_pointer') 5632}; 5633 5634class RangeModel { 5635 constructor () { 5636 this._width = 0; 5637 this._min = 0; 5638 this._max = 0; 5639 this._value = 0; 5640 this._thumbSize = 24; 5641 this._innerWidth = 0; 5642 this._prefix = ''; 5643 this._suffix = ''; 5644 this._background = {}; 5645 } 5646 5647 configure (model) { 5648 if (!model) return; 5649 this._prefix = model._prefix; 5650 this._suffix = model._suffix; 5651 this._width = model.width; 5652 this.setConstraints(model._constraints); 5653 this.value = model.value; 5654 this.update(); 5655 } 5656 5657 setPrefix (value) { 5658 this._prefix = value !== null ? value : ''; 5659 } 5660 5661 setSuffix (value) { 5662 this._suffix = value !== null ? value : ''; 5663 } 5664 5665 _decorate (value) { 5666 return `${this._prefix}${value}${this._suffix}`; 5667 } 5668 5669 get width () { 5670 return this._width; 5671 } 5672 5673 set width (value) { 5674 this._width = value; 5675 } 5676 5677 get isSm () { 5678 return this._isSm; 5679 } 5680 5681 set isSm (value) { 5682 if (this._isSm === value) return; 5683 this._isSm = value; 5684 this.setThumbSize(value ? 16 : 24); 5685 this.update(); 5686 } 5687 5688 setThumbSize (value, mult = 1) { 5689 this._thumbSize = value; 5690 this._innerPadding = value * mult; 5691 } 5692 5693 get textValue () { 5694 return this._decorate(this._value); 5695 } 5696 5697 get value () { 5698 return this._value; 5699 } 5700 5701 set value (value) { 5702 this._value = value; 5703 } 5704 5705 get outputX () { 5706 return this._outputX; 5707 } 5708 5709 setConstraints (constraints) { 5710 this._constraints = constraints; 5711 this._min = constraints.min; 5712 this._max = constraints.max; 5713 this._step = constraints.step; 5714 this._rangeWidth = constraints.rangeWidth; 5715 } 5716 5717 get min () { 5718 return this._min; 5719 } 5720 5721 get textMin () { 5722 return this._decorate(this._min); 5723 } 5724 5725 get max () { 5726 return this._max; 5727 } 5728 5729 get textMax () { 5730 return this._decorate(this._max); 5731 } 5732 5733 get step () { 5734 return this._step; 5735 } 5736 5737 get output () { 5738 return { 5739 text: this.textValue, 5740 transform: `translateX(${this._translateX}px) translateX(-${this._centerPercent}%)` 5741 }; 5742 } 5743 5744 _getRatio (value) { 5745 return (value - this._min) / this._rangeWidth; 5746 } 5747 5748 get progress () { 5749 return this._progress; 5750 } 5751 5752 update () { 5753 this._update(); 5754 } 5755 5756 _update () { 5757 this._innerWidth = this._width - this._innerPadding; 5758 const ratio = this._getRatio(this._value); 5759 this._translateX = ratio * this._width; 5760 this._centerPercent = ratio * 100; 5761 this._progress = { 5762 right: `${(this._innerWidth * ratio + this._innerPadding * 0.5).toFixed(2)}px` 5763 }; 5764 } 5765} 5766 5767class RangeModelStep extends RangeModel { 5768 get stepWidth () { 5769 return `${this._stepWidth.toFixed(3)}px`; 5770 } 5771 5772 _update () { 5773 super._update(); 5774 const steps = this._rangeWidth / this._step; 5775 this._stepWidth = this._innerWidth / steps; 5776 if (this._stepWidth < 1 || !isFinite(this._stepWidth)) this._stepWidth = 4; 5777 while (this._stepWidth < 4) this._stepWidth *= 2; 5778 } 5779} 5780 5781class RangeModelDouble extends RangeModel { 5782 get value2 () { 5783 return this._value; 5784 } 5785 5786 set value2 (value) { 5787 if (this._value2 === value) return; 5788 this._value2 = value; 5789 this.update(); 5790 } 5791 5792 get textValue () { 5793 return `${this._decorate(this._value)} - ${this._decorate(this._value2)}`; 5794 } 5795 5796 setThumbSize (value) { 5797 super.setThumbSize(value, 2); 5798 } 5799 5800 _update () { 5801 super._update(); 5802 const ratio = this._getRatio((this._value + this._value2) * 0.5); 5803 this._translateX = ratio * this._width; 5804 this._centerPercent = ratio * 100; 5805 const ratio1 = this._getRatio(this._value); 5806 const ratio2 = this._getRatio(this._value2); 5807 this._progress = { 5808 left: `${(this._innerWidth * ratio1 + this._innerPadding * 0.25).toFixed(2)}px`, 5809 right: `${(this._innerWidth * ratio2 + this._innerPadding * 0.75).toFixed(2)}px` 5810 }; 5811 } 5812} 5813 5814class RangeModelDoubleStep extends RangeModelDouble { 5815 get stepWidth () { 5816 return `${this._stepWidth.toFixed(3)}px`; 5817 } 5818 5819 _update () { 5820 super._update(); 5821 const steps = this._rangeWidth / this._step; 5822 this._stepWidth = this._innerWidth / steps; 5823 if (this._stepWidth < 4) this._stepWidth *= Math.ceil(4 / this._stepWidth); 5824 } 5825} 5826 5827const RangeTypes = { 5828 STEP: 'step', 5829 DOUBLE: 'double', 5830 DOUBLE_STEP: 'double-step', 5831 DEFAULT: 'default' 5832}; 5833 5834class Range extends api.core.Instance { 5835 static get instanceClassName () { 5836 return 'Range'; 5837 } 5838 5839 init () { 5840 this._retrieveType(); 5841 this._retrieveSize(); 5842 if (this.isLegacy) { 5843 this.isResizing = true; 5844 this.isMouseMoving = true; 5845 } else { 5846 this._observer = new ResizeObserver(this.resize.bind(this)); 5847 this._observer.observe(this.node); 5848 } 5849 5850 this.addAscent(RangeEmission.CONSTRAINTS, this.setConstraints.bind(this)); 5851 this.addAscent(RangeEmission.VALUE, this.setValue.bind(this)); 5852 this.addAscent(RangeEmission.VALUE2, this.setValue2.bind(this)); 5853 if (this.getAttribute(RangeSelector.RANGE_PREFIX)) this.setPrefix(this.getAttribute(RangeSelector.RANGE_PREFIX)); 5854 if (this.getAttribute(RangeSelector.RANGE_SUFFIX)) this.setSuffix(this.getAttribute(RangeSelector.RANGE_SUFFIX)); 5855 this.update(); 5856 } 5857 5858 _retrieveType () { 5859 switch (true) { 5860 case this.matches(RangeSelector.RANGE_DOUBLE_STEP): 5861 this.type = RangeTypes.DOUBLE; 5862 break; 5863 5864 case this.matches(RangeSelector.RANGE_DOUBLE): 5865 this.type = RangeTypes.DOUBLE; 5866 break; 5867 5868 case this.matches(RangeSelector.RANGE_STEP): 5869 this.type = RangeTypes.STEP; 5870 break; 5871 5872 default: 5873 this.type = RangeTypes.DEFAULT; 5874 } 5875 } 5876 5877 set type (value) { 5878 if (this._type === value) return; 5879 this._type = value; 5880 5881 const oldModel = this._model; 5882 5883 switch (this._type) { 5884 case RangeTypes.DOUBLE_STEP: 5885 this._model = new RangeModelDoubleStep(); 5886 break; 5887 5888 case RangeTypes.DOUBLE: 5889 this._model = new RangeModelDouble(); 5890 break; 5891 5892 case RangeTypes.STEP: 5893 this._model = new RangeModelStep(); 5894 break; 5895 5896 default: 5897 this._model = new RangeModel(); 5898 } 5899 5900 this._model.configure(oldModel); 5901 } 5902 5903 get type () { 5904 return this._type; 5905 } 5906 5907 _retrieveSize () { 5908 this._model.isSm = this.matches(RangeSelector.RANGE_SM); 5909 } 5910 5911 resize () { 5912 this._retrieveWidth(); 5913 this.update(); 5914 } 5915 5916 _retrieveWidth () { 5917 this._model.width = this.getRect().width; 5918 } 5919 5920 setValue (value) { 5921 this._model.value = value; 5922 switch (this._type) { 5923 case RangeTypes.DOUBLE_STEP: 5924 case RangeTypes.DOUBLE: 5925 this.descend(RangeEmission.VALUE, value); 5926 break; 5927 } 5928 this.update(); 5929 } 5930 5931 setValue2 (value) { 5932 this._model.value2 = value; 5933 this.descend(RangeEmission.VALUE2, value); 5934 this.update(); 5935 } 5936 5937 setConstraints (constraints) { 5938 this._model.setConstraints(constraints); 5939 this.update(); 5940 this.descend(RangeEmission.CONSTRAINTS, constraints); 5941 } 5942 5943 setPrefix (value) { 5944 this._model.setPrefix(value); 5945 this.update(); 5946 } 5947 5948 setSuffix (value) { 5949 this._model.setSuffix(value); 5950 this.update(); 5951 } 5952 5953 mutate (attributesNames) { 5954 switch (true) { 5955 case attributesNames.includes('class'): 5956 this._retrieveType(); 5957 this._retrieveSize(); 5958 break; 5959 5960 case attributesNames.includes(RangeSelector.RANGE_PREFIX): 5961 case attributesNames.includes(RangeSelector.RANGE_SUFFIX): 5962 this._model.setPrefix(this.getAttribute(RangeSelector.RANGE_PREFIX)); 5963 this._model.setSuffix(this.getAttribute(RangeSelector.RANGE_SUFFIX)); 5964 this.update(); 5965 break; 5966 } 5967 } 5968 5969 update () { 5970 this._model.update(); 5971 this.descend(RangeEmission.OUTPUT, this._model.output); 5972 this.descend(RangeEmission.MIN, this._model.textMin); 5973 this.descend(RangeEmission.MAX, this._model.textMax); 5974 const progress = this._model.progress; 5975 if (progress.left) { 5976 this.style.setProperty('--progress-left', progress.left); 5977 } else { 5978 this.style.removeProperty('--progress-left'); 5979 } 5980 if (progress.right) { 5981 this.style.setProperty('--progress-right', progress.right); 5982 if (this.isLegacy) { 5983 if (progress.left) { 5984 this.style.setProperty('background-position-x', progress.left); 5985 this.style.setProperty('background-size', `${parseFloat(progress.right) - parseFloat(progress.left)}px ${this._model.isSm ? '8px' : '12px'}`); 5986 } 5987 } 5988 } else { 5989 this.style.removeProperty('--progress-right'); 5990 if (this.isLegacy) { 5991 this.style.removeProperty('background-size'); 5992 this.style.removeProperty('background-position-x'); 5993 } 5994 } 5995 if (this._model.stepWidth) this.style.setProperty('--step-width', this._model.stepWidth); 5996 else this.style.removeProperty('--step-width'); 5997 } 5998 5999 mouseMove (point) { 6000 if (this._type !== RangeTypes.DOUBLE && this._type !== RangeTypes.DOUBLE_STEP) return; 6001 const x = point.x - this.getRect().left; 6002 this.descend(RangeEmission.ENABLE_POINTER, (parseFloat(this._model.progress.right) - parseFloat(this._model.progress.left)) / 2 + parseFloat(this._model.progress.left) < x ? 2 : 1); 6003 } 6004 6005 dispose () { 6006 this._observer.disconnect(); 6007 } 6008} 6009 6010class RangeConstraints { 6011 constructor (node) { 6012 this._min = isNaN(node.min) ? 0 : node.min; 6013 this._max = isNaN(node.max) ? 100 : node.max; 6014 this._step = isNaN(node.step) ? 1 : node.step; 6015 this._rangeWidth = this._max - this._min; 6016 } 6017 6018 get min () { 6019 return this._min; 6020 } 6021 6022 get max () { 6023 return this._max; 6024 } 6025 6026 get step () { 6027 return this._step; 6028 } 6029 6030 get rangeWidth () { 6031 return this._rangeWidth; 6032 } 6033 6034 test (min, max, step) { 6035 return this._min === min && this._max === max && this._step === step; 6036 } 6037} 6038 6039class RangeInput extends api.core.Instance { 6040 static get instanceClassName () { 6041 return 'RangeInput'; 6042 } 6043 6044 init () { 6045 this._init(); 6046 this.
vendor: 3,430 bytes, lines 6046-6174
6046node.value = this.getAttribute('value'); 6047 this._changing = this.change.bind(this); 6048 this._listenerType = this.isLegacy ? 'change' : 'input'; 6049 this.listen(this._listenerType, this._changing); 6050 if (this.isLegacy) this.addDescent(RangeEmission.ENABLE_POINTER, this._enablePointer.bind(this)); 6051 this.change(); 6052 } 6053 6054 _init () { 6055 this._pointerId = 1; 6056 this.request(() => { 6057 if (!this.hasAttribute('min')) this.setAttribute('min', 0); 6058 this.ascend(RangeEmission.CONSTRAINTS, new RangeConstraints(this.node)); 6059 this.ascend(RangeEmission.DISABLED, this.node.disabled); 6060 }); 6061 6062 this.addDescent(RangeEmission.VALUE2, this.setValue.bind(this)); 6063 } 6064 6065 _enablePointer (pointerId) { 6066 const isEnabled = pointerId === this._pointerId; 6067 if (this._isPointerEnabled === isEnabled) return; 6068 this._isPointerEnabled = isEnabled; 6069 if (isEnabled) this.style.removeProperty('pointer-events'); 6070 else this.style.setProperty('pointer-events', 'none'); 6071 } 6072 6073 setValue (value) { 6074 if (parseFloat(this.node.value) > value) { 6075 this.node.value = value; 6076 this.dispatch('change', undefined, true); 6077 this.change(); 6078 } 6079 } 6080 6081 change () { 6082 this.ascend(RangeEmission.VALUE, parseFloat(this.node.value)); 6083 } 6084 6085 mutate (attributesNames) { 6086 if (attributesNames.includes('disabled')) this.ascend(RangeEmission.DISABLED, this.node.disabled); 6087 if (attributesNames.includes('min') || attributesNames.includes('max') || attributesNames.includes('step')) { 6088 this.ascend(RangeEmission.CONSTRAINTS, new RangeConstraints(this.node)); 6089 this.change(); 6090 } 6091 } 6092 6093 dispose () { 6094 if (this._listenerType) this.unlisten(this._listenerType, this._changing); 6095 } 6096} 6097 6098class RangeInput2 extends RangeInput { 6099 static get instanceClassName () { 6100 return 'RangeInput2'; 6101 } 6102 6103 _init () { 6104 this._pointerId = 2; 6105 this.addDescent(RangeEmission.CONSTRAINTS, this.setConstraints.bind(this)); 6106 this.addDescent(RangeEmission.VALUE, this.setValue.bind(this)); 6107 } 6108 6109 setValue (value) { 6110 if (parseFloat(this.node.value) < value) { 6111 this.node.value = value; 6112 this.dispatch('change', undefined, true); 6113 this.change(); 6114 } 6115 } 6116 6117 change () { 6118 this.ascend(RangeEmission.VALUE2, parseFloat(this.node.value)); 6119 } 6120 6121 setConstraints (constraints) { 6122 this.node.min = constraints.min; 6123 this.node.max = constraints.max; 6124 this.node.step = constraints.step; 6125 this.change(); 6126 } 6127 6128 mutate (attributesNames) {} 6129} 6130 6131class RangeOutput extends api.core.Instance { 6132 static get instanceClassName () { 6133 return 'RangeOutput'; 6134 } 6135 6136 init () { 6137 this.addDescent(RangeEmission.OUTPUT, this.change.bind(this)); 6138 } 6139 6140 change (data) { 6141 this.node.innerText = data.text; 6142 this.node.style.transform = data.transform; 6143 } 6144} 6145 6146class RangeLimit extends api.core.Instance { 6147 static get instanceClassName () { 6148 return 'RangeLimit'; 6149 } 6150 6151 init () { 6152 switch (true) { 6153 case this.matches(RangeSelector.RANGE_MIN): 6154 this.addDescent(RangeEmission.MIN, this.change.bind(this)); 6155 break; 6156 6157 case this.matches(RangeSelector.RANGE_MAX): 6158 this.addDescent(RangeEmission.MAX, this.change.bind(this)); 6159 break; 6160 } 6161 } 6162 6163 change (text) { 6164 this.node.innerText = text; 6165 } 6166} 6167 6168api.range = { 6169 Range: Range, 6170 RangeInput: RangeInput, 6171 RangeInput2: RangeInput2, 6172 RangeOutput: RangeOutput, 6173 RangeLimit: RangeLimit, 6174 RangeEmission: RangeEmission,
6175 RangeSelector: RangeSelector 6176}; 6177 6178api.internals.register(api.range.RangeSelector.RANGE, api.range.Range); 6179api.internals.register(api.range.RangeSelector.RANGE_INPUT, api.range.RangeInput); 6180api.internals.register(api.range.RangeSelector.RANGE_INPUT2, api.range.RangeInput2); 6181api.internals.register(api.range.RangeSelector.RANGE_OUTPUT, api.range.RangeOutput); 6182api.internals.register(api.range.RangeSelector.RANGE_MIN, api.range.RangeLimit); 6183api.internals.register(api.range.RangeSelector.RANGE_MAX, api.range.RangeLimit); 6184 6185const HeaderSelector = { 6186 HEADER: api.internals.ns.selector('header'), 6187 TOOLS_LINKS: api.internals.ns.selector('header__tools-links'), 6188 MENU_LINKS: api.internals.ns.selector('header__menu-links'), 6189 BUTTONS: `${api.internals.ns.selector('header__tools-links')} ${api.internals.ns.selector('btns-group')}, ${api.internals.ns.selector('header__tools-links')} ${api.internals.ns.selector('links-group')}`, 6190 MODALS: `${api.internals.ns.selector('header__search')}${api.internals.ns.selector('modal')}, ${api.internals.ns.selector('header__menu')}${api.internals.ns.selector('modal')}` 6191}; 6192 6193class HeaderLinks extends api.core.Instance { 6194 static get instanceClassName () { 6195 return 'HeaderLinks'; 6196 } 6197 6198 init () { 6199 const header = this.queryParentSelector(HeaderSelector.HEADER); 6200 this.toolsLinks = header.querySelector(HeaderSelector.TOOLS_LINKS); 6201 this.menuLinks = header.querySelector(HeaderSelector.MENU_LINKS); 6202 const copySuffix = '-mobile'; 6203 6204 const toolsHtml = this.toolsLinks.innerHTML.replace(/ +/g, ' '); 6205 const menuHtml = this.menuLinks.innerHTML.replace(/ +/g, ' '); 6206 // Pour éviter de dupliquer des id, on ajoute un suffixe aux id et aria-controls duppliqués. 6207 let toolsHtmlIdList = toolsHtml.match(/id="(.*?)"/gm) || []; 6208 6209 // on a besoin d'échapper les backslash dans la chaine de caractère 6210 // eslint-disable-next-line no-useless-escape 6211 toolsHtmlIdList = toolsHtmlIdList.map(element => element.replace('id=\"', '').replace('\"', '')); 6212 6213 const toolsHtmlAriaControlList = toolsHtml.match(/aria-controls="(.*?)"/gm); 6214 let toolsHtmlDuplicateId = toolsHtml.replace(/id="(.*?)"/gm, 'id="$1' + copySuffix + '"'); 6215 if (toolsHtmlAriaControlList) { 6216 for (const element of toolsHtmlAriaControlList) { 6217 const ariaControlsValue = element.replace('aria-controls="', '').replace('"', ''); 6218 if (toolsHtmlIdList.includes(ariaControlsValue)) { 6219 toolsHtmlDuplicateId = toolsHtmlDuplicateId.replace(`aria-controls="${ariaControlsValue}"`, `aria-controls="${ariaControlsValue + copySuffix}"`); 6220 } } 6221 } 6222 6223 if (toolsHtmlDuplicateId === menuHtml) return; 6224 6225 switch (api.mode) { 6226 case api.Modes.ANGULAR: 6227 case api.Modes.REACT: 6228 case api.Modes.VUE: 6229 this.warn(`header__tools-links content is different from header__menu-links content. 6230As you're using a dynamic framework, you should handle duplication of this content yourself, please refer to documentation: 6231${api.header.doc}`); 6232 break; 6233 6234 default: 6235 this.menuLinks.innerHTML = toolsHtmlDuplicateId; 6236 } 6237 } 6238} 6239 6240class HeaderModal extends api.core.Instance { 6241 static get instanceClassName () { 6242 return 'HeaderModal'; 6243 } 6244 6245 init () { 6246 this.isResizing = true; 6247 } 6248 6249 resize () { 6250 if (this.isBreakpoint(api.core.Breakpoints.LG)) this.deactivateModal(); 6251 else this.activateModal(); 6252 } 6253 6254 activateModal () { 6255 const modal = this.element.getInstance('Modal'); 6256 if (!modal) return; 6257 modal.isEnabled = true; 6258 this.listenClick({ capture: true }); 6259 } 6260 6261 deactivateModal () { 6262 const modal = this.element.getInstance('Modal'); 6263 if (!modal) return; 6264 modal.conceal(); 6265 modal.isEnabled = false; 6266 this.unlistenClick({ capture: true }); 6267 } 6268 6269 handleClick (e) { 6270 if (e.target.matches('a, button') && !e.target.matches('[aria-controls]') && !e.target.matches(api.core.DisclosureSelector.PREVENT_CONCEAL)) { 6271 const modal = this.element.getInstance('Modal'); 6272 modal.conceal(); 6273 } 6274 } 6275} 6276 6277api.header = { 6278 HeaderLinks: HeaderLinks, 6279 HeaderModal: HeaderModal, 6280 HeaderSelector: HeaderSelector, 6281 doc: 'https://www.systeme-de-design.gouv.fr/elements-d-interface/composants/en-tete' 6282}; 6283 6284api.internals.register(api.header.HeaderSelector.TOOLS_LINKS, api.header.HeaderLinks); 6285api.internals.register(api.header.HeaderSelector.MODALS, api.header.HeaderModal); 6286 6287const DisplaySelector = { 6288 DISPLAY: api.internals.ns.selector('display'), 6289 RADIO_BUTTONS: `input[name="${api.internals.ns('radios-theme')}"]`, 6290 FIELDSET: api.internals.ns.selector('fieldset') 6291}; 6292 6293class Display extends api.core.Instance { 6294 static get instanceClassName () { 6295 return 'Display'; 6296 } 6297 6298 init () { 6299 this.radios = this.querySelectorAll(DisplaySelector.RADIO_BUTTONS); 6300 6301 if (api.scheme) { 6302 this.changing = this.change.bind(this); 6303 for (const radio of this.radios) radio.addEventListener('change', this.changing);
vendor: 6,489 bytes, lines 6304-6536
6304 this.addDescent(api.scheme.SchemeEmission.SCHEME, this.apply.bind(this)); 6305 this.ascend(api.scheme.SchemeEmission.ASK); 6306 } else { 6307 this.querySelector(DisplaySelector.FIELDSET).setAttribute('disabled', ''); 6308 } 6309 } 6310 6311 get scheme () { 6312 return this._scheme; 6313 } 6314 6315 set scheme (value) { 6316 if (this._scheme === value || !api.scheme) return; 6317 switch (value) { 6318 case api.scheme.SchemeValue.SYSTEM: 6319 case api.scheme.SchemeValue.LIGHT: 6320 case api.scheme.SchemeValue.DARK: 6321 this._scheme = value; 6322 for (const radio of this.radios) { 6323 radio.checked = radio.value === value; 6324 } 6325 this.ascend(api.scheme.SchemeEmission.SCHEME, value); 6326 break; 6327 } 6328 } 6329 6330 change () { 6331 for (const radio of this.radios) { 6332 if (radio.checked) { 6333 this.scheme = radio.value; 6334 return; 6335 } 6336 } 6337 } 6338 6339 apply (value) { 6340 this.scheme = value; 6341 } 6342 6343 dispose () { 6344 for (const radio of this.radios) radio.removeEventListener('change', this.changing); 6345 } 6346} 6347 6348api.display = { 6349 Display: Display, 6350 DisplaySelector: DisplaySelector 6351}; 6352 6353api.internals.register(api.display.DisplaySelector.DISPLAY, api.display.Display); 6354 6355const TableEmission = { 6356 SCROLLABLE: api.internals.ns.emission('table', 'scrollable'), 6357 CHANGE: api.internals.ns.emission('table', 'change'), 6358 CAPTION_HEIGHT: api.internals.ns.emission('table', 'captionheight'), 6359 CAPTION_WIDTH: api.internals.ns.emission('table', 'captionwidth') 6360}; 6361 6362class Table extends api.core.Instance { 6363 static get instanceClassName () { 6364 return 'Table'; 6365 } 6366 6367 init () { 6368 this.addAscent(TableEmission.CAPTION_HEIGHT, this.setCaptionHeight.bind(this)); 6369 } 6370 6371 setCaptionHeight (value) { 6372 this.setProperty('--table-offset', value); 6373 } 6374} 6375 6376class TableWrapper extends api.core.Instance { 6377 static get instanceClassName () { 6378 return 'TableWrapper'; 6379 } 6380 6381 init () { 6382 this.addAscent(TableEmission.CAPTION_HEIGHT, this.setCaptionHeight.bind(this)); 6383 } 6384 6385 setCaptionHeight (value) { 6386 requestAnimationFrame(() => this.ascend(TableEmission.CAPTION_HEIGHT, 0)); 6387 this.setProperty('--table-offset', value); 6388 } 6389} 6390 6391const TableSelector = { 6392 TABLE: api.internals.ns.selector('table'), 6393 TABLE_WRAPPER: [`${api.internals.ns.selector('table')} ${api.internals.ns.selector('table__wrapper')}`], 6394 SHADOW: api.internals.ns.selector('table__shadow'), 6395 SHADOW_LEFT: api.internals.ns.selector('table__shadow--left'), 6396 SHADOW_RIGHT: api.internals.ns.selector('table__shadow--right'), 6397 ELEMENT: [`${api.internals.ns.selector('table')}:not(${api.internals.ns.selector('table--no-scroll')}) table`], 6398 CAPTION: `${api.internals.ns.selector('table')} table caption`, 6399 ROW: `${api.internals.ns.selector('table')} tbody tr`, 6400 COL: `${api.internals.ns.selector('table')} thead th` 6401}; 6402 6403const SCROLL_OFFSET = 0; // valeur en px du scroll avant laquelle le shadow s'active ou se desactive 6404 6405class TableElement extends api.core.Instance { 6406 static get instanceClassName () { 6407 return 'TableElement'; 6408 } 6409 6410 init () { 6411 this.listen('scroll', this.scroll.bind(this)); 6412 this.content = this.querySelector('tbody'); 6413 this.tableOffsetHeight = 0; 6414 this.isResizing = true; 6415 } 6416 6417 get isScrolling () { 6418 return this._isScrolling; 6419 } 6420 6421 set isScrolling (value) { 6422 if (this._isScrolling === value) return; 6423 this._isScrolling = value; 6424 6425 if (value) { 6426 this.addClass(TableSelector.SHADOW); 6427 this.scroll(); 6428 } else { 6429 this.removeClass(TableSelector.SHADOW); 6430 this.removeClass(TableSelector.SHADOW_LEFT); 6431 this.removeClass(TableSelector.SHADOW_RIGHT); 6432 } 6433 } 6434 6435 /* ajoute la classe fr-table__shadow-left ou fr-table__shadow-right sur fr-table en fonction d'une valeur de scroll et du sens (right, left) */ 6436 scroll () { 6437 const isMin = this.node.scrollLeft <= SCROLL_OFFSET; 6438 const max = this.content.offsetWidth - this.node.offsetWidth - SCROLL_OFFSET; 6439 const isMax = Math.abs(this.node.scrollLeft) >= max; 6440 const isRtl = document.documentElement.getAttribute('dir') === 'rtl'; 6441 const minSelector = isRtl ? TableSelector.SHADOW_RIGHT : TableSelector.SHADOW_LEFT; 6442 const maxSelector = isRtl ? TableSelector.SHADOW_LEFT : TableSelector.SHADOW_RIGHT; 6443 6444 if (isMin) { 6445 this.removeClass(minSelector); 6446 } else { 6447 this.addClass(minSelector); 6448 } 6449 6450 if (isMax) { 6451 this.removeClass(maxSelector); 6452 } else { 6453 this.addClass(maxSelector); 6454 } 6455 } 6456 6457 resize () { 6458 this.isScrolling = this.content.offsetWidth > this.node.offsetWidth; 6459 } 6460 6461 dispose () { 6462 this.isScrolling = false; 6463 } 6464} 6465 6466const PADDING = '1rem'; // padding de 4v sur le caption 6467class TableCaption extends api.core.Instance { 6468 static get instanceClassName () { 6469 return 'TableCaption'; 6470 } 6471 6472 init () { 6473 this.height = 0; 6474 this.isResizing = true; 6475 } 6476 6477 resize () { 6478 const height = this.getRect().height; 6479 if (this.height === height) return; 6480 this.height = height; 6481 this.ascend(TableEmission.CAPTION_HEIGHT, `calc(${height}px + ${PADDING})`); 6482 } 6483} 6484 6485class TableRow extends api.core.Instance { 6486 static get instanceClassName () { 6487 return 'TableRow'; 6488 } 6489 6490 init () { 6491 if (api.checkbox) { 6492 this.addAscent(CheckboxEmission.CHANGE, this._handleCheckboxChange.bind(this)); 6493 this.descend(CheckboxEmission.RETRIEVE); 6494 } 6495 } 6496 6497 _handleCheckboxChange (node) { 6498 if (node.name === 'row-select') { 6499 this.isSelected = node.checked === true; 6500 } 6501 } 6502 6503 render () { 6504 const height = this.getRect().height + 2; 6505 if (this._height === height) return; 6506 this._height = height; 6507 this.setProperty('--row-height', `${this._height}px`); 6508 } 6509 6510 get isSelected () { 6511 return this._isSelected; 6512 } 6513 6514 set isSelected (value) { 6515 if (this._isSelected === value) return; 6516 this.isRendering = value; 6517 this._isSelected = value; 6518 this.setAttribute('aria-selected', value); 6519 } 6520} 6521 6522api.table = { 6523 Table: Table, 6524 TableWrapper: TableWrapper, 6525 TableElement: TableElement, 6526 TableCaption: TableCaption, 6527 TableSelector: TableSelector, 6528 TableRow: TableRow 6529}; 6530 6531api.internals.register(api.table.TableSelector.TABLE, api.table.Table); 6532api.internals.register(api.table.TableSelector.TABLE_WRAPPER, api.table.TableWrapper); 6533api.internals.register(api.table.TableSelector.ELEMENT, api.table.TableElement); 6534api.internals.register(api.table.TableSelector.CAPTION, api.table.TableCaption); 6535api.internals.register(api.table.TableSelector.ROW, api.table.TableRow); 6536//# sourceMappingURL=dsfr.module.js.map
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.