@dnax/ui 0.0.42 → 0.0.44
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/components/QAutocomplete.vue +5 -2
- package/components/QBtnActions.vue +4 -2
- package/components/QChart.vue +533 -0
- package/components/QCountryPicker.vue +4 -2
- package/components/QDatePicker.vue +99 -16
- package/components/QFab.vue +4 -2
- package/components/QNavMenu.vue +4 -2
- package/components/QSelect.vue +4 -2
- package/components/QSpreadsheet.vue +10 -4
- package/components/QSwipeCell.vue +3 -3
- package/components/QTiptap.vue +4 -2
- package/components/internal/QDateCalendar.vue +112 -19
- package/index.ts +21 -1
- package/lib/chart.test.ts +991 -0
- package/lib/chart.ts +1340 -0
- package/lib/color.test.ts +81 -0
- package/lib/color.ts +60 -0
- package/lib/datePicker.test.ts +122 -0
- package/lib/datePicker.ts +114 -0
- package/package.json +2 -9
- package/scripts/generate-exports.ts +13 -0
- package/styles/main.css +251 -130
- package/components/QEditorJs.vue +0 -146
package/styles/main.css
CHANGED
|
@@ -203,31 +203,66 @@ body {
|
|
|
203
203
|
border-color: rgb(244 67 54 / 0.4);
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
-
/* ===== Tokens de couleur (palette Material façon Quasar) =====
|
|
207
|
-
:
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
206
|
+
/* ===== Tokens de couleur (palette Material façon Quasar) =====
|
|
207
|
+
Déclarés dans `@layer dnax-tokens` : une règle hors couche (le thème de
|
|
208
|
+
l'application hôte) gagne toujours sur une règle en couche, quelle que soit
|
|
209
|
+
la spécificité et l'ordre d'émission. Sans cette couche, ce `:root` — émis
|
|
210
|
+
*après* le CSS de l'hôte — écrasait ses tokens de même nom (`--primary`,
|
|
211
|
+
`--accent`, `--muted`, `--border`…). C'est ce qui faisait apparaître le
|
|
212
|
+
violet `--accent: #9c27b0` dans les docs à thème bleu (Docd/Docus).
|
|
213
|
+
Attention : les noms shadcn n'ont pas le même sens ici (`--accent` = couleur
|
|
214
|
+
d'accent Material, pas la surface de survol shadcn). */
|
|
215
|
+
@layer dnax-tokens {
|
|
216
|
+
:root {
|
|
217
|
+
--primary: #1976d2;
|
|
218
|
+
--primary-foreground: #fff;
|
|
219
|
+
--secondary: #26a69a;
|
|
220
|
+
--secondary-foreground: #fff;
|
|
221
|
+
--accent: #9c27b0;
|
|
222
|
+
--accent-foreground: #fff;
|
|
223
|
+
--dark: #1d1d1d;
|
|
224
|
+
--dark-foreground: #fff;
|
|
225
|
+
--positive: #21ba45;
|
|
226
|
+
--positive-foreground: #fff;
|
|
227
|
+
--negative: #c10015;
|
|
228
|
+
--negative-foreground: #fff;
|
|
229
|
+
--info: #31ccec;
|
|
230
|
+
--info-foreground: #fff;
|
|
231
|
+
--warning: #f2c037;
|
|
232
|
+
--warning-foreground: #1d1d1d;
|
|
233
|
+
|
|
234
|
+
/* Tokens sémantiques (utilisés par le mode sombre) */
|
|
235
|
+
--background: #ffffff;
|
|
236
|
+
--foreground: #1d1d1d;
|
|
237
|
+
--card: #ffffff;
|
|
238
|
+
--muted: #f4f6f9;
|
|
239
|
+
--border: rgb(0 0 0 / 0.12);
|
|
240
|
+
|
|
241
|
+
/* Palette des séries de <q-chart> (convention `--chart-N`, reprise de shadcn) :
|
|
242
|
+
ce sont les SEULES couleurs de la palette Material à être faites pour ça —
|
|
243
|
+
`--secondary` et `--accent` sont des surfaces chez shadcn-vue, pas des
|
|
244
|
+
couleurs de série (une ligne de leur couleur serait invisible). */
|
|
245
|
+
--chart-1: #1976d2;
|
|
246
|
+
--chart-2: #26a69a;
|
|
247
|
+
--chart-3: #9c27b0;
|
|
248
|
+
--chart-4: #31ccec;
|
|
249
|
+
--chart-5: #21ba45;
|
|
250
|
+
--chart-6: #f2c037;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/* Mode sombre : mêmes règles de priorité que ci-dessus. */
|
|
254
|
+
.dark {
|
|
255
|
+
--background: #0f1115;
|
|
256
|
+
--foreground: #e6edf3;
|
|
257
|
+
--card: #161b22;
|
|
258
|
+
--muted: #1c2128;
|
|
259
|
+
--border: rgb(255 255 255 / 0.12);
|
|
260
|
+
--q-table-border: rgb(255 255 255 / 0.12);
|
|
261
|
+
--q-table-header-bg: rgb(255 255 255 / 0.04);
|
|
262
|
+
--q-table-hover-bg: rgb(255 255 255 / 0.06);
|
|
263
|
+
--q-sidebar-border: rgb(255 255 255 / 0.12);
|
|
264
|
+
--q-translucent-bg: #0f1115;
|
|
265
|
+
}
|
|
231
266
|
}
|
|
232
267
|
|
|
233
268
|
/* ===== Mode sombre (QConfigProvider mode="dark" | "system") =====
|
|
@@ -235,16 +270,6 @@ body {
|
|
|
235
270
|
Les overlays téléportés au body (dialog, sheets) sont couverts via html.dark. */
|
|
236
271
|
.dark {
|
|
237
272
|
color-scheme: dark;
|
|
238
|
-
--background: #0f1115;
|
|
239
|
-
--foreground: #e6edf3;
|
|
240
|
-
--card: #161b22;
|
|
241
|
-
--muted: #1c2128;
|
|
242
|
-
--border: rgb(255 255 255 / 0.12);
|
|
243
|
-
--q-table-border: rgb(255 255 255 / 0.12);
|
|
244
|
-
--q-table-header-bg: rgb(255 255 255 / 0.04);
|
|
245
|
-
--q-table-hover-bg: rgb(255 255 255 / 0.06);
|
|
246
|
-
--q-sidebar-border: rgb(255 255 255 / 0.12);
|
|
247
|
-
--q-translucent-bg: #0f1115;
|
|
248
273
|
}
|
|
249
274
|
|
|
250
275
|
/* Surface de base : le conteneur et la page (html.dark body) */
|
|
@@ -5541,6 +5566,57 @@ a.q-breadcrumbs__el:hover {
|
|
|
5541
5566
|
overflow-y: auto;
|
|
5542
5567
|
}
|
|
5543
5568
|
|
|
5569
|
+
/* — mode popover : panneau ancré au champ, sans voile — */
|
|
5570
|
+
/* `top`/`bottom`/`left` sont posés en inline par `positionPopover()` : le panneau
|
|
5571
|
+
suit le champ au scroll et bascule au-dessus quand la place manque. */
|
|
5572
|
+
.q-date-picker__overlay--popover {
|
|
5573
|
+
inset: auto;
|
|
5574
|
+
display: block;
|
|
5575
|
+
width: max-content;
|
|
5576
|
+
background-color: transparent;
|
|
5577
|
+
}
|
|
5578
|
+
|
|
5579
|
+
/* Flèche (caret) : portée par le voile et non par le panneau — `.q-date-picker__sheet`
|
|
5580
|
+
est en `overflow: hidden` et la rognerait. Elle est peinte *avant* le panneau (ordre
|
|
5581
|
+
du DOM) : seule sa moitié dépassante reste visible, le reste passe sous le bord. */
|
|
5582
|
+
.q-date-picker__overlay--popover::before {
|
|
5583
|
+
content: "";
|
|
5584
|
+
position: absolute;
|
|
5585
|
+
top: -5px;
|
|
5586
|
+
left: var(--q-date-picker-caret, 24px);
|
|
5587
|
+
width: 10px;
|
|
5588
|
+
height: 10px;
|
|
5589
|
+
margin-left: -5px;
|
|
5590
|
+
background-color: #fff;
|
|
5591
|
+
border-top: 1px solid rgb(0 0 0 / 0.08);
|
|
5592
|
+
border-left: 1px solid rgb(0 0 0 / 0.08);
|
|
5593
|
+
transform: rotate(45deg);
|
|
5594
|
+
}
|
|
5595
|
+
|
|
5596
|
+
.q-date-picker__overlay--popover-up::before {
|
|
5597
|
+
top: auto;
|
|
5598
|
+
bottom: -5px;
|
|
5599
|
+
border: none;
|
|
5600
|
+
border-right: 1px solid rgb(0 0 0 / 0.08);
|
|
5601
|
+
border-bottom: 1px solid rgb(0 0 0 / 0.08);
|
|
5602
|
+
}
|
|
5603
|
+
|
|
5604
|
+
.q-date-picker__sheet--popover {
|
|
5605
|
+
width: 360px;
|
|
5606
|
+
max-width: calc(100vw - 16px);
|
|
5607
|
+
border: 1px solid rgb(0 0 0 / 0.08);
|
|
5608
|
+
box-shadow: 0 10px 32px rgb(0 0 0 / 0.16);
|
|
5609
|
+
}
|
|
5610
|
+
|
|
5611
|
+
.dark .q-date-picker__overlay--popover::before {
|
|
5612
|
+
background-color: var(--card);
|
|
5613
|
+
border-color: rgb(255 255 255 / 0.12);
|
|
5614
|
+
}
|
|
5615
|
+
|
|
5616
|
+
.dark .q-date-picker__sheet--popover {
|
|
5617
|
+
border-color: rgb(255 255 255 / 0.12);
|
|
5618
|
+
}
|
|
5619
|
+
|
|
5544
5620
|
/* — calendrier — */
|
|
5545
5621
|
.q-date-calendar {
|
|
5546
5622
|
display: flex;
|
|
@@ -5561,6 +5637,34 @@ a.q-breadcrumbs__el:hover {
|
|
|
5561
5637
|
font-weight: 600;
|
|
5562
5638
|
}
|
|
5563
5639
|
|
|
5640
|
+
/* En-tête cliquable (prop `month-dropdown`) : même poids que le libellé simple */
|
|
5641
|
+
.q-date-calendar__nav-label--button {
|
|
5642
|
+
display: inline-flex;
|
|
5643
|
+
align-items: center;
|
|
5644
|
+
gap: 4px;
|
|
5645
|
+
padding: 4px 8px;
|
|
5646
|
+
border: none;
|
|
5647
|
+
border-radius: 8px;
|
|
5648
|
+
background: transparent;
|
|
5649
|
+
color: inherit;
|
|
5650
|
+
font: inherit;
|
|
5651
|
+
font-size: 14px;
|
|
5652
|
+
font-weight: 600;
|
|
5653
|
+
cursor: pointer;
|
|
5654
|
+
transition: background-color 0.15s ease;
|
|
5655
|
+
}
|
|
5656
|
+
|
|
5657
|
+
.q-date-calendar__nav-label--button:hover,
|
|
5658
|
+
.q-date-calendar__nav-label--open {
|
|
5659
|
+
background-color: rgb(0 0 0 / 0.06);
|
|
5660
|
+
}
|
|
5661
|
+
|
|
5662
|
+
.q-date-calendar__nav-caret {
|
|
5663
|
+
width: 14px;
|
|
5664
|
+
height: 14px;
|
|
5665
|
+
opacity: 0.65;
|
|
5666
|
+
}
|
|
5667
|
+
|
|
5564
5668
|
.q-date-calendar__nav-btn {
|
|
5565
5669
|
display: inline-flex;
|
|
5566
5670
|
align-items: center;
|
|
@@ -5659,6 +5763,88 @@ a.q-breadcrumbs__el:hover {
|
|
|
5659
5763
|
background-color: var(--primary);
|
|
5660
5764
|
}
|
|
5661
5765
|
|
|
5766
|
+
/* — choix du mois / de l'année (en-tête cliquable) — */
|
|
5767
|
+
.q-date-calendar__picker {
|
|
5768
|
+
display: flex;
|
|
5769
|
+
flex-direction: column;
|
|
5770
|
+
gap: 8px;
|
|
5771
|
+
padding: 2px 0;
|
|
5772
|
+
}
|
|
5773
|
+
|
|
5774
|
+
.q-date-calendar__picker-year {
|
|
5775
|
+
display: flex;
|
|
5776
|
+
align-items: center;
|
|
5777
|
+
justify-content: space-between;
|
|
5778
|
+
}
|
|
5779
|
+
|
|
5780
|
+
.q-date-calendar__picker-year-label {
|
|
5781
|
+
font-size: 14px;
|
|
5782
|
+
font-weight: 600;
|
|
5783
|
+
}
|
|
5784
|
+
|
|
5785
|
+
.q-date-calendar__picker-months {
|
|
5786
|
+
display: grid;
|
|
5787
|
+
grid-template-columns: repeat(3, 1fr);
|
|
5788
|
+
gap: 4px;
|
|
5789
|
+
}
|
|
5790
|
+
|
|
5791
|
+
.q-date-calendar__picker-month {
|
|
5792
|
+
padding: 8px 0;
|
|
5793
|
+
border: none;
|
|
5794
|
+
border-radius: 8px;
|
|
5795
|
+
background: transparent;
|
|
5796
|
+
color: inherit;
|
|
5797
|
+
font: inherit;
|
|
5798
|
+
font-size: 13px;
|
|
5799
|
+
cursor: pointer;
|
|
5800
|
+
transition: background-color 0.15s ease;
|
|
5801
|
+
}
|
|
5802
|
+
|
|
5803
|
+
.q-date-calendar__picker-month:hover {
|
|
5804
|
+
background-color: rgb(0 0 0 / 0.06);
|
|
5805
|
+
}
|
|
5806
|
+
|
|
5807
|
+
.q-date-calendar__picker-month--current {
|
|
5808
|
+
background-color: var(--primary);
|
|
5809
|
+
color: var(--primary-foreground);
|
|
5810
|
+
font-weight: 600;
|
|
5811
|
+
}
|
|
5812
|
+
|
|
5813
|
+
.q-date-calendar__picker-month--current:hover {
|
|
5814
|
+
background-color: var(--primary);
|
|
5815
|
+
}
|
|
5816
|
+
|
|
5817
|
+
/* — raccourci « Today » (prop `today-btn`) : bord à bord dans le panneau — */
|
|
5818
|
+
.q-date-calendar__footer {
|
|
5819
|
+
display: flex;
|
|
5820
|
+
justify-content: flex-end;
|
|
5821
|
+
margin: 0 -12px -12px;
|
|
5822
|
+
padding: 8px 12px;
|
|
5823
|
+
border-top: 1px solid rgb(0 0 0 / 0.08);
|
|
5824
|
+
}
|
|
5825
|
+
|
|
5826
|
+
.q-date-calendar__today {
|
|
5827
|
+
padding: 2px 6px;
|
|
5828
|
+
border: none;
|
|
5829
|
+
border-radius: 6px;
|
|
5830
|
+
background: transparent;
|
|
5831
|
+
color: var(--primary);
|
|
5832
|
+
font: inherit;
|
|
5833
|
+
font-size: 13px;
|
|
5834
|
+
font-weight: 500;
|
|
5835
|
+
cursor: pointer;
|
|
5836
|
+
transition: background-color 0.15s ease;
|
|
5837
|
+
}
|
|
5838
|
+
|
|
5839
|
+
.q-date-calendar__today:hover {
|
|
5840
|
+
background-color: rgb(0 0 0 / 0.04);
|
|
5841
|
+
}
|
|
5842
|
+
|
|
5843
|
+
.q-date-calendar__today:disabled {
|
|
5844
|
+
opacity: 0.4;
|
|
5845
|
+
cursor: default;
|
|
5846
|
+
}
|
|
5847
|
+
|
|
5662
5848
|
/* — transition panneau — */
|
|
5663
5849
|
.q-date-modal-enter-active,
|
|
5664
5850
|
.q-date-modal-leave-active {
|
|
@@ -5678,6 +5864,36 @@ a.q-breadcrumbs__el:hover {
|
|
|
5678
5864
|
animation: q-sheet-pop-out 0.2s ease forwards;
|
|
5679
5865
|
}
|
|
5680
5866
|
|
|
5867
|
+
/* — transition popover : le panneau entre par le bas (ou par le haut s'il est basculé) — */
|
|
5868
|
+
.q-date-popover-enter-active,
|
|
5869
|
+
.q-date-popover-leave-active {
|
|
5870
|
+
transition: opacity 0.12s ease;
|
|
5871
|
+
}
|
|
5872
|
+
|
|
5873
|
+
.q-date-popover-enter-from,
|
|
5874
|
+
.q-date-popover-leave-to {
|
|
5875
|
+
opacity: 0;
|
|
5876
|
+
}
|
|
5877
|
+
|
|
5878
|
+
.q-date-popover-enter-active .q-date-picker__sheet {
|
|
5879
|
+
animation: q-popup-in 0.12s ease;
|
|
5880
|
+
}
|
|
5881
|
+
|
|
5882
|
+
.q-date-popover-leave-active .q-date-picker__sheet {
|
|
5883
|
+
animation: q-date-popover-out 0.1s ease forwards;
|
|
5884
|
+
}
|
|
5885
|
+
|
|
5886
|
+
.q-date-picker__overlay--popover-up.q-date-popover-enter-active .q-date-picker__sheet {
|
|
5887
|
+
animation: q-popup-in-up 0.12s ease;
|
|
5888
|
+
}
|
|
5889
|
+
|
|
5890
|
+
@keyframes q-date-popover-out {
|
|
5891
|
+
to {
|
|
5892
|
+
opacity: 0;
|
|
5893
|
+
transform: translateY(-4px);
|
|
5894
|
+
}
|
|
5895
|
+
}
|
|
5896
|
+
|
|
5681
5897
|
/* ===== QCountryPicker : panneaux sheet / modal / dialog ===== */
|
|
5682
5898
|
.q-country-picker__overlay {
|
|
5683
5899
|
position: fixed;
|
|
@@ -9416,101 +9632,6 @@ a.q-breadcrumbs__el:hover {
|
|
|
9416
9632
|
border-radius: 50%;
|
|
9417
9633
|
}
|
|
9418
9634
|
|
|
9419
|
-
/* ===== QEditorJs (Editor.js, éditeur par blocs) ===== */
|
|
9420
|
-
.q-editor-js {
|
|
9421
|
-
border: 1px solid var(--border, rgb(0 0 0 / 0.12));
|
|
9422
|
-
border-radius: var(--q-radius, 12px);
|
|
9423
|
-
background: var(--card, #ffffff);
|
|
9424
|
-
color: var(--foreground, #1d1d1d);
|
|
9425
|
-
overflow: hidden;
|
|
9426
|
-
transition: border-color 0.15s ease;
|
|
9427
|
-
}
|
|
9428
|
-
.q-editor-js:focus-within {
|
|
9429
|
-
border-color: var(--primary, #1976d2);
|
|
9430
|
-
}
|
|
9431
|
-
.q-editor-js--disable {
|
|
9432
|
-
opacity: 0.7;
|
|
9433
|
-
pointer-events: none;
|
|
9434
|
-
}
|
|
9435
|
-
.q-editor-js__holder {
|
|
9436
|
-
padding: 14px 16px;
|
|
9437
|
-
}
|
|
9438
|
-
.q-editor-js .codex-editor__redactor {
|
|
9439
|
-
padding-bottom: 40px;
|
|
9440
|
-
}
|
|
9441
|
-
.q-editor-js .ce-block__content,
|
|
9442
|
-
.q-editor-js .ce-toolbar__content {
|
|
9443
|
-
max-width: none;
|
|
9444
|
-
}
|
|
9445
|
-
.q-editor-js .ce-block {
|
|
9446
|
-
margin-bottom: 8px;
|
|
9447
|
-
}
|
|
9448
|
-
.q-editor-js .ce-paragraph {
|
|
9449
|
-
color: var(--foreground, #1d1d1d);
|
|
9450
|
-
font-size: 14px;
|
|
9451
|
-
line-height: 1.7;
|
|
9452
|
-
outline: none;
|
|
9453
|
-
}
|
|
9454
|
-
.q-editor-js .ce-paragraph[data-placeholder]:empty::before {
|
|
9455
|
-
color: var(--muted, #8b93a1);
|
|
9456
|
-
}
|
|
9457
|
-
.q-editor-js .ce-header {
|
|
9458
|
-
color: var(--foreground, #1d1d1d);
|
|
9459
|
-
font-weight: 700;
|
|
9460
|
-
line-height: 1.3;
|
|
9461
|
-
}
|
|
9462
|
-
.q-editor-js h1.ce-header {
|
|
9463
|
-
font-size: 1.55em;
|
|
9464
|
-
}
|
|
9465
|
-
.q-editor-js h2.ce-header {
|
|
9466
|
-
font-size: 1.3em;
|
|
9467
|
-
}
|
|
9468
|
-
.q-editor-js h3.ce-header {
|
|
9469
|
-
font-size: 1.1em;
|
|
9470
|
-
}
|
|
9471
|
-
.q-editor-js .cdx-list {
|
|
9472
|
-
color: var(--foreground, #1d1d1d);
|
|
9473
|
-
line-height: 1.7;
|
|
9474
|
-
}
|
|
9475
|
-
.q-editor-js .cdx-checklist {
|
|
9476
|
-
color: var(--foreground, #1d1d1d);
|
|
9477
|
-
}
|
|
9478
|
-
.q-editor-js .ce-code__textarea {
|
|
9479
|
-
background: color-mix(in srgb, var(--foreground, #1d1d1d) 6%, transparent);
|
|
9480
|
-
color: var(--foreground, #1d1d1d);
|
|
9481
|
-
border: 1px solid var(--border, rgb(0 0 0 / 0.12));
|
|
9482
|
-
border-radius: 8px;
|
|
9483
|
-
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
9484
|
-
}
|
|
9485
|
-
.q-editor-js .cdx-quote {
|
|
9486
|
-
margin: 0;
|
|
9487
|
-
padding: 10px 14px;
|
|
9488
|
-
border-left: 3px solid var(--primary, #1976d2);
|
|
9489
|
-
background: color-mix(in srgb, var(--primary, #1976d2) 8%, transparent);
|
|
9490
|
-
color: var(--foreground, #1d1d1d);
|
|
9491
|
-
border-radius: 0 8px 8px 0;
|
|
9492
|
-
}
|
|
9493
|
-
.q-editor-js .ce-toolbar__plus,
|
|
9494
|
-
.q-editor-js .ce-toolbar__settings-btn {
|
|
9495
|
-
color: var(--foreground, #1d1d1d);
|
|
9496
|
-
}
|
|
9497
|
-
.q-editor-js .ce-toolbar__plus:hover,
|
|
9498
|
-
.q-editor-js .ce-toolbar__settings-btn:hover {
|
|
9499
|
-
color: var(--primary, #1976d2);
|
|
9500
|
-
}
|
|
9501
|
-
.q-editor-js .ce-inline-toolbar,
|
|
9502
|
-
.q-editor-js .ce-conv-toolbar,
|
|
9503
|
-
.q-editor-js .ce-toolbox__button,
|
|
9504
|
-
.q-editor-js .ce-popover {
|
|
9505
|
-
background: var(--card, #ffffff);
|
|
9506
|
-
color: var(--foreground, #1d1d1d);
|
|
9507
|
-
border: 1px solid var(--border, rgb(0 0 0 / 0.12));
|
|
9508
|
-
}
|
|
9509
|
-
.q-editor-js .ce-toolbox__button--active,
|
|
9510
|
-
.q-editor-js .ce-inline-tool--active {
|
|
9511
|
-
color: var(--primary, #1976d2);
|
|
9512
|
-
}
|
|
9513
|
-
|
|
9514
9635
|
/* NodeView Vue de la table : wrapper + table + tbody (contentDOM) */
|
|
9515
9636
|
.q-tiptap__editor .ProseMirror .q-tiptap__table-wrapper {
|
|
9516
9637
|
position: relative;
|
package/components/QEditorJs.vue
DELETED
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
// QEditorJs — éditeur par blocs basé sur Editor.js (https://editorjs.io).
|
|
3
|
-
// v-model = données JSON : { time?, blocks: [{ type, data }], version? }.
|
|
4
|
-
// Tools par défaut : paragraph, header (H1-H3), list (bullet/ordered),
|
|
5
|
-
// checklist, quote, code, delimiter. Extensible via la prop `tools`.
|
|
6
|
-
// SSR-safe : l'instance Editor.js n'est créée qu'au montage (client).
|
|
7
|
-
// Note : Editor.js v2 injecte lui-même ses styles à l'init (aucun import CSS
|
|
8
|
-
// nécessaire) ; les surcharges de thème vivent dans styles/main.css.
|
|
9
|
-
import { onBeforeUnmount, onMounted, ref, watch } from "vue"
|
|
10
|
-
|
|
11
|
-
interface QEditorJsBlock {
|
|
12
|
-
type: string
|
|
13
|
-
data: Record<string, unknown>
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
interface QEditorJsData {
|
|
17
|
-
time?: number
|
|
18
|
-
blocks: QEditorJsBlock[]
|
|
19
|
-
version?: string
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
interface Props {
|
|
23
|
-
/** Données du document (v-model) — { blocks: [{ type, data }] } */
|
|
24
|
-
data?: QEditorJsData | null
|
|
25
|
-
/** Placeholder du bloc par défaut */
|
|
26
|
-
placeholder?: string
|
|
27
|
-
/** Hauteur minimale de la zone d'édition (défaut : 220px) */
|
|
28
|
-
minHeight?: string
|
|
29
|
-
/** Lecture seule */
|
|
30
|
-
readonly?: boolean
|
|
31
|
-
/** Désactivé (lecture seule + style grisé) */
|
|
32
|
-
disable?: boolean
|
|
33
|
-
/** Tools supplémentaires (défaut : paragraph, header, list, checklist, quote, code, delimiter) */
|
|
34
|
-
tools?: Record<string, any>
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const props = withDefaults(defineProps<Props>(), {
|
|
38
|
-
data: null,
|
|
39
|
-
placeholder: "Write something…",
|
|
40
|
-
minHeight: "220px",
|
|
41
|
-
readonly: false,
|
|
42
|
-
disable: false,
|
|
43
|
-
tools: () => ({}),
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
const emit = defineEmits<{
|
|
47
|
-
"update:modelValue": [data: QEditorJsData]
|
|
48
|
-
ready: [editor: unknown]
|
|
49
|
-
}>()
|
|
50
|
-
|
|
51
|
-
const holderEl = ref<HTMLElement | null>(null)
|
|
52
|
-
let editor: any = null
|
|
53
|
-
let lastJson = ""
|
|
54
|
-
let internal = false
|
|
55
|
-
|
|
56
|
-
const isEditable = () => !props.readonly && !props.disable
|
|
57
|
-
|
|
58
|
-
const emitData = async () => {
|
|
59
|
-
if (!editor) return
|
|
60
|
-
try {
|
|
61
|
-
const saved: QEditorJsData = (await editor.save()) as QEditorJsData
|
|
62
|
-
internal = true
|
|
63
|
-
lastJson = JSON.stringify(saved)
|
|
64
|
-
emit("update:modelValue", saved)
|
|
65
|
-
internal = false
|
|
66
|
-
}
|
|
67
|
-
catch {
|
|
68
|
-
/* document invalide — ignoré */
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
onMounted(async () => {
|
|
73
|
-
const mod = await import("@editorjs/editorjs")
|
|
74
|
-
const EditorJS = mod.default
|
|
75
|
-
// Les packages tools Editor.js n'embarquent pas tous des types TS
|
|
76
|
-
// @ts-ignore
|
|
77
|
-
const Header = (await import("@editorjs/header")).default
|
|
78
|
-
// @ts-ignore
|
|
79
|
-
const Paragraph = (await import("@editorjs/paragraph")).default
|
|
80
|
-
// @ts-ignore
|
|
81
|
-
const List = (await import("@editorjs/list")).default
|
|
82
|
-
// @ts-ignore
|
|
83
|
-
const Checklist = (await import("@editorjs/checklist")).default
|
|
84
|
-
// @ts-ignore
|
|
85
|
-
const Quote = (await import("@editorjs/quote")).default
|
|
86
|
-
// @ts-ignore
|
|
87
|
-
const Code = (await import("@editorjs/code")).default
|
|
88
|
-
// @ts-ignore
|
|
89
|
-
const Delimiter = (await import("@editorjs/delimiter")).default
|
|
90
|
-
|
|
91
|
-
const defaults: Record<string, any> = {
|
|
92
|
-
paragraph: { class: Paragraph },
|
|
93
|
-
header: { class: Header, inlineToolbar: true, config: { levels: [1, 2, 3] } },
|
|
94
|
-
list: { class: List, inlineToolbar: true },
|
|
95
|
-
checklist: { class: Checklist, inlineToolbar: true },
|
|
96
|
-
quote: { class: Quote, inlineToolbar: true },
|
|
97
|
-
code: { class: Code },
|
|
98
|
-
delimiter: { class: Delimiter },
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
editor = new EditorJS({
|
|
102
|
-
holder: holderEl.value as HTMLElement,
|
|
103
|
-
tools: { ...defaults, ...props.tools },
|
|
104
|
-
data: props.data ?? undefined,
|
|
105
|
-
placeholder: props.placeholder,
|
|
106
|
-
readOnly: !isEditable(),
|
|
107
|
-
onChange: () => { emitData() },
|
|
108
|
-
onReady: () => { emit("ready", editor) },
|
|
109
|
-
})
|
|
110
|
-
emit("ready", editor)
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
// Changement externe → render (sans boucle : on compare au dernier JSON émis)
|
|
114
|
-
watch(
|
|
115
|
-
() => props.data,
|
|
116
|
-
(value) => {
|
|
117
|
-
if (internal || !editor) return
|
|
118
|
-
const json = value ? JSON.stringify(value) : ""
|
|
119
|
-
if (json === lastJson) return
|
|
120
|
-
editor.render(value ?? { time: Date.now(), blocks: [], version: "2.31.6" })
|
|
121
|
-
.then(() => { lastJson = json })
|
|
122
|
-
.catch(() => { /* ignore */ })
|
|
123
|
-
},
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
onBeforeUnmount(() => {
|
|
127
|
-
if (editor && typeof editor.destroy === "function") editor.destroy()
|
|
128
|
-
editor = null
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
// — Méthodes exposées —
|
|
132
|
-
const save = () => editor?.save() as Promise<QEditorJsData> | undefined
|
|
133
|
-
const clear = () => editor?.clear()
|
|
134
|
-
const getEditor = () => editor
|
|
135
|
-
|
|
136
|
-
defineExpose({ save, clear, getEditor })
|
|
137
|
-
</script>
|
|
138
|
-
|
|
139
|
-
<template>
|
|
140
|
-
<div
|
|
141
|
-
class="q-editor-js"
|
|
142
|
-
:class="{ 'q-editor-js--readonly': readonly || disable, 'q-editor-js--disable': disable }"
|
|
143
|
-
>
|
|
144
|
-
<div ref="holderEl" class="q-editor-js__holder" :style="{ minHeight }" />
|
|
145
|
-
</div>
|
|
146
|
-
</template>
|