@trilogy-ds/vanilla 0.0.1-beta.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.eslintrc.js ADDED
@@ -0,0 +1,82 @@
1
+ module.exports = {
2
+ root: true,
3
+ parser: '@typescript-eslint/parser',
4
+ env: {
5
+ node: true,
6
+ },
7
+ plugins: ['@typescript-eslint', 'import', 'jest', 'react', 'react-hooks'],
8
+ extends: [
9
+ 'eslint:recommended',
10
+ 'plugin:@typescript-eslint/recommended',
11
+ 'plugin:import/errors',
12
+ 'plugin:import/warnings',
13
+ 'plugin:import/typescript',
14
+ 'plugin:jest/recommended',
15
+ 'plugin:react/recommended',
16
+ 'plugin:react-hooks/recommended',
17
+ ],
18
+ parserOptions: {
19
+ ecmaFeatures: {
20
+ jsx: true,
21
+ tsx: true,
22
+ },
23
+ },
24
+ rules: {
25
+ 'arrow-spacing': 2,
26
+ 'block-spacing': 1,
27
+ 'brace-style': 1,
28
+ camelcase: ['error', { properties: 'always' }],
29
+ 'comma-spacing': 1,
30
+ 'comma-style': 1,
31
+ 'default-case': 'error',
32
+ 'dot-location': ['warn', 'property'],
33
+ 'eol-last': 2,
34
+ 'func-call-spacing': 2,
35
+ 'import/export': 0,
36
+ 'jsx-quotes': ['warn', 'prefer-single'],
37
+ 'key-spacing': 1,
38
+ 'keyword-spacing': 1,
39
+ 'lines-between-class-members': 1,
40
+ 'max-len': ['error', { code: 170 }],
41
+ 'no-alert': 0,
42
+ 'no-confusing-arrow': 1,
43
+ 'no-console': 0,
44
+ 'no-duplicate-imports': 1,
45
+ 'no-eval': 2,
46
+ 'no-extend-native': 2,
47
+ 'no-multiple-empty-lines': [1, { max: 1 }],
48
+ 'no-trailing-spaces': 1,
49
+ 'no-unneeded-ternary': 1,
50
+ 'no-unused-expressions': 0,
51
+ 'no-use-before-define': 0,
52
+ 'react-hooks/exhaustive-deps': 'off',
53
+ 'no-useless-constructor': 1,
54
+ 'no-var': 1,
55
+ 'object-curly-spacing': [1, 'always'],
56
+ 'prefer-const': 1,
57
+ 'prefer-destructuring': ['warn', { array: true, object: true }],
58
+ 'prefer-rest-params': 1,
59
+ 'prefer-spread': 1,
60
+ 'prefer-template': 1,
61
+ indent: 'off',
62
+ quotes: 'off',
63
+ 'quote-props': ['warn', 'as-needed'],
64
+ 'react/display-name': 2,
65
+ 'react/jsx-key': 2,
66
+ 'react/jsx-no-duplicate-props': 2,
67
+ 'react/jsx-no-useless-fragment': 1,
68
+ 'react/jsx-no-target-blank': 2,
69
+ 'react/prop-types': 0,
70
+ 'rest-spread-spacing': 2,
71
+ semi: ['error', 'never', { beforeStatementContinuationChars: 'always' }],
72
+ 'spaced-comment': ['warn', 'always'],
73
+ 'switch-colon-spacing': 1,
74
+ },
75
+ settings: {
76
+ react: {
77
+ pragma: 'React',
78
+ version: 'detect',
79
+ },
80
+ 'import/ignore': ['react-native'],
81
+ },
82
+ }
package/.prettierrc.js ADDED
@@ -0,0 +1,12 @@
1
+ module.exports = {
2
+ bracketSpacing: true,
3
+ jsxBracketSameLine: true,
4
+ jsxSingleQuote: true,
5
+ singleQuote: true,
6
+ trailingComma: 'all',
7
+ semi: false,
8
+ printWidth: 120,
9
+ tabWidth: 2,
10
+ importOrder: ["^react$", "^[A-Za-z-\/]+$", "^[@]", "^[~]", "^[./]",],
11
+ importOrderSeparation: true,
12
+ }
package/README.md ADDED
@@ -0,0 +1,18 @@
1
+ # Trilogy DS Vanilla
2
+
3
+ ___
4
+
5
+ This repository contains all the necessary Javascript components to integrate Trilogy Design System in an HTML setting.
6
+
7
+ ___
8
+
9
+ ## CDN
10
+
11
+ For a faster setup, you can utilize the JSDelivr CDN to include Trilogy Vanilla in your project's HTML:
12
+
13
+ ```html
14
+ <!-- ... -->
15
+ <link href="https://cdn.jsdelivr.net/npm/@trilogy-ds/vanilla" rel="stylesheet" />
16
+ <!-- ... -->
17
+ ```
18
+
@@ -0,0 +1,11 @@
1
+ (function(A){typeof define=="function"&&define.amd?define(A):A()})(function(){"use strict";const A=()=>{let c=document.querySelectorAll("[data-modal-context]");for(let s=0;s<c.length;s++){let e=c[s],n=e.querySelectorAll("[data-modal-open]"),a=e.querySelector("[data-modal]"),u=e.querySelectorAll("[data-modal-close]"),d=e.querySelectorAll('iframe[src*="www.youtube.com"], iframe[src*="player.vimeo.com"], video');for(let o=0;o<n.length;o++){let f=n[o],m=d[o];f.addEventListener("click",function(){if(i(a),m){if(m.tagName.toLowerCase()==="video"){m.play();return}m.src=m.src+(m.src.indexOf("?")<0?"?":"&")+"autoplay=1"}else return})}for(let o=0;o<u.length;o++)u[o].addEventListener("click",function(){l(a)});t(a);const r=e.querySelector(".modal-background");r&&(r.onclick=function(){l(a)}),e.setAttribute("data-modal-initialized","true")}function t(s){document.addEventListener("keyup",function(e){e=e||window.event;var n=!1;"key"in e?n=e.key==="Escape"||e.key==="Esc":n=e.keyCode===27,n&&l(s)}),document.addEventListener("click",function(e){const n=e.target;let a=n.closest("[data-modal-content]"),u=n.classList.contains("modal");a===null&&u==!0&&l(s)})}const i=s=>{s.classList.add("is-active"),document.body.style.overflow="hidden"},l=s=>{let e=s.querySelector('iframe[src*="youtube"], iframe[src*="vimeo"], video');if(e){if(e.tagName.toLowerCase()==="video"){e.pause();return}e.src=e.src.replace("&autoplay=1","").replace("?autoplay=1","")}s.classList.remove("is-active"),document.body.style.overflow=null}},E=()=>{let c=document.querySelectorAll('[data-variant="auto"]');const t=function(i){let l=0,s=i.length,e=0;if(s>0)for(;e<s;)l=(l<<5)-l+i.charCodeAt(e++)|0;return l};for(let i=0;i<c.length;i++){let l=c[i];const s=l.textContent,e=["b","g","o","q"],n=[];if(s.toLowerCase().split("").map((a,u)=>(e.indexOf(a)>-1&&n.push(u),a)),n.length){const a=t(s),u=Math.abs(a%n.length),d=s.charAt(n[u]),r=s.substring(0,n[u]),o=s.substring(n[u]+1);l.innerHTML=`${r}<span class="has-variant">${d}</span>${o}`}l.setAttribute("data-fontVariant-initialized","true")}},k=()=>{document.querySelectorAll("[data-countdown]").forEach(t=>{let n=t,a=new Date(n.dataset.date).getTime(),u=n.querySelector("[data-days]"),d=n.querySelector("[data-hours]"),r=n.querySelector("[data-minutes]"),o=n.querySelector("[data-seconds]"),f=setInterval(()=>{let m=new Date().getTime(),p=0;a-m>0&&(p=a-m),p<=0&&clearInterval(f),u.innerText=Math.floor(p/864e5).toString(),d.innerText=Math.floor(p%864e5/36e5).toString(),r.innerText=Math.floor(p%36e5/6e4).toString(),o.innerText=Math.floor(p%6e4/1e3).toString()},1e3);n.setAttribute("data-countdown-initialized","true")})},H=c=>{c.length&&c.forEach(t=>{const i=t.querySelector("button.toggle");t.classList.remove("is-active"),i.setAttribute("aria-expanded","false")})},W=c=>{const t=c.querySelectorAll("[data-accordion-context]");t.forEach(i=>{const l=i.querySelector("[data-accordion-toggle]"),s=i.querySelector("button.toggle");l.addEventListener("click",()=>{i.classList.contains("is-disabled")||(i.classList.contains("is-active")?(i.classList.remove("is-active"),s.setAttribute("aria-expanded","false")):(H(t),i.classList.toggle("is-active"),s.setAttribute("aria-expanded","true")))})}),c.setAttribute("data-accordion-initialized","true")},z=()=>{document.querySelectorAll("[data-accordion], .accordions").forEach(t=>{W(t)})},I=()=>{let c=document.querySelectorAll("[data-expandable-row]");for(let t=0;t<c.length;t++){let i=c[t];i.querySelector("[data-expandable-trigger]").addEventListener("click",function(){i.classList.toggle("is-expanded")}),i.setAttribute("data-tableexpansion-initialized","true")}},N=()=>{const c=document.querySelectorAll("[data-tabs-context]"),t=`right: 0;padding-left: 16px;width: 4.5rem !important;background: linear-gradient(90deg, rgba(256,256,256, 0) -25%, white 30%);
2
+ background-attachment: local, local, scroll, scroll;`,i=`
3
+ .tabs {
4
+ padding-right: 2rem;
5
+ }
6
+ `,l=document.createElement("style");l.innerText=i,c.forEach(e=>{const n=e.clientWidth;let a=e.querySelector(".tabs");if(a||(a=e.querySelector('[data-real-class*="tabs"]')),a.scrollWidth>n&&!a.innerHTML.includes("icon is-small is-absolute")){const d=`<span class="icon is-small is-absolute" style="${t}"><i class="tri-arrow-right" aria-hidden='true'></i></span>`;a.innerHTML+=d,a.appendChild(l)}});let s=document.querySelectorAll("[data-tabs-context]");for(let e=0;e<s.length;e++){let n=s[e],a=n.querySelectorAll("[data-tab-navigation]"),u=n.querySelectorAll("[data-tab-content]");for(let o=0;o<a.length;o++){let f=a[o],m=u[o];f.addEventListener("click",function(){r(f,m)}),f.addEventListener("keyup",p=>{switch(p.preventDefault(),p.keyCode){case 35:r(a[a.length-1],u[a.length-1]);break;case 36:r(a[0],u[0]);break;case 37:let h=(o-1)%a.length;r(a[h],u[h]);break;case 39:let v=(o+1)%a.length;r(a[v],u[v]);break}})}const d=()=>{for(let o=0;o<a.length;o++)a[o].classList.remove("is-active"),a[o].setAttribute("aria-selected","false"),a[o].setAttribute("tabindex","-1");for(let o=0;o<u.length;o++)u[o].classList.remove("is-active"),u[o].setAttribute("aria-expanded","false")},r=(o,f)=>{d(),o.classList.add("is-active"),o.setAttribute("aria-selected","true"),o.setAttribute("tabindex","0"),o.focus(),f.classList.add("is-active"),f.setAttribute("aria-expanded","true")};n.setAttribute("data-tab-initialized","true")}},O=()=>{document.querySelectorAll("[data-autocomplete-context]").forEach(t=>{const i=t.querySelector("[data-autocomplete-input]"),l=t.querySelector("[data-autocomplete-menu]"),s=l.querySelectorAll(".autocomplete-item");let e=-1,n="";const a=()=>{e=-1,document.body.classList.remove("autocomplete-close"),t.classList.remove("is-active"),i.blur(),s.forEach(d=>{d.removeAttribute("data-autocomplete-item-hover")})};l.querySelectorAll(".autocomplete-item").forEach((d,r)=>{d.setAttribute("data-autocomplete-item-index",String(r))}),i.addEventListener("focus",d=>{d.stopPropagation(),t.closest(".is-autocomplete").classList.add("is-active"),document.body.classList.add("autocomplete-close");const r=[];s.forEach(o=>{o.textContent.trim().toLocaleLowerCase().includes(i.value.trim().toLocaleLowerCase())?(o.style.display="block",r.push(o)):o.style.display="none",o.removeAttribute("data-autocomplete-item-index")}),r.forEach((o,f)=>{o.setAttribute("data-autocomplete-item-index",String(f))})}),s.forEach(d=>{d.addEventListener("mousemove",()=>{s.forEach(o=>{o.removeAttribute("data-autocomplete-item-hover")});const r=d.getAttribute("data-autocomplete-item-index");e=Number(r),d.setAttribute("data-autocomplete-item-hover","true"),n=""}),d.addEventListener("mouseout",()=>{d.removeAttribute("data-autocomplete-item-hover")}),d.addEventListener("click",r=>{const o=r.target;i.value=o.textContent,a()})}),i.addEventListener("input",d=>{const r=d.target;e!==-1&&(e=-1);const o=[];s.forEach(f=>{f.textContent.trim().toLocaleLowerCase().includes(r.value.trim().toLocaleLowerCase())?(f.style.display="block",o.push(f)):f.style.display="none",f.removeAttribute("data-autocomplete-item-index"),f.removeAttribute("data-autocomplete-item-hover")}),o.forEach((f,m)=>{f.setAttribute("data-autocomplete-item-index",String(m))})}),i.addEventListener("keydown",d=>{const r=l.querySelectorAll("[data-autocomplete-item-index]");d.key==="ArrowDown"&&(e=e+1,e===r.length&&(e=0)),d.key==="ArrowUp"&&(e=e-1,e<0&&(e=r.length-1)),["ArrowDown","ArrowUp"].includes(d.key)&&(r.forEach(o=>{o.removeAttribute("data-autocomplete-item-hover")}),r[e].setAttribute("data-autocomplete-item-hover","true"),n=r[e].textContent),d.key==="Enter"&&n.trim().length>0&&(i.value=n,a())}),i.addEventListener("blur",()=>{setTimeout(()=>a(),100)}),t.setAttribute("data-autocomplete-initialized","true")})},M=()=>{document.querySelectorAll(".segmented-control-item").forEach(t=>{t.addEventListener("click",function(){this.parentElement.querySelector(".is-active").classList.remove("is-active"),t.classList.contains("is-active")||t.classList.add("is-active")}),t.setAttribute("data-segmentedControl-initialized","true")})},$=()=>{document.querySelectorAll(".textarea-wrapper").forEach(t=>{if(!t.getAttribute("data-textarea-initialized")){const s=t.querySelector("textarea.textarea"),e=s.getAttribute("maxlength");if(e){var l=0;const n=document.createElement("div");n.classList.add("counter"),n.innerHTML=`${l}/${e}`,t.appendChild(n),s.addEventListener("input",function(a){l=this.value.length,n.innerHTML=`${l}/${e}`,l===10&&a.preventDefault()})}t.setAttribute("data-textarea-initialized","true")}})},F=c=>{c.forEach(t=>{t.classList.remove("is-active")})},T=()=>{document.querySelectorAll(".chips-list").forEach(t=>{if(!t.getAttribute("data-chips-initialized")){const l=t.querySelectorAll(".chips"),s=t.classList.contains("is-multiple");l.forEach(e=>{e.classList.contains("is-disabled")||e.addEventListener("click",function(){s||F(l),e.classList.toggle("is-active")})}),t.setAttribute("data-chips-initialized","true")}})},R=c=>{if(!c.getAttribute("data-progress-radial-initialized")){let i=Number(c.getAttribute("data-progress-radial-first-value")),l=Number(c.getAttribute("data-progress-radial-second-value")),s="",e=0,n=0;if(l===0){const a=setInterval(()=>{e!==i?e+=1:clearInterval(a),s=`radial-gradient(white 58%, transparent 51%),
7
+ conic-gradient(#0C7B91 0deg ${360*(e/100)}deg,
8
+ gainsboro ${360*(e/100)}deg 360deg)`,c.style.background=s},13)}else{l+=i;const a=setInterval(()=>{e<i&&(e+=1),n<l?n+=1:clearInterval(a),s=`radial-gradient(white 58%, transparent 51%),
9
+ conic-gradient(#0C7B91 0deg ${360*(e/100)}deg,
10
+ #25465f ${360*(e/100)}deg ${360*(n/100)}deg,
11
+ gainsboro ${360*(n/100)}deg 360deg)`,c.style.background=s},13)}c.setAttribute("data-progress-radial-initialized","true")}},_=c=>{let t=c.querySelector(".range-cursor-min"),i=c.querySelector(".range-cursor-max"),l=c.querySelector(".range-track"),s=c.querySelector(".range-value-min"),e=c.querySelector(".range-value-max"),n=t.max,a=0;const u=()=>{let d=Number(t.value)/Number(n)*100,r=Number(i.value)/Number(n)*100;l.style.background=`linear-gradient(to right, #E1E1E1 ${d}% , #0C7B91 ${d}% , #0C7B91 ${r}%, #E1E1E1 ${r}%) `};u(),t.addEventListener("input",d=>{const r=d.target;Number(r.value)<Number(i.value)-a?t.value=r.value:t.value=String(Number(i.value)-a),s.textContent=r.value,u()}),i.addEventListener("input",d=>{const r=d.target;Number(r.value)>Number(t.value)+a?i.value=r.value:i.value=String(Number(t.value)+a),e.textContent=r.value,u()}),c.setAttribute("data-ranges-initialized","true")},G=(c,t)=>{new IntersectionObserver(l=>{l.forEach(s=>{s.isIntersecting&&t(s.target)})}).observe(c)},V=()=>{const c=document.querySelectorAll("[data-select-name]");c&&c.forEach(t=>{const i=t.querySelector("label");if(!t.classList.contains("select-disabled")){const l=t.parentNode;let s=l.querySelector("[data-is-open-options]");t.addEventListener("click",()=>{const n=s.getAttribute("data-is-open-options");n==="true"&&s.setAttribute("data-is-open-options","false"),n==="false"&&s.setAttribute("data-is-open-options","true")}),t.addEventListener("blur",()=>{s.setAttribute("data-is-open-options","false")});const e=s.querySelectorAll("[data-option-name]");e.forEach(n=>{n.classList.contains("select-options-option-disabled")||n.addEventListener("mousedown",()=>{e.forEach(u=>u.classList.remove("select-options-option-activated")),n.classList.add("select-options-option-activated");let a=t.querySelector("[data-option-selected]");a||(a=document.createElement("span"),a.classList.add("select-value"),(!i||i===null)&&a.classList.add("no-label"),t.appendChild(a)),t.setAttribute("data-option-selected",n.getAttribute("data-option-name")),a.setAttribute("data-option-selected",n.getAttribute("data-option-name")),a.textContent=n.getAttribute("data-option-name"),l.classList.add("has-dynamic-placeholder")})})}t.setAttribute("data-selects-initialized","true")})},B=()=>{const c=document.querySelectorAll("[data-has-gauge]");c&&c.forEach(t=>{var S,x;const i="#007B52",l="#707070",s="#D42D02",e="#FFBB33",n=t.querySelector("input"),a=t.querySelector("[data-gauge]"),u=(S=t.querySelector("[data-length-min]"))==null?void 0:S.getAttribute("data-length-min"),d=(x=t.querySelector("[data-length-max]"))==null?void 0:x.getAttribute("data-length-max"),r={},o={fn:g=>d&&!u?g.length>0&&g.length<=Number(d):u&&!d?g.length>=Number(u):d&&u?g.length>=Number(u)&&g.length<=Number(d):!1,ref:t.querySelector("[data-security-length]")},f={fn:g=>/[^\w\*]/.test(g),ref:t.querySelector("[data-security-special-chars]")},m={fn:g=>/[0-9]/.test(g),ref:t.querySelector("[data-security-number]")},p={fn:g=>/[A-Z]/.test(g),ref:t.querySelector("[data-security-uppercase]")},h={fn:g=>/[a-z]/.test(g),ref:t.querySelector("[data-security-lowercase]")};m.ref&&Object.assign(r,{numberVerify:m}),o.ref&&Object.assign(r,{lengthVerify:o}),h.ref&&Object.assign(r,{lowercaseVerify:h}),p.ref&&Object.assign(r,{uppercaseVerify:p}),f.ref&&Object.assign(r,{specialCharsverify:f}),Object.keys(r).length;const v=g=>{const b=[];return Object.keys(r).map(y=>{const C=r[y].fn(g),L=r[y].ref.querySelector("[data-icon-securities]").querySelector("i");b.push(C),C?(L.classList.remove("tri-times-circle"),L.classList.add("tri-check-circle","is-success")):(L.classList.remove("tri-check-circle","is-success"),L.classList.add("tri-times-circle"))}),b.filter(y=>y).length},q=g=>{const b=Number((g/Object.keys(r).length*100).toFixed(0));b<=50&&b>0?(a.style.width="50%",a.style.backgroundColor=s):b<=99&&b>50?(a.style.width="75%",a.style.backgroundColor=e):b===100?(a.style.width="100%",a.style.backgroundColor=i):(a.style.width="0%",a.style.backgroundColor=l)},w=g=>{const b=g.target.value,y=v(b);q(y)};n.addEventListener("input",w),t.setAttribute("data-gauges-initialized","true")})},j=()=>{const c=document.querySelectorAll("[data-show-pwd]");c&&c.forEach(t=>{const i=e=>{e.classList.contains("tri-eye")?(e.classList.remove("tri-eye"),e.classList.add("tri-eye-slash")):(e.classList.remove("tri-eye-slash"),e.classList.add("tri-eye"))},l=e=>{const n=e.parentNode.parentNode;let a=n.querySelector("input");a||(a=n.parentNode.querySelector("input")),a.type==="password"?a.type="text":a.type="password"},s=e=>{const n=e.target;i(n),l(n)};t.addEventListener("click",s),t.setAttribute("data-iconsShowPwd-initialized","true")})},D=()=>{N(),A(),E(),k(),I(),z(),O(),M(),$(),T(),V(),B(),j()};let P=!1;P||document.addEventListener("DOMContentLoaded",function(){D(),P=!0;const c={attributes:!0,childList:!0,subtree:!0};new MutationObserver(function(i){i.forEach(function(l){const s=l.target;if(s){const e=s.querySelectorAll("[data-modal-context]"),n=s.querySelectorAll(".accordions"),a=s.querySelectorAll(".countdown"),u=s.querySelectorAll("[data-tabs-context]"),d=s.querySelectorAll("[data-autocomplete-context]"),r=s.querySelectorAll('[data-variant="auto"]'),o=s.querySelectorAll(".segmented-control-item"),f=document.querySelectorAll(".textarea"),m=document.querySelectorAll(".progress-radial"),p=document.querySelectorAll(".range-container"),h=document.querySelectorAll(".chips-list"),v=s.querySelectorAll("[data-select-name]"),q=document.querySelectorAll("[data-has-gauge]"),w=document.querySelectorAll("[data-show-pwd]"),S=s.querySelectorAll("[data-expandable-row]");[{modal:e},{accordion:n},{tab:u},{countdown:a},{autocomplete:d},{fontVariant:r},{segmentedControl:o},{textareas:f},{ranges:p},{progressRadials:m},{chips:h},{selects:v},{gauges:q},{iconsShowPwd:w},{tableexpansion:S}].forEach(g=>{const b=Object.keys(g)[0];g[b].length&&g[b].forEach(y=>{if(y.getAttribute(`data-${b}-initialized`)!=="true")switch(y.setAttribute(`data-${b}-initialized`,"true"),b){case"modal":return A();case"tab":return N();case"accordion":return z();case"countdown":return k();case"autocomplete":return O();case"fontVariant":return E();case"segmentedControl":return M();case"textareas":return $();case"chips":return T();case"selects":return V();case"ranges":return _(y);case"progressRadials":return G(y,R);case"gauges":return B();case"iconsShowPwd":return j();case"tableexpansion":return I();default:return D()}})})}})}).observe(document.documentElement,c)})});
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@trilogy-ds/vanilla",
3
+ "version": "0.0.1-beta.9",
4
+ "author": "Bouygues Telecom",
5
+ "main": "lib/trilogy-ds-vanilla.js",
6
+ "scripts": {
7
+ "start": "webpack --watch",
8
+ "build": "vite --config vite.config.js build"
9
+ },
10
+ "repository": {
11
+ "type": "git"
12
+ },
13
+ "license": "ISC",
14
+ "dependencies": {
15
+ "eslint": "4.19.1",
16
+ "typescript": "^4.1.5",
17
+ "rimraf": "^3.0.2",
18
+ "ts-loader": "^8.0.17",
19
+ "fork-ts-checker-webpack-plugin": "^6.1.0",
20
+ "vite": "^4.5.2",
21
+ "vite-tsconfig-paths": "^4.3.1"
22
+ }
23
+ }
package/src/app.ts ADDED
@@ -0,0 +1,142 @@
1
+ import { initModals } from './components/modal'
2
+ import { initVariant } from './components/font-variant'
3
+ import { initCountdowns } from './components/countdown'
4
+ import { initAccordions } from './components/accordion'
5
+ import { initTableExpansion } from './components/table-expansion'
6
+ import { initTabs } from './components/tabs'
7
+ import { initAutocomplete } from './components/autocomplete'
8
+ import { initSegmentedControl } from './components/segmented-control'
9
+ import { initTextarea } from './components/textarea'
10
+ import { initChips } from './components/chips'
11
+ import { initProgressRadial } from './components/progress-radial'
12
+ import { initRange } from './components/range'
13
+ import { intersectionObserver } from './utils/intersectionObserver'
14
+ import { initSelects } from './components/select'
15
+ import { initInputGauge } from './components/input-gauge'
16
+ import { initInputIcon } from './components/input-icon'
17
+
18
+ const loadVanilla = () => {
19
+ initTabs()
20
+ initModals()
21
+ initVariant()
22
+ initCountdowns()
23
+ initTableExpansion()
24
+ initAccordions()
25
+ initAutocomplete()
26
+ initSegmentedControl()
27
+ initTextarea()
28
+ initChips()
29
+ initSelects()
30
+ initInputGauge()
31
+ initInputIcon()
32
+ }
33
+
34
+ let isInitialized = false
35
+
36
+ if (!isInitialized) {
37
+ // when DOM is loaded
38
+ document.addEventListener('DOMContentLoaded', function () {
39
+ loadVanilla()
40
+
41
+ // Assign variable to true
42
+ isInitialized = true
43
+
44
+ // Observer config
45
+ const observerConfig = {
46
+ attributes: true,
47
+ childList: true,
48
+ subtree: true,
49
+ }
50
+
51
+ // Checking mutations
52
+ const mutationObserver = new MutationObserver(function (mutations: MutationRecord[]) {
53
+ mutations.forEach(function (mutation: MutationRecord) {
54
+ const mutationTarget = mutation.target as HTMLElement
55
+
56
+ if (mutationTarget) {
57
+ const modals = mutationTarget.querySelectorAll('[data-modal-context]')
58
+ const accordions = mutationTarget.querySelectorAll('.accordions')
59
+ const countdowns = mutationTarget.querySelectorAll('.countdown')
60
+ const tabs = mutationTarget.querySelectorAll('[data-tabs-context]')
61
+ const autocomplete = mutationTarget.querySelectorAll('[data-autocomplete-context]')
62
+ const fontVariant = mutationTarget.querySelectorAll('[data-variant="auto"]')
63
+ const segmentedControl = mutationTarget.querySelectorAll('.segmented-control-item')
64
+ const textareas = document.querySelectorAll('.textarea')
65
+ const progressRadials = document.querySelectorAll('.progress-radial')
66
+ const ranges = document.querySelectorAll('.range-container')
67
+ const chips = document.querySelectorAll('.chips-list')
68
+ const selects = mutationTarget.querySelectorAll('[data-select-name]')
69
+ const gauges = document.querySelectorAll<HTMLElement>('[data-has-gauge]')
70
+ const iconsShowPwd = document.querySelectorAll<HTMLElement>('[data-show-pwd]')
71
+ const tableexpansion = mutationTarget.querySelectorAll('[data-expandable-row]')
72
+
73
+ const elements = [
74
+ { modal: modals },
75
+ { accordion: accordions },
76
+ { tab: tabs },
77
+ { countdown: countdowns },
78
+ { autocomplete: autocomplete },
79
+ { fontVariant: fontVariant },
80
+ { segmentedControl: segmentedControl },
81
+ { textareas: textareas },
82
+ { ranges: ranges },
83
+ { progressRadials: progressRadials },
84
+ { chips: chips },
85
+ { selects: selects },
86
+ { gauges: gauges },
87
+ { iconsShowPwd: iconsShowPwd },
88
+ { tableexpansion: tableexpansion },
89
+ ]
90
+
91
+ elements.forEach((element: any) => {
92
+ const key = Object.keys(element)[0]
93
+ if (element[key].length) {
94
+ element[key].forEach((htmlElement: HTMLElement) => {
95
+ const initialized = htmlElement.getAttribute(`data-${key}-initialized`)
96
+ if (initialized !== 'true') {
97
+ htmlElement.setAttribute(`data-${key}-initialized`, 'true')
98
+ switch (key) {
99
+ case 'modal':
100
+ return initModals()
101
+ case 'tab':
102
+ return initTabs()
103
+ case 'accordion':
104
+ return initAccordions()
105
+ case 'countdown':
106
+ return initCountdowns()
107
+ case 'autocomplete':
108
+ return initAutocomplete()
109
+ case 'fontVariant':
110
+ return initVariant()
111
+ case 'segmentedControl':
112
+ return initSegmentedControl()
113
+ case 'textareas':
114
+ return initTextarea()
115
+ case 'chips':
116
+ return initChips()
117
+ case 'selects':
118
+ return initSelects()
119
+ case 'ranges':
120
+ return initRange(htmlElement)
121
+ case 'progressRadials':
122
+ return intersectionObserver(htmlElement, initProgressRadial)
123
+ case 'gauges':
124
+ return initInputGauge()
125
+ case 'iconsShowPwd':
126
+ return initInputIcon()
127
+ case 'tableexpansion':
128
+ return initTableExpansion()
129
+ default:
130
+ return loadVanilla()
131
+ }
132
+ }
133
+ })
134
+ }
135
+ })
136
+ }
137
+ })
138
+ })
139
+
140
+ mutationObserver.observe(document.documentElement, observerConfig)
141
+ })
142
+ }
@@ -0,0 +1,37 @@
1
+ const _closeAccordionContexts = (accordionsContexts: NodeListOf<HTMLElement>) => {
2
+ if (accordionsContexts.length) {
3
+ accordionsContexts.forEach((accordionContext: HTMLElement) => {
4
+ const accordionButton = accordionContext.querySelector('button.toggle')
5
+ accordionContext.classList.remove('is-active')
6
+ accordionButton.setAttribute('aria-expanded', 'false')
7
+ })
8
+ }
9
+ }
10
+
11
+ const _loadAccordion = (accordion: HTMLElement) => {
12
+ const accordionsContexts: NodeListOf<HTMLElement> = accordion.querySelectorAll('[data-accordion-context]')
13
+ accordionsContexts.forEach((context) => {
14
+ const toggle = context.querySelector('[data-accordion-toggle]')
15
+ const accordionButton = context.querySelector('button.toggle')
16
+ toggle.addEventListener('click', () => {
17
+ if (!context.classList.contains('is-disabled')) {
18
+ if (context.classList.contains('is-active')) {
19
+ context.classList.remove('is-active')
20
+ accordionButton.setAttribute('aria-expanded', 'false')
21
+ } else {
22
+ _closeAccordionContexts(accordionsContexts)
23
+ context.classList.toggle('is-active')
24
+ accordionButton.setAttribute('aria-expanded', 'true')
25
+ }
26
+ }
27
+ })
28
+ })
29
+ accordion.setAttribute('data-accordion-initialized', 'true')
30
+ }
31
+
32
+ export const initAccordions = () => {
33
+ const allAccordions = document.querySelectorAll('[data-accordion], .accordions')
34
+ allAccordions.forEach((accordion: HTMLElement) => {
35
+ _loadAccordion(accordion)
36
+ })
37
+ }
@@ -0,0 +1,133 @@
1
+ export const initAutocomplete = () => {
2
+ const autocompleteContainer = document.querySelectorAll('[data-autocomplete-context]')
3
+
4
+ autocompleteContainer.forEach((autocompleteConainterElmt: HTMLDivElement) => {
5
+ const inputAutocomplete: HTMLInputElement = autocompleteConainterElmt.querySelector('[data-autocomplete-input]')
6
+ const autocompleteMenu: HTMLDivElement = autocompleteConainterElmt.querySelector('[data-autocomplete-menu]')
7
+ const autocompleteItems = autocompleteMenu.querySelectorAll('.autocomplete-item')
8
+
9
+ let currentItems = -1
10
+ let itemSelected = ''
11
+
12
+ const onBlur = () => {
13
+ currentItems = -1
14
+ document.body.classList.remove('autocomplete-close')
15
+ autocompleteConainterElmt.classList.remove('is-active')
16
+ inputAutocomplete.blur()
17
+ autocompleteItems.forEach((item: HTMLDivElement) => {
18
+ item.removeAttribute('data-autocomplete-item-hover')
19
+ })
20
+ }
21
+
22
+ // add data attributte on items
23
+ const itemsList = autocompleteMenu.querySelectorAll('.autocomplete-item')
24
+ itemsList.forEach((item: HTMLDivElement, index: number) => {
25
+ item.setAttribute('data-autocomplete-item-index', String(index))
26
+ })
27
+
28
+ // Open suggestions
29
+ inputAutocomplete.addEventListener('focus', (eventFocus: FocusEvent) => {
30
+ eventFocus.stopPropagation()
31
+ autocompleteConainterElmt.closest('.is-autocomplete').classList.add('is-active')
32
+ document.body.classList.add('autocomplete-close')
33
+ const items: HTMLDivElement[] = []
34
+
35
+ // open with suggestions filtered
36
+ autocompleteItems.forEach((item: HTMLDivElement) => {
37
+ if (item.textContent.trim().toLocaleLowerCase().includes(inputAutocomplete.value.trim().toLocaleLowerCase())) {
38
+ item.style.display = 'block'
39
+ items.push(item)
40
+ } else {
41
+ item.style.display = 'none'
42
+ }
43
+ item.removeAttribute('data-autocomplete-item-index')
44
+ })
45
+
46
+ items.forEach((item: HTMLDivElement, index: number) => {
47
+ item.setAttribute('data-autocomplete-item-index', String(index))
48
+ })
49
+ })
50
+
51
+ // on hover and blur item
52
+ autocompleteItems.forEach((autocompleteItem: HTMLDivElement) => {
53
+ autocompleteItem.addEventListener('mousemove', () => {
54
+ autocompleteItems.forEach((i: HTMLDivElement) => {
55
+ i.removeAttribute('data-autocomplete-item-hover')
56
+ })
57
+
58
+ const index = autocompleteItem.getAttribute('data-autocomplete-item-index')
59
+ currentItems = Number(index)
60
+ autocompleteItem.setAttribute('data-autocomplete-item-hover', 'true')
61
+ itemSelected = ''
62
+ })
63
+
64
+ autocompleteItem.addEventListener('mouseout', () => {
65
+ autocompleteItem.removeAttribute('data-autocomplete-item-hover')
66
+ })
67
+
68
+ autocompleteItem.addEventListener('click', (eventClick: MouseEvent) => {
69
+ const targetItem = eventClick.target as HTMLInputElement
70
+ inputAutocomplete.value = targetItem.textContent
71
+ onBlur()
72
+ })
73
+ })
74
+
75
+ // filter suggestions
76
+ inputAutocomplete.addEventListener('input', (eventInput: InputEvent) => {
77
+ const target = eventInput.target as HTMLInputElement
78
+ if (currentItems !== -1) currentItems = -1
79
+ const items: HTMLDivElement[] = []
80
+
81
+ autocompleteItems.forEach((item: HTMLDivElement) => {
82
+ if (item.textContent.trim().toLocaleLowerCase().includes(target.value.trim().toLocaleLowerCase())) {
83
+ item.style.display = 'block'
84
+ items.push(item)
85
+ } else {
86
+ item.style.display = 'none'
87
+ }
88
+ item.removeAttribute('data-autocomplete-item-index')
89
+ item.removeAttribute('data-autocomplete-item-hover')
90
+ })
91
+
92
+ items.forEach((item: HTMLDivElement, index: number) => {
93
+ item.setAttribute('data-autocomplete-item-index', String(index))
94
+ })
95
+ })
96
+
97
+ // on select items with keyboard
98
+ inputAutocomplete.addEventListener('keydown', (eventKeyboard: KeyboardEvent) => {
99
+ const itemsList = autocompleteMenu.querySelectorAll('[data-autocomplete-item-index]')
100
+
101
+ if (eventKeyboard.key === 'ArrowDown') {
102
+ currentItems = currentItems + 1
103
+ if (currentItems === itemsList.length) currentItems = 0
104
+ }
105
+
106
+ if (eventKeyboard.key === 'ArrowUp') {
107
+ currentItems = currentItems - 1
108
+ if (currentItems < 0) currentItems = itemsList.length - 1
109
+ }
110
+
111
+ if (['ArrowDown', 'ArrowUp'].includes(eventKeyboard.key)) {
112
+ itemsList.forEach((item: HTMLDivElement) => {
113
+ item.removeAttribute('data-autocomplete-item-hover')
114
+ })
115
+ itemsList[currentItems].setAttribute('data-autocomplete-item-hover', 'true')
116
+ itemSelected = itemsList[currentItems].textContent
117
+ }
118
+
119
+ if (eventKeyboard.key === 'Enter' && itemSelected.trim().length > 0) {
120
+ inputAutocomplete.value = itemSelected
121
+ onBlur()
122
+ }
123
+ })
124
+
125
+ // on blur
126
+ // set timeout for click item before blur input
127
+ inputAutocomplete.addEventListener('blur', () => {
128
+ setTimeout(() => onBlur(), 100)
129
+ })
130
+
131
+ autocompleteConainterElmt.setAttribute(`data-autocomplete-initialized`, 'true')
132
+ })
133
+ }
@@ -0,0 +1,28 @@
1
+ const _resetChips = (chipsList: any) => {
2
+ chipsList.forEach((chips: any) => {
3
+ chips.classList.remove('is-active');
4
+ })
5
+ }
6
+
7
+ export const initChips = () => {
8
+ const allChipsLists = document.querySelectorAll(".chips-list");
9
+
10
+ allChipsLists.forEach((chipsList: any) => {
11
+ const isInitialized = chipsList.getAttribute('data-chips-initialized');
12
+ if (!isInitialized) {
13
+ const chips = chipsList.querySelectorAll('.chips');
14
+ const isMultiple = chipsList.classList.contains('is-multiple');
15
+ chips.forEach((chip: HTMLElement) => {
16
+ if (!chip.classList.contains('is-disabled')) {
17
+ chip.addEventListener("click", function () {
18
+ if (!isMultiple) {
19
+ _resetChips(chips);
20
+ }
21
+ chip.classList.toggle('is-active');
22
+ });
23
+ }
24
+ });
25
+ chipsList.setAttribute('data-chips-initialized', 'true');
26
+ }
27
+ });
28
+ };
@@ -0,0 +1,40 @@
1
+ export const initCountdowns = () => {
2
+
3
+ let countdowns: NodeListOf<HTMLElement> = document.querySelectorAll('[data-countdown]');
4
+
5
+ countdowns.forEach((countdown: HTMLElement) => {
6
+ const second = 1000,
7
+ minute = second * 60,
8
+ hour = minute * 60,
9
+ day = hour * 24;
10
+
11
+ let countdownElements = countdown;
12
+ let countDown = new Date(countdownElements.dataset.date).getTime();
13
+ let numbersDays: HTMLElement = countdownElements.querySelector('[data-days]');
14
+ let numbersHours: HTMLElement = countdownElements.querySelector('[data-hours]');
15
+ let numbersMinutes: HTMLElement = countdownElements.querySelector('[data-minutes]');
16
+ let numbersSeconds: HTMLElement = countdownElements.querySelector('[data-seconds]');
17
+
18
+ let timer = setInterval(() => {
19
+
20
+ let now = new Date().getTime();
21
+ let distance = 0;
22
+ if ((countDown - now) > 0) {
23
+ distance = (countDown - now);
24
+ }
25
+ if (distance <= 0) {
26
+ clearInterval(timer);
27
+ }
28
+
29
+ numbersDays.innerText = Math.floor(distance / (day)).toString(),
30
+ numbersHours.innerText = Math.floor((distance % (day)) / (hour)).toString(),
31
+ numbersMinutes.innerText = Math.floor((distance % (hour)) / (minute)).toString(),
32
+ numbersSeconds.innerText = Math.floor((distance % (minute)) / second).toString();
33
+
34
+ }, second)
35
+ countdownElements.setAttribute(`data-countdown-initialized`, 'true')
36
+ })
37
+
38
+ };
39
+
40
+ export default initCountdowns;
@@ -0,0 +1,42 @@
1
+ export const initVariant = () => {
2
+ let titres = document.querySelectorAll('[data-variant="auto"]');
3
+
4
+ const hashCode = function (string: string) {
5
+ let hashNumbers = 0;
6
+ let hashLength = string.length;
7
+ let i = 0;
8
+
9
+ if (hashLength > 0) {
10
+ while (i < hashLength) {
11
+ hashNumbers = (hashNumbers << 5) - hashNumbers + string.charCodeAt(i++) | 0;
12
+ }
13
+ }
14
+ return hashNumbers;
15
+ };
16
+
17
+ for (let i = 0; i < titres.length; i++) {
18
+ let titre = titres[i];
19
+ const content = titre.textContent;
20
+ const charsArray = ['b', 'g', 'o', 'q'];
21
+ const charsIndexes: number[] = [];
22
+
23
+ content.toLowerCase().split('').map((i, j) => {
24
+ if (charsArray.indexOf(i) > -1) {
25
+ charsIndexes.push(j);
26
+ }
27
+ return i;
28
+ });
29
+
30
+ if (charsIndexes.length) {
31
+ const hashContent = hashCode(content);
32
+ const position = Math.abs(hashContent % charsIndexes.length);
33
+ const char = content.charAt(charsIndexes[position]);
34
+ const beforeChar = content.substring(0, charsIndexes[position]);
35
+ const afterChar = content.substring(charsIndexes[position] + 1);
36
+ titre.innerHTML = `${beforeChar}<span class="has-variant">${char}</span>${afterChar}`;
37
+ }
38
+ titre.setAttribute(`data-fontVariant-initialized`, 'true')
39
+ }
40
+ };
41
+
42
+ export default initVariant;
@@ -0,0 +1,111 @@
1
+ interface IVerifies {
2
+ [key: string]: { fn: (e: string) => boolean; ref: HTMLElement };
3
+ }
4
+
5
+ export const initInputGauge = () => {
6
+ const pwdInput = document.querySelectorAll<HTMLElement>("[data-has-gauge]");
7
+
8
+ if (pwdInput) {
9
+ pwdInput.forEach((htmlElement: HTMLElement) => {
10
+ const green: string = "#007B52";
11
+ const grey: string = "#707070";
12
+ const red: string = "#D42D02";
13
+ const yellow: string = "#FFBB33";
14
+
15
+ const input = htmlElement.querySelector("input") as HTMLElement;
16
+ const gauge = htmlElement.querySelector("[data-gauge]") as HTMLElement;
17
+ const min = htmlElement.querySelector("[data-length-min]")?.getAttribute("data-length-min");
18
+ const max = htmlElement.querySelector("[data-length-max]")?.getAttribute("data-length-max");
19
+ const dataVerifies: IVerifies = {};
20
+ let nbVerified = 0;
21
+
22
+ const lengthVerify = {
23
+ fn: (e: string) => {
24
+ if (max && !min) {
25
+ return e.length > 0 && e.length <= Number(max);
26
+ }
27
+ if (min && !max) {
28
+ return e.length >= Number(min);
29
+ }
30
+ if (max && min) {
31
+ return e.length >= Number(min) && e.length <= Number(max);
32
+ }
33
+ return false;
34
+ },
35
+ ref: htmlElement.querySelector("[data-security-length]") as HTMLElement,
36
+ };
37
+
38
+ const specialCharsverify = {
39
+ fn: (e: string) => /[^\w\*]/.test(e),
40
+ ref: htmlElement.querySelector("[data-security-special-chars]") as HTMLElement,
41
+ };
42
+
43
+ const numberVerify = {
44
+ fn: (e: string) => /[0-9]/.test(e),
45
+ ref: htmlElement.querySelector("[data-security-number]") as HTMLElement,
46
+ };
47
+
48
+ const uppercaseVerify = {
49
+ fn: (e: string) => /[A-Z]/.test(e),
50
+ ref: htmlElement.querySelector("[data-security-uppercase]") as HTMLElement,
51
+ };
52
+
53
+ const lowercaseVerify = {
54
+ fn: (e: string) => /[a-z]/.test(e),
55
+ ref: htmlElement.querySelector("[data-security-lowercase]") as HTMLElement,
56
+ };
57
+
58
+ if (numberVerify.ref) Object.assign(dataVerifies, { numberVerify });
59
+ if (lengthVerify.ref) Object.assign(dataVerifies, { lengthVerify });
60
+ if (lowercaseVerify.ref) Object.assign(dataVerifies, { lowercaseVerify });
61
+ if (uppercaseVerify.ref) Object.assign(dataVerifies, { uppercaseVerify });
62
+ if (specialCharsverify.ref) Object.assign(dataVerifies, { specialCharsverify });
63
+ nbVerified = Object.keys(dataVerifies).length;
64
+
65
+ const handleChangeIconsVerify = (e: string): number => {
66
+ const verifiesTests: boolean[] = [];
67
+
68
+ Object.keys(dataVerifies).map((key: string) => {
69
+ const test = dataVerifies[key].fn(e);
70
+ const icon = dataVerifies[key].ref.querySelector("[data-icon-securities]").querySelector("i");
71
+ verifiesTests.push(test);
72
+
73
+ if (test) {
74
+ icon.classList.remove("tri-times-circle");
75
+ icon.classList.add("tri-check-circle", "is-success");
76
+ } else {
77
+ icon.classList.remove("tri-check-circle", "is-success");
78
+ icon.classList.add("tri-times-circle");
79
+ }
80
+ });
81
+ return verifiesTests.filter((item) => item).length;
82
+ };
83
+
84
+ const handleChangeGauge = (points: number) => {
85
+ const calc = Number(((points / Object.keys(dataVerifies).length) * 100).toFixed(0));
86
+ if (calc <= 50 && calc > 0) {
87
+ gauge.style.width = "50%";
88
+ gauge.style.backgroundColor = red;
89
+ } else if (calc <= 99 && calc > 50) {
90
+ gauge.style.width = "75%";
91
+ gauge.style.backgroundColor = yellow;
92
+ } else if (calc === 100) {
93
+ gauge.style.width = "100%";
94
+ gauge.style.backgroundColor = green;
95
+ } else {
96
+ gauge.style.width = "0%";
97
+ gauge.style.backgroundColor = grey;
98
+ }
99
+ };
100
+
101
+ const onInput = (e: InputEvent) => {
102
+ const value = (e.target as HTMLInputElement).value;
103
+ const points = handleChangeIconsVerify(value);
104
+ handleChangeGauge(points);
105
+ };
106
+
107
+ input.addEventListener("input", onInput);
108
+ htmlElement.setAttribute(`data-gauges-initialized`, 'true')
109
+ });
110
+ }
111
+ };
@@ -0,0 +1,36 @@
1
+ export const initInputIcon = () => {
2
+ const iconsShowPwd = document.querySelectorAll<HTMLElement>("[data-show-pwd]");
3
+
4
+ if (iconsShowPwd) {
5
+ iconsShowPwd.forEach((htmlElement: HTMLElement) => {
6
+ const changeIcon = (icon: HTMLElement) => {
7
+ if (icon.classList.contains("tri-eye")) {
8
+ icon.classList.remove("tri-eye");
9
+ icon.classList.add("tri-eye-slash");
10
+ } else {
11
+ icon.classList.remove("tri-eye-slash");
12
+ icon.classList.add("tri-eye");
13
+ }
14
+ };
15
+
16
+ const changeTypeInput = (icon: HTMLElement) => {
17
+ const parent = icon.parentNode.parentNode;
18
+ let input = parent.querySelector("input");
19
+ if (!input) input = parent.parentNode.querySelector("input");
20
+ if (input.type === "password") {
21
+ input.type = "text";
22
+ } else {
23
+ input.type = "password";
24
+ }
25
+ };
26
+
27
+ const handleClickIcon = (e: Event) => {
28
+ const target = e.target as HTMLElement;
29
+ changeIcon(target);
30
+ changeTypeInput(target);
31
+ };
32
+ htmlElement.addEventListener("click", handleClickIcon);
33
+ htmlElement.setAttribute(`data-iconsShowPwd-initialized`, 'true')
34
+ });
35
+ }
36
+ };
@@ -0,0 +1,104 @@
1
+ export const initModals = () => {
2
+
3
+ let modalContexts: NodeListOf<HTMLElement> = document.querySelectorAll('[data-modal-context]');
4
+
5
+ for (let i = 0; i < modalContexts.length; i++) {
6
+ let modalContext: HTMLElement = modalContexts[i];
7
+
8
+ let modalOpenButtons: NodeListOf<HTMLElement> = modalContext.querySelectorAll('[data-modal-open]');
9
+ let modalElement: HTMLElement = modalContext.querySelector('[data-modal]');
10
+ let modalCloseButtons: NodeListOf<HTMLElement> = modalContext.querySelectorAll('[data-modal-close]');
11
+ let modalVideos: NodeListOf<HTMLVideoElement> = modalContext.querySelectorAll('iframe[src*="www.youtube.com"], iframe[src*="player.vimeo.com"], video');
12
+
13
+ for (let j = 0; j < modalOpenButtons.length; j++) {
14
+ let modalOpenButton = modalOpenButtons[j];
15
+ let modalVideo: HTMLVideoElement = modalVideos[j];
16
+
17
+ modalOpenButton.addEventListener('click', function () {
18
+ makeModalActive(modalElement);
19
+ if (!modalVideo) {
20
+ return;
21
+ } else {
22
+ if (modalVideo.tagName.toLowerCase() === 'video') {
23
+ modalVideo.play();
24
+ return;
25
+ }
26
+ modalVideo.src = modalVideo.src + (modalVideo.src.indexOf('?') < 0 ? '?' : '&') + 'autoplay=1';
27
+ }
28
+ });
29
+ }
30
+
31
+ for (let j = 0; j < modalCloseButtons.length; j++) {
32
+ let modalCloseButton = modalCloseButtons[j];
33
+ modalCloseButton.addEventListener('click', function () {
34
+ makeModalInactive(modalElement);
35
+ });
36
+ }
37
+
38
+ initClosingListeners(modalElement);
39
+
40
+ // TODO: supprimer en 1.0.0
41
+ // rétrocompatibilité : fermeture de la modal en cliquant n'importe où
42
+ const background: HTMLElement = modalContext.querySelector('.modal-background');
43
+ if (background) {
44
+ background.onclick = function () {
45
+ makeModalInactive(modalElement);
46
+ }
47
+ }
48
+ modalContext.setAttribute(`data-modal-initialized`, 'true')
49
+ }
50
+
51
+ /**
52
+ * Initialise l'écoute des événements permettant de fermer une modale
53
+ */
54
+ function initClosingListeners(modalElement: HTMLElement) {
55
+ document.addEventListener('keyup', function (e: any) {
56
+ e = e || window.event;
57
+ var isEscape = false;
58
+ if ('key' in e) {
59
+ isEscape = (e.key === 'Escape' || e.key === 'Esc');
60
+ } else {
61
+ isEscape = (e.keyCode === 27);
62
+ }
63
+ if (isEscape) {
64
+ makeModalInactive(modalElement);
65
+ }
66
+ });
67
+
68
+ document.addEventListener('click', function (e: MouseEvent) {
69
+ const target = e.target as HTMLInputElement
70
+ let modalContent = target.closest('[data-modal-content]');
71
+ let isModal = target.classList.contains('modal');
72
+ if (modalContent === null && isModal == true) {
73
+ makeModalInactive(modalElement);
74
+ }
75
+ });
76
+ }
77
+
78
+ /**
79
+ * Make an element active
80
+ * @param { Element } modalElementToMakeActive element to be made active
81
+ */
82
+ const makeModalActive = (modalElementToMakeActive: HTMLElement) => {
83
+ modalElementToMakeActive.classList.add('is-active');
84
+ document.body.style.overflow = 'hidden';
85
+ };
86
+
87
+ /**
88
+ * Make an element inactive
89
+ * @param { Element } modalElementToMakeInactive element to be made inactive
90
+ */
91
+ const makeModalInactive = (modalElementToMakeInactive: HTMLElement) => {
92
+ let video: HTMLVideoElement = modalElementToMakeInactive.querySelector('iframe[src*="youtube"], iframe[src*="vimeo"], video');
93
+ if (video) {
94
+ if (video.tagName.toLowerCase() === 'video') {
95
+ video.pause();
96
+ return;
97
+ }
98
+ video.src = video.src.replace('&autoplay=1', '').replace('?autoplay=1', '');
99
+ }
100
+ modalElementToMakeInactive.classList.remove('is-active');
101
+ document.body.style.overflow = null;
102
+ };
103
+
104
+ };
@@ -0,0 +1,45 @@
1
+ export const initProgressRadial = (progressRadial: HTMLElement) => {
2
+ const isInitialized = progressRadial.getAttribute('data-progress-radial-initialized');
3
+ if (!isInitialized) {
4
+ let firstProgressValueMax: number = Number(progressRadial.getAttribute('data-progress-radial-first-value'));
5
+ let secondProgressValueMax: number = Number(progressRadial.getAttribute('data-progress-radial-second-value'));
6
+ let backgroundStyle: string = '';
7
+
8
+ let firstProgressCurrentValue: number = 0;
9
+ let secondProgressCurrentValue: number = 0;
10
+
11
+ // If second value does not exist
12
+ if (secondProgressValueMax === 0) {
13
+ const animation = setInterval(() => {
14
+ if (firstProgressCurrentValue !== firstProgressValueMax) {
15
+ firstProgressCurrentValue += 1;
16
+ } else {
17
+ clearInterval(animation);
18
+ }
19
+ backgroundStyle = `radial-gradient(white 58%, transparent 51%),
20
+ conic-gradient(#0C7B91 0deg ${(360 * (firstProgressCurrentValue / 100))}deg,
21
+ gainsboro ${(360 * (firstProgressCurrentValue / 100))}deg 360deg)`;
22
+ progressRadial.style.background = backgroundStyle;
23
+ }, 13);
24
+ } else {
25
+ secondProgressValueMax += firstProgressValueMax;
26
+ const animation = setInterval(() => {
27
+ if (firstProgressCurrentValue < firstProgressValueMax) {
28
+ firstProgressCurrentValue += 1;
29
+ }
30
+ if (secondProgressCurrentValue < secondProgressValueMax) {
31
+ secondProgressCurrentValue += 1;
32
+ } else {
33
+ clearInterval(animation);
34
+ }
35
+ backgroundStyle = `radial-gradient(white 58%, transparent 51%),
36
+ conic-gradient(#0C7B91 0deg ${(360 * (firstProgressCurrentValue / 100))}deg,
37
+ #25465f ${(360 * (firstProgressCurrentValue / 100))}deg ${(360 * (secondProgressCurrentValue / 100))}deg,
38
+ gainsboro ${(360 * (secondProgressCurrentValue / 100))}deg 360deg)`;
39
+ progressRadial.style.background = backgroundStyle;
40
+ }, 13);
41
+ }
42
+
43
+ progressRadial.setAttribute('data-progress-radial-initialized', 'true');
44
+ }
45
+ };
@@ -0,0 +1,48 @@
1
+ export const initRange = (rangeContainer: HTMLElement) => {
2
+
3
+ let HTMLInputRangeMin: HTMLInputElement = rangeContainer.querySelector(".range-cursor-min");
4
+ let HTMLInputRangeMax: HTMLInputElement = rangeContainer.querySelector(".range-cursor-max");
5
+ let track: HTMLDivElement = rangeContainer.querySelector(".range-track");
6
+ let valueMin: HTMLHtmlElement = rangeContainer.querySelector(".range-value-min");
7
+ let valueMax: HTMLHtmlElement = rangeContainer.querySelector(".range-value-max");
8
+
9
+ let maxValue = HTMLInputRangeMin.max;
10
+ let gap = 0;
11
+
12
+ const setColor = () => {
13
+ let percent1 =
14
+ (Number(HTMLInputRangeMin.value) / Number(maxValue)) * 100;
15
+ let percent2 =
16
+ (Number(HTMLInputRangeMax.value) / Number(maxValue)) * 100;
17
+ track.style.background = `linear-gradient(to right, #E1E1E1 ${percent1}% , #0C7B91 ${percent1}% , #0C7B91 ${percent2}%, #E1E1E1 ${percent2}%) `;
18
+ };
19
+
20
+ setColor();
21
+
22
+ HTMLInputRangeMin.addEventListener("input", (e: InputEvent) => {
23
+ const event = e.target as HTMLInputElement;
24
+ if (Number(event.value) < Number(HTMLInputRangeMax.value) - gap) {
25
+ HTMLInputRangeMin.value = event.value;
26
+ } else {
27
+ HTMLInputRangeMin.value = String(
28
+ Number(HTMLInputRangeMax.value) - gap
29
+ );
30
+ }
31
+ valueMin.textContent = event.value;
32
+ setColor();
33
+ });
34
+
35
+ HTMLInputRangeMax.addEventListener("input", (e: InputEvent) => {
36
+ const event = e.target as HTMLInputElement;
37
+ if (Number(event.value) > Number(HTMLInputRangeMin.value) + gap) {
38
+ HTMLInputRangeMax.value = event.value;
39
+ } else {
40
+ HTMLInputRangeMax.value = String(
41
+ Number(HTMLInputRangeMin.value) + gap
42
+ );
43
+ }
44
+ valueMax.textContent = event.value;
45
+ setColor();
46
+ });
47
+ rangeContainer.setAttribute(`data-ranges-initialized`, 'true')
48
+ };
@@ -0,0 +1,16 @@
1
+ export const initSegmentedControl = () => {
2
+ const HTMLButtons = document.querySelectorAll(".segmented-control-item");
3
+
4
+ HTMLButtons.forEach((button: HTMLButtonElement) => {
5
+ button.addEventListener("click", function () {
6
+ this.parentElement
7
+ .querySelector(".is-active")
8
+ .classList.remove("is-active");
9
+
10
+ if (!button.classList.contains("is-active")) {
11
+ button.classList.add("is-active");
12
+ }
13
+ });
14
+ button.setAttribute(`data-segmentedControl-initialized`, 'true')
15
+ });
16
+ };
@@ -0,0 +1,48 @@
1
+ export const initSelects = () => {
2
+ const HTMLSelects = document.querySelectorAll('[data-select-name]')
3
+
4
+ if (HTMLSelects) {
5
+ HTMLSelects.forEach((HTMLSelect: HTMLDivElement) => {
6
+ const HTMLLabel = HTMLSelect.querySelector('label')
7
+ if (!HTMLSelect.classList.contains('select-disabled')) {
8
+ const HTMLParentOfSelect = HTMLSelect.parentNode as HTMLElement
9
+ let HTMLOptions = HTMLParentOfSelect.querySelector('[data-is-open-options]')
10
+
11
+ HTMLSelect.addEventListener('click', () => {
12
+ const isOpenOptions = HTMLOptions.getAttribute('data-is-open-options')
13
+ if (isOpenOptions === 'true') HTMLOptions.setAttribute('data-is-open-options', 'false')
14
+ if (isOpenOptions === 'false') HTMLOptions.setAttribute('data-is-open-options', 'true')
15
+ })
16
+
17
+ HTMLSelect.addEventListener('blur', () => {
18
+ HTMLOptions.setAttribute('data-is-open-options', 'false')
19
+ })
20
+
21
+ const options = HTMLOptions.querySelectorAll('[data-option-name]')
22
+ options.forEach((option: HTMLElement) => {
23
+ if (!option.classList.contains('select-options-option-disabled')) {
24
+ option.addEventListener('mousedown', () => {
25
+ // select option
26
+ options.forEach((opt: HTMLElement) => opt.classList.remove('select-options-option-activated'))
27
+ option.classList.add('select-options-option-activated')
28
+
29
+ // insert/update otp
30
+ let HTMLSpan: HTMLSpanElement = HTMLSelect.querySelector('[data-option-selected]')
31
+ if (!HTMLSpan) {
32
+ HTMLSpan = document.createElement('span')
33
+ HTMLSpan.classList.add('select-value')
34
+ if (!HTMLLabel || HTMLLabel === null) HTMLSpan.classList.add('no-label')
35
+ HTMLSelect.appendChild(HTMLSpan)
36
+ }
37
+ HTMLSelect.setAttribute('data-option-selected', option.getAttribute('data-option-name'))
38
+ HTMLSpan.setAttribute('data-option-selected', option.getAttribute('data-option-name'))
39
+ HTMLSpan.textContent = option.getAttribute('data-option-name')
40
+ HTMLParentOfSelect.classList.add('has-dynamic-placeholder')
41
+ })
42
+ }
43
+ })
44
+ }
45
+ HTMLSelect.setAttribute(`data-selects-initialized`, 'true')
46
+ })
47
+ }
48
+ }
@@ -0,0 +1,14 @@
1
+ export const initSticky = () => {
2
+ const navbar = document.querySelector('.navbar');
3
+
4
+ const myObserverCallback: any = (event: any) => {
5
+ navbar.classList.toggle('enable-transition', event.intersectionRatio < 1);
6
+ setTimeout(() => {
7
+ event.target.parentNode.classList.toggle('is-sticky', event.intersectionRatio < 1)
8
+ }, 100);
9
+ };
10
+
11
+ let observer = new IntersectionObserver(myObserverCallback, { threshold: 1 });
12
+ observer.observe(navbar);
13
+
14
+ };
@@ -0,0 +1,11 @@
1
+ export const initTableExpansion = () => {
2
+ let expandableRows: NodeListOf<HTMLElement> = document.querySelectorAll('[data-expandable-row]');
3
+ for (let i = 0; i < expandableRows.length; i++) {
4
+ let expandableRow: HTMLElement = expandableRows[i];
5
+ let trigger: HTMLElement = expandableRow.querySelector('[data-expandable-trigger]');
6
+ trigger.addEventListener('click', function () {
7
+ expandableRow.classList.toggle('is-expanded');
8
+ });
9
+ expandableRow.setAttribute(`data-tableexpansion-initialized`, 'true')
10
+ }
11
+ }
@@ -0,0 +1,102 @@
1
+ export const initTabs = () => {
2
+ const tabContexts = document.querySelectorAll('[data-tabs-context]')
3
+
4
+ const styleCusto =
5
+ 'right: 0;' +
6
+ 'padding-left: 16px;' +
7
+ 'width: 4.5rem !important;' +
8
+ 'background: linear-gradient(90deg, rgba(256,256,256, 0) -25%, white 30%);\n' +
9
+ 'background-attachment: local, local, scroll, scroll;'
10
+
11
+ const styledTabs = `
12
+ .tabs {
13
+ padding-right: 2rem;
14
+ }
15
+ `
16
+
17
+ const styleTabAttr = document.createElement('style')
18
+ styleTabAttr.innerText = styledTabs
19
+
20
+ tabContexts.forEach((tabContext) => {
21
+ const tabContextWidth = tabContext.clientWidth
22
+ let tabs = tabContext.querySelector('.tabs')
23
+ if(!tabs) {
24
+ tabs = tabContext.querySelector('[data-real-class*="tabs"]')
25
+ }
26
+ const tabsWidth = tabs.scrollWidth
27
+
28
+ if (tabsWidth > tabContextWidth) {
29
+ if (!tabs.innerHTML.includes('icon is-small is-absolute')) {
30
+ const arrowIcon = `<span class="icon is-small is-absolute" style="${styleCusto}"><i class="tri-arrow-right" aria-hidden=\'true\'></i></span>`
31
+ tabs.innerHTML += arrowIcon
32
+ tabs.appendChild(styleTabAttr)
33
+ }
34
+ }
35
+ })
36
+
37
+ let tabsContext = document.querySelectorAll('[data-tabs-context]')
38
+ for (let i = 0; i < tabsContext.length; i++) {
39
+ let tabContext = tabsContext[i]
40
+ let tabs: NodeListOf<HTMLElement> = tabContext.querySelectorAll('[data-tab-navigation]')
41
+ let tabsContent: NodeListOf<HTMLElement> = tabContext.querySelectorAll('[data-tab-content]')
42
+ for (let j = 0; j < tabs.length; j++) {
43
+ let tabElement: HTMLElement = tabs[j]
44
+ let tabContent: HTMLElement = tabsContent[j]
45
+ tabElement.addEventListener('click', function () {
46
+ makeTabActive(tabElement, tabContent)
47
+ })
48
+ tabElement.addEventListener('keyup', (e: KeyboardEvent) => {
49
+ e.preventDefault()
50
+ switch (e.keyCode) {
51
+ case 35: // end key
52
+ makeTabActive(tabs[tabs.length - 1], tabsContent[tabs.length - 1])
53
+ break
54
+ case 36: // home key
55
+ makeTabActive(tabs[0], tabsContent[0])
56
+ break
57
+ case 37: // left arrow
58
+ let previous = (j - 1) % tabs.length
59
+ makeTabActive(tabs[previous], tabsContent[previous])
60
+ break
61
+ case 39: // right arrow
62
+ let next = (j + 1) % tabs.length
63
+ makeTabActive(tabs[next], tabsContent[next])
64
+ break
65
+ }
66
+ })
67
+ }
68
+
69
+ /**
70
+ * Make an element inactive
71
+ */
72
+ const makeAllTabsInactive = () => {
73
+ for (let i = 0; i < tabs.length; i++) {
74
+ tabs[i].classList.remove('is-active')
75
+ tabs[i].setAttribute('aria-selected', 'false')
76
+ tabs[i].setAttribute('tabindex', '-1')
77
+ }
78
+
79
+ for (let j = 0; j < tabsContent.length; j++) {
80
+ tabsContent[j].classList.remove('is-active')
81
+ tabsContent[j].setAttribute('aria-expanded', 'false')
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Make an element active
87
+ * @param { Element } tabElementToMakeActive element to be made active
88
+ * @param tabContentToMakeActive
89
+ */
90
+ const makeTabActive = (tabElementToMakeActive: HTMLElement, tabContentToMakeActive: HTMLElement) => {
91
+ makeAllTabsInactive()
92
+ tabElementToMakeActive.classList.add('is-active')
93
+ tabElementToMakeActive.setAttribute('aria-selected', 'true')
94
+ tabElementToMakeActive.setAttribute('tabindex', '0')
95
+ tabElementToMakeActive.focus()
96
+ tabContentToMakeActive.classList.add('is-active')
97
+ tabContentToMakeActive.setAttribute('aria-expanded', 'true')
98
+ }
99
+
100
+ tabContext.setAttribute(`data-tab-initialized`, 'true')
101
+ }
102
+ }
@@ -0,0 +1,27 @@
1
+ export const initTextarea = () => {
2
+ const textareaWrappers = document.querySelectorAll(".textarea-wrapper");
3
+ textareaWrappers.forEach((textareaWrapper: HTMLElement) => {
4
+ const isInitialized = textareaWrapper.getAttribute('data-textarea-initialized');
5
+ if(!isInitialized) {
6
+ const textarea: HTMLTextAreaElement = textareaWrapper.querySelector('textarea.textarea');
7
+
8
+ const counterMaxLength = textarea.getAttribute('maxlength');
9
+ if (counterMaxLength) {
10
+ var currentLength = 0;
11
+ const counter = document.createElement('div');
12
+ counter.classList.add('counter');
13
+ counter.innerHTML = `${currentLength}/${counterMaxLength}`;
14
+ textareaWrapper.appendChild(counter);
15
+
16
+ textarea.addEventListener("input", function (e) {
17
+ currentLength = this.value.length;
18
+ counter.innerHTML = `${currentLength}/${counterMaxLength}`;
19
+ if(currentLength === 10) {
20
+ e.preventDefault();
21
+ }
22
+ });
23
+ }
24
+ textareaWrapper.setAttribute('data-textarea-initialized', 'true');
25
+ }
26
+ });
27
+ };
@@ -0,0 +1,13 @@
1
+ type functionToPlayType = (htmlElement: HTMLElement) => void;
2
+
3
+ export const intersectionObserver = (elementToObserve: HTMLElement, functionToPlay: functionToPlayType) => {
4
+ const observer = new IntersectionObserver((entries) => {
5
+ entries.forEach((entry: any) => {
6
+ if (entry.isIntersecting) {
7
+ functionToPlay(entry.target);
8
+ }
9
+ });
10
+ });
11
+
12
+ observer.observe(elementToObserve);
13
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "compilerOptions": {
3
+ "outDir": "./lib/",
4
+ "noImplicitAny": true,
5
+ "module": "es6",
6
+ "target": "es5",
7
+ "allowJs": true
8
+ },
9
+ "include": ["./src/**/*"]
10
+ }
package/vite.config.js ADDED
@@ -0,0 +1,23 @@
1
+ import { defineConfig } from 'vite'
2
+ import { resolve } from 'path'
3
+ import tsconfigPaths from 'vite-tsconfig-paths'
4
+
5
+ export default defineConfig({
6
+ plugins: [tsconfigPaths()],
7
+ root: resolve(__dirname, 'src/'),
8
+ base: './',
9
+ mode: 'production',
10
+ build: {
11
+ outDir: '../lib',
12
+ lib: {
13
+ entry: resolve(__dirname, 'src/app.ts'),
14
+ name: 'trilogy-ds-vanilla',
15
+ fileName: () => `trilogy-ds-vanilla.js`,
16
+ },
17
+ rollupOptions: {
18
+ input: {
19
+ main: resolve(__dirname, 'src/app.ts'),
20
+ },
21
+ },
22
+ }
23
+ })