qc-trousse-sdg 1.4.7 → 1.4.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.
@@ -94,53 +94,25 @@ h1,h2,h3,h4,h5,h6 {
94
94
 
95
95
  a {
96
96
 
97
- &.pseudo-visited {
98
- color: token-value(color,link,visited);
99
- & > .qc-ext-link-img {
100
- background-color: token-value(color, link, visited);
101
- }
102
- }
103
97
  &.pseudo-hover {
104
98
  @include hover-link();
105
- & > .qc-ext-link-img {
106
- background-color: token-value(color, link, hover);
107
- }
99
+ --qc-color-link-visited: var(--qc-color-link-hover);
108
100
  }
109
101
  &.pseudo-active {
110
102
  @include active-link();
111
- & > .qc-ext-link-img {
112
- background-color: token-value(color, link, active);
113
- }
103
+ --qc-color-link-visited: var(--qc-color-link-active);
104
+ --qc-color-link-focus: var(--qc-color-link-active);
105
+ --qc-color-link-hover: var(--qc-color-link-active );
114
106
  }
115
107
  &.pseudo-focus {
116
108
  @include focus-link();
117
- & > .qc-ext-link-img {
118
- background-color: token-value(color, link, text);
119
- }
120
- }
121
- &.not-visited:visited:not(:hover) {
122
- color: token-value(color,link,text);
123
- & > .qc-ext-link-text {
124
- color: token-value(color,link,text);
125
- }
126
- }
127
- }
128
-
129
- div.qc-ext-link-img {
130
- a.pseudo-visited & {
131
- background: token-value(color link visited)!important;
109
+ --qc-color-link-visited: var(--qc-color-link-focus);
132
110
  }
133
- a.pseudo-focus & {
134
- background: token-value(color link hover)!important;
135
- }
136
- a.pseudo-hover & {
137
- background: token-value(color link hover)!important;
138
- }
139
- a.pseudo-active & {
140
- background: token-value(color link active)!important;
111
+ &.pseudo-visited {
112
+ --qc-color-link-text: var(--qc-color-link-visited);
141
113
  }
142
- a.not-visited:visited:not(:hover) & {
143
- background: token-value(color link text)!important;
114
+ &.not-visited {
115
+ --qc-color-link-visited: var(--qc-color-link-text);
144
116
  }
145
117
  }
146
118
 
@@ -1,7 +1,7 @@
1
1
  <h2 id="liens">Liens</h2>
2
2
  <qc-doc-exemple caption="Exemple de différents liens hypertexte selon leurs états.">
3
3
  <dl>
4
- <dt>Lien interne</dt>
4
+ <dt>Lien non visité</dt>
5
5
  <dd><a href="#" class="not-visited">Lien interne</a></dd>
6
6
  <dd>
7
7
  <qc-external-link><a href="#" class="not-visited">Lien externe</a></qc-external-link>
@@ -4,10 +4,6 @@
4
4
 
5
5
  a {
6
6
  color: token-value(color, link, text);
7
- &:has(.qc-ext-link-text) {
8
- white-space: nowrap;
9
- text-decoration: none;
10
- }
11
7
  &:visited {
12
8
  color: token-value(color, link, visited);
13
9
  }
@@ -23,35 +19,40 @@ a {
23
19
  .img-wrap {
24
20
  white-space: nowrap;
25
21
  }
26
- }
27
22
 
28
- .qc-external-link {
29
- .qc-ext-link-text {
30
- white-space: normal;
31
- text-decoration: underline;
32
- }
33
23
  }
24
+ div.qc-ext-link-img {
34
25
 
35
- .qc-ext-link-img {
36
26
  @extend .icon-external-link;
37
27
  $ratio : math.div(11,16) * 1em;
38
28
  height: $ratio;
39
29
  width: $ratio;
30
+ display: inline-block;
40
31
  background: token-value(color link text);
41
32
  mask-size: $ratio;
42
- display: inline-block;
43
- mask-repeat: no-repeat;
44
-
45
- a:visited & {
33
+ margin-left: 4px;
34
+ a:visited & {
46
35
  background: token-value(color link visited);
47
36
  }
48
- a:focus & {
37
+ a:focus & {
49
38
  background: token-value(color link hover);
50
39
  }
51
- a:hover & {
40
+ a:hover & {
52
41
  background: token-value(color link hover);
53
42
  }
54
- a:active & {
43
+ a:active & {
55
44
  background: token-value(color link active);
56
45
  }
46
+
47
+
48
+ .img-wrap + & {
49
+ display: none;
50
+ }
51
+ }
52
+
53
+ .qc-external-link .qc-button {
54
+ gap: 0;
55
+ > .img-wrap::before {
56
+ content: "\a0";
57
+ }
57
58
  }
@@ -8,6 +8,9 @@
8
8
  }
9
9
 
10
10
  .qc-button {
11
+ --qc-color-link-focus: var(--qc-color-link-text);
12
+ --qc-color-link-hover: var(--qc-color-link-text);
13
+ --qc-color-link-visited: var(--qc-color-link-text);
11
14
  font-family: token-value(font family, content);
12
15
  font-size: token-value(font size, md);
13
16
  font-weight: token-value(font weight, bold);
@@ -15,7 +18,7 @@
15
18
  display: inline-flex;
16
19
  align-items: center;
17
20
  justify-content: center;
18
- gap: rem(8);
21
+ gap: token-value(spacer, xs);
19
22
  box-sizing: border-box;
20
23
  min-width: rem(112);
21
24
  max-width: rem(360);
@@ -47,6 +50,7 @@
47
50
  }
48
51
 
49
52
  &.qc-primary {
53
+ --qc-color-link-text: #{token-value(color white)};
50
54
  color: token-value(color white);
51
55
  background-color: token-value(color blue piv);
52
56
  border-color: token-value(color blue piv);
@@ -83,6 +87,7 @@
83
87
  }
84
88
 
85
89
  &.qc-secondary {
90
+ --qc-color-link-text: #{token-value(color blue, piv)};
86
91
  color: token-value(color blue, piv);
87
92
  background-color: token-value(color white);
88
93
  border-color: token-value(color blue, piv);
@@ -91,7 +96,8 @@
91
96
  &:focus:not(:disabled),
92
97
  &:active:not(:disabled),
93
98
  &.qc-hover, &.qc-focus, &.qc-active {
94
- background-color: rgba(map.get(map.get(map.get($xl-tokens, color), blue), piv), 0.16);
99
+
100
+ background-color: rgba(var(--qc-color-blue-piv-rgb), 0.16);
95
101
  }
96
102
 
97
103
  &:focus:not(:disabled),
@@ -102,7 +108,7 @@
102
108
 
103
109
  &:active:not(:disabled),
104
110
  &.qc-active {
105
- background-color: rgba(map.get(map.get(map.get($xl-tokens, color), blue), piv), 0.08);
111
+ background-color: rgba(var(--qc-color-blue-piv-rgb), 0.08);
106
112
  }
107
113
 
108
114
  &:disabled,
@@ -114,26 +120,28 @@
114
120
  }
115
121
 
116
122
  &.qc-tertiary {
123
+ --qc-color-link-text: #{token-value(color blue, piv)};
117
124
  color: token-value(color blue, piv);
118
125
  background-color: token-value(color white);
119
126
  border-color: token-value(color white);
120
127
 
121
128
  &:hover:not(:disabled),
122
129
  &.qc-hover{
123
- background-color: rgba(map.get(map.get(map.get($xl-tokens, color), grey), light), 0.24);
130
+
131
+ background-color: rgba(var(--qc-color-grey-light-rgb), 0.24);
124
132
  text-decoration: underline;
125
133
  }
126
134
 
127
135
  &:focus:not(:disabled),
128
136
  &.qc-focus {
129
- background-color: rgba(map.get(map.get(map.get($xl-tokens, color), grey), light), 0.24);
137
+ background-color: rgba(var(--qc-color-grey-light-rgb), 0.24);
130
138
  border-color: token-value(color blue, dark);
131
139
  box-shadow: 0 0 0 2px token-value(color blue, light);
132
140
  }
133
141
 
134
142
  &:active:not(:disabled),
135
143
  &.qc-active {
136
- background-color: rgba(map.get(map.get(map.get($xl-tokens, color), grey), light), 0.16);
144
+ background-color: rgba(var(--qc-color-grey-light-rgb), 0.16);
137
145
  }
138
146
 
139
147
  &:disabled,
@@ -255,6 +255,14 @@
255
255
  }
256
256
  });
257
257
 
258
+ $effect(() => {
259
+ if (value) {
260
+ items.forEach((item) => {
261
+ item.checked = value.includes(item.value);
262
+ });
263
+ }
264
+ });
265
+
258
266
  $effect(() => {
259
267
  items.forEach((item) => {
260
268
  if (!item.id) {
@@ -13,20 +13,76 @@ let {
13
13
  } = $props();
14
14
 
15
15
  let imgElement = $state();
16
- let processedLinks = new Set();
17
16
 
18
- function addExternalLinkIcon(links) {
19
- links.forEach(link => {
20
- if (processedLinks.has(link.innerHTML)) {
21
- return;
17
+ function createVisibleNodesTreeWalker(link) {
18
+ return document.createTreeWalker(
19
+ link,
20
+ NodeFilter.SHOW_ALL,
21
+ {
22
+ acceptNode: node => {
23
+ if (node instanceof Element) {
24
+ if (node.hasAttribute('hidden')) {
25
+ return NodeFilter.FILTER_REJECT;
26
+ }
27
+ const style = window.getComputedStyle(node);
28
+ // Si l'élément est masqué par CSS (display ou visibility), on l'ignore
29
+ if (style.display === 'none'
30
+ || style.visibility === 'hidden'
31
+ || style.position === 'absolute') {
32
+ return NodeFilter.FILTER_REJECT;
33
+ }
34
+ }
35
+ if (!node instanceof Text) {
36
+ return NodeFilter.FILTER_SKIP;
37
+ }
38
+
39
+ // Ignore les nœuds vides
40
+ if (!/\S/.test(node.textContent)) {
41
+ return NodeFilter.FILTER_SKIP;
42
+ }
43
+
44
+ return NodeFilter.FILTER_ACCEPT;
45
+ }
22
46
  }
47
+ );
48
+ }
23
49
 
24
- let linkContent = link.innerHTML;
25
- linkContent = `<span class="qc-ext-link-text">${linkContent}</span>&nbsp;${imgElement.outerHTML}`;
50
+ function addExternalLinkIcon(link) {
51
+ // Crée un TreeWalker pour parcourir uniquement les nœuds texte visibles
52
+ const walker = createVisibleNodesTreeWalker(link);
26
53
 
27
- link.innerHTML = linkContent;
28
- processedLinks.add(linkContent);
29
- });
54
+ let lastTextNode = null;
55
+ while (walker.nextNode()) {
56
+ lastTextNode = walker.currentNode;
57
+ }
58
+ // S'il n'y a pas de nœud texte visible, on ne fait rien
59
+ if (!lastTextNode) {
60
+ return;
61
+ }
62
+
63
+ // Séparer le contenu du dernier nœud texte en deux parties :
64
+ // le préfixe (éventuel) et le dernier mot
65
+ const text = lastTextNode.textContent;
66
+ const match = text.match(/^([\s\S]*\s)?(\S+)\s*$/m);
67
+ if (!match) {
68
+ return;
69
+ }
70
+
71
+ const prefix = match[1] || "";
72
+ const lastWord = match[2].replace(/([\/\-\u2013\u2014])/g, "$1<wbr>");
73
+
74
+ // Crée un span avec white-space: nowrap pour empêcher le saut de ligne de l'image de lien externe
75
+ const span = document.createElement('span');
76
+ span.classList.add('img-wrap')
77
+ span.innerHTML = `${lastWord}${imgElement.outerHTML}`;
78
+
79
+ // Met à jour le nœud texte : on garde le préfixe et on insère le span après
80
+ if (prefix) {
81
+ lastTextNode.textContent = prefix;
82
+ lastTextNode.parentNode.insertBefore(span, lastTextNode.nextSibling);
83
+ } else {
84
+ lastTextNode.parentNode.replaceChild(span, lastTextNode);
85
+ }
30
86
  }
31
87
 
32
88
  $effect(() => {
@@ -37,7 +93,11 @@ $effect(() => {
37
93
  isUpdating = true;
38
94
 
39
95
  tick().then(() => {
40
- addExternalLinkIcon(links);
96
+ links.forEach(link => {
97
+ if (!link.querySelector('.qc-ext-link-img')) {
98
+ addExternalLinkIcon(link);
99
+ }
100
+ });
41
101
  return tick();
42
102
  }).then(() => {
43
103
  isUpdating = false;
@@ -51,8 +111,7 @@ $effect(() => {
51
111
  alt={externalIconAlt}
52
112
  bind:rootElement={imgElement}
53
113
  class="qc-ext-link-img"
114
+ color="link-text"
54
115
  />
55
116
  </div>
56
117
 
57
-
58
-
@@ -8,17 +8,16 @@
8
8
 
9
9
  <script>
10
10
  import ExternalLink from "./ExternalLink.svelte";
11
- import {onDestroy, onMount, tick} from "svelte";
12
11
  import {Utils} from "../utils";
12
+ import {onDestroy, onMount, tick} from "svelte";
13
13
 
14
14
  const props = $props();
15
- const nestedExternalLinks = $host().querySelector('qc-external-link');
16
-
17
- let links = $state([]);
18
- const observer = Utils.createMutationObserver($host(), refreshLinks);
15
+ let links = $state(queryLinks());
19
16
  let isUpdating = $state(false);
20
17
  let pendingUpdate = false;
18
+ const nestedExternalLinks = $host().querySelector('qc-external-link');
21
19
 
20
+ const observer = Utils.createMutationObserver($host(), refreshLinks);
22
21
  function queryLinks() {
23
22
  return Array.from($host().querySelectorAll('a'));
24
23
  }
@@ -41,7 +40,6 @@
41
40
 
42
41
  onMount(() => {
43
42
  $host().classList.add('qc-external-link');
44
- links = queryLinks();
45
43
 
46
44
  observer?.observe($host(), {
47
45
  childList: true,
@@ -50,9 +48,7 @@
50
48
  });
51
49
  });
52
50
 
53
- onDestroy(() => {
54
- observer?.disconnect();
55
- });
51
+ onDestroy(() => observer?.disconnect());
56
52
  </script>
57
53
 
58
- <ExternalLink {links} bind:isUpdating {nestedExternalLinks} {...props} />
54
+ <ExternalLink bind:links bind:isUpdating {nestedExternalLinks} {...props} />
@@ -2,12 +2,15 @@
2
2
  import IconButton from "../IconButton/IconButton.svelte";
3
3
  import {Utils} from "../utils";
4
4
  import Icon from "../../bases/Icon/Icon.svelte";
5
+ import Label from "../Label/Label.svelte";
5
6
 
6
7
  const lang = Utils.getPageLanguage();
7
8
 
8
9
 
9
10
  let {
10
11
  value = $bindable(''),
12
+ label = '',
13
+ size = '',
11
14
  ariaLabel = lang === "fr" ? "Rechercher..." : "Search...",
12
15
  clearAriaLabel = lang === "fr" ? "Effacer le texte" : "Clear text",
13
16
  leftIcon = false,
@@ -25,11 +28,20 @@
25
28
  }
26
29
  </script>
27
30
 
31
+ {#if label}
32
+ <Label
33
+ disabled={isDisabled}
34
+ text={label}
35
+ forId={id}
36
+ />
37
+ {/if}
28
38
  <div class={[
29
39
  "qc-search-input",
30
40
  leftIcon && "qc-search-left-icon",
31
41
  leftIcon && isDisabled && "qc-search-left-icon-disabled"
32
- ]} >
42
+ ]}
43
+ size={size}>
44
+
33
45
  {#if leftIcon}
34
46
  <Icon type="search-thin"
35
47
  iconColor="grey-regular"
@@ -40,7 +52,7 @@
40
52
  bind:value
41
53
  type="search"
42
54
  autocomplete="off"
43
- aria-label={ariaLabel}
55
+ aria-label={label ? undefined : ariaLabel}
44
56
  class={isDisabled ? "qc-disabled" : ""}
45
57
  id={id}
46
58
  {...rest}
@@ -5,6 +5,8 @@
5
5
  id: {attribute: 'id'},
6
6
  ariaLabel: {attribute:'aria-label'},
7
7
  clearAriaLabel: {attribute: 'clear-aria-label'},
8
+ label: {attribute: 'label'},
9
+ size: {attribute: 'size'},
8
10
  leftIcon: {attribute: 'left-icon'}
9
11
  }
10
12
  }}" />
@@ -28,6 +28,16 @@
28
28
  left-icon
29
29
  clear-aria-label="Effacer le texte"
30
30
  ></qc-search-input>
31
+
32
+ <br>
33
+
34
+ <qc-search-input
35
+ label="Nom de l'article"
36
+ size="md"
37
+ placeholder="Rechercher un article"
38
+ clear-aria-label="Effacer le texte"
39
+ left-icon
40
+ ></qc-search-input>
31
41
  </qc-doc-exemple>
32
42
 
33
43
  <h3>Documentation technique</h3>
@@ -75,10 +85,16 @@
75
85
  <td>Désactive le champ.</td>
76
86
  </tr>
77
87
  <tr>
78
- <td>name</td>
88
+ <td>label</td>
79
89
  <td>Texte</td>
80
90
  <td>""</td>
81
- <td>Nom du champ utilisé lors de la soumission du formulaire.</td>
91
+ <td>Texte du libellé du champ. Remplace automatiquement l'aria-label lorsqu'il est défini.</td>
92
+ </tr>
93
+ <tr>
94
+ <td>size</td>
95
+ <td>"xs", "sm", "md", "lg", "xl"</td>
96
+ <td>""</td>
97
+ <td>Largeur du champ</td>
82
98
  </tr>
83
99
  </tbody>
84
100
  </table>
@@ -1,10 +1,19 @@
1
- @use "../../scss/qc-sdg-lib" as *;
1
+ @use "qc-sdg-lib" as *;
2
+ @use "sass:map";
2
3
 
3
4
  qc-search-input {
4
5
  display: block;
5
6
  width: 100%;
6
7
  // TODO mettre un token
7
8
  max-width: rem(548);
9
+
10
+ $sizes: map.get($lg-tokens, size, max-width);
11
+ @each $size, $width in $sizes {
12
+ &[size="#{$size}"] {
13
+ max-width: 100%;
14
+ width: $width;
15
+ }
16
+ }
8
17
  }
9
18
 
10
19
  %qc-search-wrapper {
@@ -66,6 +66,9 @@
66
66
 
67
67
  $effect(() => {
68
68
  if (!input) return;
69
+ if (labelElement) {
70
+ input.before(labelElement);
71
+ }
69
72
  if (description) {
70
73
  input.before(descriptionElement);
71
74
  }
@@ -4,10 +4,10 @@
4
4
  <qc-doc-exemple caption="Exemples 1 - différents champs de texte. Soumettre le formulaire pour voir les messages d'erreur.">
5
5
  <form id="formulaire-champs-textes">
6
6
  <qc-textfield
7
- label="Nom complet"
8
7
  size="md"
9
8
  required
10
9
  >
10
+ <label>Nom complet</label>
11
11
  <input type="text"
12
12
  name="code-postal"
13
13
  placeholder="ex : Jean Tremblay"
@@ -63,12 +63,6 @@ textarea {
63
63
 
64
64
  $sizes: map.get($lg-tokens, size, max-width);
65
65
 
66
- @each $size, $width in $sizes {
67
- [size="#{$size}"] {
68
- max-width: 100%;
69
- }
70
- }
71
-
72
66
  input,
73
67
  textarea,
74
68
  {
@@ -461,6 +461,23 @@ test.describe('choix multiples', () => {
461
461
  await expect(page.locator('#qc-select-multiple-choices-input')).toContainText('Option 3, Option 4');
462
462
  });
463
463
 
464
+ test('manipulation externe de la valeur', {
465
+ tag: ['@multiple', '@dropdownlist'],
466
+ annotation: {
467
+ type: 'description',
468
+ description: 'Soit la liste déroulante Choix multiples, en changeant value à 1, alors option 1 est cochée.'
469
+ }
470
+ }, async ({page}) => {
471
+ // On ne manipule pas value directement sur l'élément select en slot en raison de limitations avec
472
+ // les éléments select multiple.
473
+ await page.locator('#qc-select-multiple-choices').evaluate((qcSelect: HTMLElement) => {
474
+ // @ts-ignore
475
+ qcSelect.value = ["1"];
476
+ });
477
+
478
+ await expect(page.locator('#select-multiple-choices > option[value="1"]')).toHaveAttribute("selected");
479
+ });
480
+
464
481
  test('choix multiples 2 clics par option', {
465
482
  tag: ['@multiple', '@dropdownlist'],
466
483
  annotation: {