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