codevdesign 1.0.47 → 1.0.49

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.
@@ -1,102 +1,193 @@
1
- <template>
2
- <v-row
3
- dense
4
- class="align-center"
5
- >
6
- <!-- Texte + détails -->
7
- <v-col
8
- cols="10"
9
- xl="11"
10
- class="py-0"
11
- >
12
- <component
13
- :is="labelCliquable ? 'label' : 'div'"
14
- :for="labelCliquable ? switchId : undefined"
15
- class="labelSwitchSiSwitchApres"
16
- :class="{ 'label-cliquable': labelCliquable && !desactiver }"
17
- >
18
- <slot name="label">
19
- {{ texte }}
20
- </slot>
21
- </component>
22
-
23
- <div
24
- v-if="afficherDetails"
25
- class="details"
26
- >
27
- <slot name="details">
28
- <span v-html="texteDetaille"></span>
29
- </slot>
30
- </div>
31
- </v-col>
32
-
33
- <!-- Switch -->
34
- <v-col
35
- cols="2"
36
- xl="1"
37
- class="d-flex align-center justify-end py-0"
38
- >
39
- <v-switch
40
- :id="switchId"
41
- v-model="maValeur"
42
- class="switch-compact"
43
- :disabled="desactiver"
44
- :density="($attrs.density as any) ?? 'compact'"
45
- :inset="($attrs.inset as any) ?? true"
46
- v-bind="$attrs"
47
- hide-details
48
- />
49
- </v-col>
50
- </v-row>
51
- </template>
52
-
53
- <script setup lang="ts">
54
- import { computed, useSlots } from 'vue'
55
-
56
- const props = defineProps({
57
- valeurInverse: { type: Boolean, default: false },
58
- desactiver: { type: Boolean, default: false },
59
- modelValue: { type: Boolean, required: true },
60
- texte: { type: String, required: true },
61
- texteDetaille: { type: String, default: '' },
62
- labelCliquable: { type: Boolean, default: true },
63
- })
64
-
65
- const emit = defineEmits(['update:modelValue'])
66
- const slots = useSlots()
67
-
68
- const maValeur = computed<boolean>({
69
- get: () => (props.valeurInverse ? !props.modelValue : props.modelValue),
70
- set: v => emit('update:modelValue', props.valeurInverse ? !v : v),
71
- })
72
-
73
- const afficherDetails = computed(() => {
74
- const slotExiste = !!slots.details?.().length
75
- return slotExiste || props.texteDetaille.trim().length > 0
76
- })
77
-
78
- const switchId = `sw_${Math.random().toString(36).slice(2)}`
79
- </script>
80
-
81
- <style scoped>
82
- .labelSwitchSiSwitchApres {
83
- font-weight: bold;
84
- line-height: 1.2;
85
- }
86
-
87
- .label-cliquable {
88
- cursor: pointer;
89
- }
90
-
91
- .details {
92
- margin-top: 4px;
93
- }
94
-
95
- /* compact switch */
96
- :deep(.switch-compact.v-input) {
97
- margin: 0;
98
- }
99
- :deep(.switch-compact .v-selection-control) {
100
- margin: 0;
101
- }
102
- </style>
1
+ <template>
2
+ <v-row
3
+ dense
4
+ class="align-center"
5
+ >
6
+ <!-- Texte + détails -->
7
+ <v-col
8
+ cols="10"
9
+ xl="11"
10
+ class="py-0"
11
+ >
12
+ <component
13
+ :is="labelCliquable ? 'label' : 'div'"
14
+ :for="labelCliquable ? switchId : undefined"
15
+ class="labelSwitchSiSwitchApres"
16
+ :class="{ 'label-cliquable': labelCliquable && !desactiver }"
17
+ >
18
+ <slot name="label">
19
+ {{ texte }}
20
+ </slot>
21
+ </component>
22
+
23
+ <div
24
+ v-if="afficherDetails"
25
+ class="details"
26
+ >
27
+ <slot name="details">
28
+ <span v-html="texteDetaille"></span>
29
+ </slot>
30
+ </div>
31
+ </v-col>
32
+
33
+ <!-- Switch -->
34
+ <v-col
35
+ cols="2"
36
+ xl="1"
37
+ class="d-flex align-center justify-end py-0"
38
+ >
39
+ <span class="d-inline-flex">
40
+ <v-switch
41
+ :id="switchId"
42
+ class="switch-compact switch-tristate"
43
+ :class="{
44
+ 'is-null': maValeur === null,
45
+ 'is-true': maValeur === true,
46
+ 'is-false': maValeur === false,
47
+ }"
48
+ :disabled="desactiver"
49
+ hide-details
50
+ v-bind="$attrs"
51
+ :model-value="switchChecked"
52
+ :indeterminate="isIndeterminate"
53
+ indeterminate-icon="mdi-minus"
54
+ @click.prevent="onToggle"
55
+ @keydown.enter.prevent="onToggle"
56
+ @keydown.space.prevent="onToggle"
57
+ />
58
+
59
+ <v-tooltip
60
+ v-if="maValeur === null"
61
+ activator="parent"
62
+ >
63
+ {{ $t("csqc.csqcOptionSwitch.indeterminee") }}
64
+ </v-tooltip>
65
+ </span>
66
+ </v-col>
67
+ </v-row>
68
+ </template>
69
+
70
+ <script setup lang="ts">
71
+ import { computed, useSlots } from 'vue'
72
+
73
+ type TriBool = boolean | null
74
+
75
+ const props = defineProps({
76
+ valeurInverse: { type: Boolean, default: false },
77
+ desactiver: { type: Boolean, default: false },
78
+ modelValue: { type: [Boolean, null] as unknown as () => TriBool, default: false },
79
+ autoriserNull: { type: Boolean, default: false },
80
+ texte: { type: String, required: true },
81
+ texteDetaille: { type: String, default: '' },
82
+ labelCliquable: { type: Boolean, default: true },
83
+ })
84
+
85
+ const emit = defineEmits<{
86
+ (e: 'update:modelValue', v: TriBool): void
87
+ }>()
88
+ const isIndeterminate = computed(() => maValeur.value === null)
89
+
90
+
91
+ const slots = useSlots()
92
+
93
+ /**
94
+ * Valeur "logique" du composant (après inversion).
95
+ * Peut être true/false/null si allowNull=true.
96
+ */
97
+ const maValeur = computed<TriBool>({
98
+ get: () => (props.valeurInverse ? inverseTri(props.modelValue) : props.modelValue),
99
+ set: v => emit('update:modelValue', props.valeurInverse ? inverseTri(v) : v),
100
+ })
101
+
102
+ function inverseTri(v: TriBool): TriBool {
103
+ if (v === null) return null
104
+ return !v
105
+ }
106
+
107
+ /**
108
+ * v-switch attend un bool pour afficher ON/OFF.
109
+ * - null => on affiche OFF (false) visuellement
110
+ */
111
+ const switchChecked = computed(() => maValeur.value === true)
112
+
113
+ function onToggle() {
114
+ if (props.desactiver) return
115
+
116
+ // Mode tri-state
117
+ if (props.autoriserNull) {
118
+ // cycle: null -> true -> false -> null
119
+ const cur = maValeur.value
120
+ const next: TriBool = cur === null ? true : cur === true ? false : null
121
+ maValeur.value = next
122
+ return
123
+ }
124
+
125
+ // Mode classique (ne change rien vs avant)
126
+ maValeur.value = !switchChecked.value
127
+ }
128
+
129
+ const afficherDetails = computed(() => {
130
+ const slotExiste = !!slots.details?.().length
131
+ return slotExiste || props.texteDetaille.trim().length > 0
132
+ })
133
+
134
+ const switchId = `sw_${Math.random().toString(36).slice(2)}`
135
+ </script>
136
+
137
+ <style scoped>
138
+ /* OFF (false) */
139
+ :deep(.switch-tristate.is-false .v-switch__track) {
140
+ opacity: 0.22 !important;
141
+ }
142
+ :deep(.switch-tristate.is-false .v-switch__thumb) {
143
+ opacity: 0.65 !important;
144
+ }
145
+
146
+ /* NULL (centre + très distinct) */
147
+ :deep(.switch-tristate.is-null .v-switch__track) {
148
+ opacity: 1 !important;
149
+ background:
150
+ repeating-linear-gradient(
151
+ 45deg,
152
+ rgba(255, 255, 255, 0.22),
153
+ rgba(255, 255, 255, 0.22) 6px,
154
+ rgba(255, 255, 255, 0.05) 6px,
155
+ rgba(255, 255, 255, 0.05) 12px
156
+ ),
157
+ rgb(var(--v-theme-warning)) !important;
158
+ }
159
+
160
+ :deep(.switch-tristate.is-null .v-switch__thumb) {
161
+ transform: translateX(calc((100% - 20px) / 2)) !important;
162
+ background: white !important;
163
+ opacity: 1 !important;
164
+ box-shadow:
165
+ 0 0 0 2px rgba(var(--v-theme-warning), 0.55),
166
+ 0 2px 6px rgba(0, 0, 0, 0.18) !important;
167
+ }
168
+
169
+ /* Icône minus visible sur fond blanc */
170
+ :deep(.switch-tristate.is-null .v-selection-control__input-icon) {
171
+ color: rgb(var(--v-theme-warning)) !important;
172
+ }
173
+
174
+ /* Transition */
175
+ :deep(.switch-tristate .v-switch__thumb) {
176
+ transition:
177
+ transform 0.18s ease,
178
+ background 0.18s ease,
179
+ box-shadow 0.18s ease;
180
+ }
181
+ .labelSwitchSiSwitchApres {
182
+ font-weight: bold;
183
+ line-height: 1.2;
184
+ }
185
+
186
+ .label-cliquable {
187
+ cursor: pointer;
188
+ }
189
+
190
+ .details {
191
+ margin-top: 4px;
192
+ }
193
+ </style>