@prolibu-suite/cobalt-core 0.9.3 → 0.9.4
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/custom-elements.json +375 -25
- package/dist/cjs/co-audio-player.cjs.entry.js +107 -18
- package/dist/cjs/co-avatar_2.cjs.entry.js +7 -31
- package/dist/cjs/co-button.cjs.entry.js +6 -4
- package/dist/cjs/co-calendar-month_2.cjs.entry.js +59 -2
- package/dist/cjs/co-checkbox.cjs.entry.js +10 -4
- package/dist/cjs/co-chip_2.cjs.entry.js +388 -18
- package/dist/cjs/co-color-picker.cjs.entry.js +36 -3
- package/dist/cjs/co-combobox.cjs.entry.js +44 -4
- package/dist/cjs/co-event-popover.cjs.entry.js +5 -2
- package/dist/cjs/co-file-field.cjs.entry.js +11 -5
- package/dist/cjs/co-icon-button.cjs.entry.js +5 -2
- package/dist/cjs/co-icon.cjs.entry.js +17 -1
- package/dist/cjs/co-input.cjs.entry.js +21 -1
- package/dist/cjs/co-loader.cjs.entry.js +1 -1
- package/dist/cjs/co-number-field.cjs.entry.js +22 -2
- package/dist/cjs/co-ref-field.cjs.entry.js +87 -13
- package/dist/cjs/co-select.cjs.entry.js +273 -8
- package/dist/cjs/co-tags-field.cjs.entry.js +1 -1
- package/dist/cjs/cobalt.cjs.js +1 -1
- package/dist/cjs/color-YhUkm10t.js +32 -0
- package/dist/cjs/fieldChip-BLzo9MEV.js +7 -0
- package/dist/cjs/index-DK-2Lzmf.js +4 -4
- package/dist/cjs/loader.cjs.js +1 -1
- package/dist/cobalt/cobalt.esm.js +1 -1
- package/dist/cobalt/p-0c463d9a.entry.js +1 -0
- package/dist/cobalt/p-0caa059c.entry.js +1 -0
- package/dist/cobalt/{p-3ced4269.entry.js → p-2828ecf6.entry.js} +1 -1
- package/dist/cobalt/p-2f88d151.entry.js +1 -0
- package/dist/cobalt/p-409f28bb.entry.js +1 -0
- package/dist/cobalt/{p-18af9e44.entry.js → p-568cfe8d.entry.js} +1 -1
- package/dist/cobalt/p-6bf814f9.entry.js +1 -0
- package/dist/cobalt/p-7ea87b75.entry.js +1 -0
- package/dist/cobalt/p-8b4b3589.entry.js +1 -0
- package/dist/cobalt/p-93c68029.entry.js +1 -0
- package/dist/cobalt/p-BG4WRR22.js +1 -0
- package/dist/cobalt/p-a2d31248.entry.js +1 -0
- package/dist/cobalt/p-bcedbd73.entry.js +1 -0
- package/dist/cobalt/p-dfd25f1d.entry.js +1 -0
- package/dist/cobalt/p-e364b8a5.entry.js +1 -0
- package/dist/cobalt/p-f00be11f.entry.js +1 -0
- package/dist/cobalt/p-f25967fb.entry.js +1 -0
- package/dist/cobalt/p-f5f1b463.entry.js +1 -0
- package/dist/cobalt/p-fef5db54.entry.js +1 -0
- package/dist/cobalt/p-mAGnZXRn.js +1 -0
- package/dist/collection/components/co-audio-player/co-audio-player.js +81 -17
- package/dist/collection/components/co-audio-player/co-audio-player.utils.js +27 -2
- package/dist/collection/components/co-autocomplete/co-autocomplete.js +1 -1
- package/dist/collection/components/co-avatar/co-avatar.js +5 -1
- package/dist/collection/components/co-avatar-group/co-avatar-group.js +1 -1
- package/dist/collection/components/co-button/co-button.css +7 -0
- package/dist/collection/components/co-button/co-button.js +7 -5
- package/dist/collection/components/co-calendar-month/co-calendar-month.js +59 -2
- package/dist/collection/components/co-checkbox/co-checkbox.css +22 -0
- package/dist/collection/components/co-checkbox/co-checkbox.js +9 -3
- package/dist/collection/components/co-checkbox-group/co-checkbox-group.js +1 -1
- package/dist/collection/components/co-color-picker/co-color-picker.css +7 -0
- package/dist/collection/components/co-color-picker/co-color-picker.js +35 -2
- package/dist/collection/components/co-combobox/co-combobox.css +7 -0
- package/dist/collection/components/co-combobox/co-combobox.js +44 -4
- package/dist/collection/components/co-command-search/co-command-search.js +1 -1
- package/dist/collection/components/co-data-table/co-data-table.js +1 -1
- package/dist/collection/components/co-date-range-picker/co-date-range-picker.js +2 -2
- package/dist/collection/components/co-event-popover/co-event-popover.js +5 -2
- package/dist/collection/components/co-file-field/co-file-field.js +8 -5
- package/dist/collection/components/co-icon/iconLoader.js +17 -1
- package/dist/collection/components/co-icon-button/co-icon-button.css +7 -0
- package/dist/collection/components/co-icon-button/co-icon-button.js +6 -3
- package/dist/collection/components/co-input/co-input.css +13 -0
- package/dist/collection/components/co-input/co-input.js +21 -0
- package/dist/collection/components/co-loader/co-loader.css +5 -3
- package/dist/collection/components/co-number-field/co-number-field.js +23 -2
- package/dist/collection/components/co-option-list/co-option-list.css +45 -2
- package/dist/collection/components/co-option-list/co-option-list.js +468 -18
- package/dist/collection/components/co-option-list/optionListLogic.js +25 -0
- package/dist/collection/components/co-popover/co-popover.js +1 -1
- package/dist/collection/components/co-radio-group/co-radio-group.js +1 -1
- package/dist/collection/components/co-ref-field/co-ref-field.css +49 -0
- package/dist/collection/components/co-ref-field/co-ref-field.js +240 -12
- package/dist/collection/components/co-segmented-bar/co-segmented-bar.js +1 -1
- package/dist/collection/components/co-select/co-select.css +19 -0
- package/dist/collection/components/co-select/co-select.js +275 -9
- package/dist/collection/components/co-sidebar-nav/co-sidebar-nav.js +1 -1
- package/dist/collection/components/co-toast/co-toast.js +2 -2
- package/dist/collection/components/co-transfer-list/co-transfer-list.js +1 -1
- package/dist/collection/utils/fieldChip.js +1 -1
- package/dist/collection/utils/fileI18n.js +3 -0
- package/dist/components/co-accordion.js +1 -1
- package/dist/components/co-action-bar.js +1 -1
- package/dist/components/co-audio-player.js +1 -1
- package/dist/components/co-autocomplete.js +1 -1
- package/dist/components/co-avatar-group.js +1 -1
- package/dist/components/co-avatar.js +1 -1
- package/dist/components/co-breadcrumb-item.js +1 -1
- package/dist/components/co-breadcrumb.js +1 -1
- package/dist/components/co-button.js +1 -1
- package/dist/components/co-calendar-month.js +1 -1
- package/dist/components/co-calendar-timegrid.js +1 -1
- package/dist/components/co-calendar.js +1 -1
- package/dist/components/co-carousel.js +1 -1
- package/dist/components/co-checkbox-group.js +1 -1
- package/dist/components/co-checkbox.js +1 -1
- package/dist/components/co-chip.js +1 -1
- package/dist/components/co-color-picker.js +1 -1
- package/dist/components/co-column-picker.js +1 -1
- package/dist/components/co-combobox.js +1 -1
- package/dist/components/co-command-search.js +1 -1
- package/dist/components/co-data-table-pagination.js +1 -1
- package/dist/components/co-data-table.js +1 -1
- package/dist/components/co-date-field.js +1 -1
- package/dist/components/co-date-picker.js +1 -1
- package/dist/components/co-date-range-picker.js +1 -1
- package/dist/components/co-date-range.js +1 -1
- package/dist/components/co-date-time-picker.js +1 -1
- package/dist/components/co-drawer.js +1 -1
- package/dist/components/co-drive-card.js +1 -1
- package/dist/components/co-drop-zone.js +1 -1
- package/dist/components/co-emoji-picker.js +1 -1
- package/dist/components/co-empty-state.js +1 -1
- package/dist/components/co-event-calendar.js +1 -1
- package/dist/components/co-event-popover.js +1 -1
- package/dist/components/co-fab.js +1 -1
- package/dist/components/co-file-field.js +1 -1
- package/dist/components/co-file-type-tile.js +1 -1
- package/dist/components/co-helper-text.js +1 -1
- package/dist/components/co-icon-button.js +1 -1
- package/dist/components/co-icon.js +1 -1
- package/dist/components/co-input.js +1 -1
- package/dist/components/co-json-field.js +1 -1
- package/dist/components/co-lightbox.js +1 -1
- package/dist/components/co-list-footer.js +1 -1
- package/dist/components/co-list-item.js +1 -1
- package/dist/components/co-loader.js +1 -1
- package/dist/components/co-menu-item.js +1 -1
- package/dist/components/co-modal.js +1 -1
- package/dist/components/co-number-field.js +1 -1
- package/dist/components/co-option-list.js +1 -1
- package/dist/components/co-page-header.js +1 -1
- package/dist/components/co-phone-field.js +1 -1
- package/dist/components/co-radio-group.js +1 -1
- package/dist/components/co-rating.js +1 -1
- package/dist/components/co-ref-field.js +1 -1
- package/dist/components/co-rich-text-editor.js +1 -1
- package/dist/components/co-rich-text-toolbar.js +1 -1
- package/dist/components/co-select.js +1 -1
- package/dist/components/co-sidebar-nav.js +1 -1
- package/dist/components/co-slider.js +1 -1
- package/dist/components/co-switch.js +1 -1
- package/dist/components/co-tab.js +1 -1
- package/dist/components/co-tags-field.js +1 -1
- package/dist/components/co-textarea.js +1 -1
- package/dist/components/co-toast.js +1 -1
- package/dist/components/co-toggle-button.js +1 -1
- package/dist/components/co-transfer-list.js +1 -1
- package/dist/components/co-video.js +1 -1
- package/dist/components/{p-CklsvHeL.js → p-B0Dc-I_C.js} +1 -1
- package/dist/components/{p-DzoNGEDU.js → p-BH184F0X.js} +1 -1
- package/dist/components/{p-BGD3oQ73.js → p-BHYtxTzM.js} +1 -1
- package/dist/components/{p-mom5kiBa.js → p-BTQEg3UO.js} +1 -1
- package/dist/components/p-BZIB4JDs.js +1 -0
- package/dist/components/p-Bbny3WUc.js +1 -0
- package/dist/components/{p-D5QJpmqf.js → p-Bxk96Zgc.js} +1 -1
- package/dist/components/p-C1Kv3zYZ.js +1 -0
- package/dist/components/p-C4U2tbRp.js +1 -0
- package/dist/components/{p-CyMaOb1j.js → p-CA_JAK9s.js} +1 -1
- package/dist/components/{p-AvSaw4Ua.js → p-CR0izsJC.js} +1 -1
- package/dist/components/p-CuZXTvDM.js +1 -0
- package/dist/components/{p-_131DL6m.js → p-D4qzGifz.js} +1 -1
- package/dist/components/{p-BsQQ7Mki.js → p-D5EdMieb.js} +1 -1
- package/dist/components/p-Dabi98BI.js +1 -0
- package/dist/components/p-DbipMAyT.js +1 -0
- package/dist/components/p-DgUBcJIS.js +1 -0
- package/dist/components/p-Dj1EZ5qD.js +1 -0
- package/dist/components/{p-DkmKOF60.js → p-P8wvwhM2.js} +1 -1
- package/dist/components/{p-nwy6X1QT.js → p-hfGFsvaD.js} +1 -1
- package/dist/esm/co-audio-player.entry.js +107 -18
- package/dist/esm/co-avatar_2.entry.js +6 -30
- package/dist/esm/co-button.entry.js +6 -4
- package/dist/esm/co-calendar-month_2.entry.js +59 -2
- package/dist/esm/co-checkbox.entry.js +10 -4
- package/dist/esm/co-chip_2.entry.js +388 -18
- package/dist/esm/co-color-picker.entry.js +36 -3
- package/dist/esm/co-combobox.entry.js +44 -4
- package/dist/esm/co-event-popover.entry.js +5 -2
- package/dist/esm/co-file-field.entry.js +11 -5
- package/dist/esm/co-icon-button.entry.js +5 -2
- package/dist/esm/co-icon.entry.js +17 -1
- package/dist/esm/co-input.entry.js +21 -1
- package/dist/esm/co-loader.entry.js +1 -1
- package/dist/esm/co-number-field.entry.js +22 -2
- package/dist/esm/co-ref-field.entry.js +87 -13
- package/dist/esm/co-select.entry.js +273 -8
- package/dist/esm/co-tags-field.entry.js +1 -1
- package/dist/esm/cobalt.js +1 -1
- package/dist/esm/color-BG4WRR22.js +30 -0
- package/dist/esm/fieldChip-DsAfq1e0.js +5 -0
- package/dist/esm/index-CrtiywTB.js +4 -4
- package/dist/esm/loader.js +1 -1
- package/dist/types/components/co-audio-player/co-audio-player.d.ts +13 -0
- package/dist/types/components/co-audio-player/co-audio-player.utils.d.ts +18 -1
- package/dist/types/components/co-button/co-button.d.ts +3 -1
- package/dist/types/components/co-calendar-month/co-calendar-month.d.ts +7 -0
- package/dist/types/components/co-checkbox/co-checkbox.d.ts +1 -0
- package/dist/types/components/co-color-picker/co-color-picker.d.ts +15 -0
- package/dist/types/components/co-combobox/co-combobox.d.ts +17 -0
- package/dist/types/components/co-icon-button/co-icon-button.d.ts +3 -0
- package/dist/types/components/co-input/co-input.d.ts +9 -0
- package/dist/types/components/co-number-field/co-number-field.d.ts +9 -0
- package/dist/types/components/co-option-list/co-option-list.d.ts +79 -0
- package/dist/types/components/co-option-list/optionListLogic.d.ts +23 -1
- package/dist/types/components/co-ref-field/co-ref-field.d.ts +46 -0
- package/dist/types/components/co-select/co-select.d.ts +67 -0
- package/dist/types/components.d.ts +110 -4
- package/dist/types/utils/fieldChip.d.ts +5 -0
- package/dist/types/utils/fileI18n.d.ts +1 -0
- package/package.json +3 -3
- package/dist/cjs/fieldChip-xNJPEFcS.js +0 -7
- package/dist/cobalt/p-02ac492f.entry.js +0 -1
- package/dist/cobalt/p-0ccb672b.entry.js +0 -1
- package/dist/cobalt/p-2d81b292.entry.js +0 -1
- package/dist/cobalt/p-4a503187.entry.js +0 -1
- package/dist/cobalt/p-55566b37.entry.js +0 -1
- package/dist/cobalt/p-680d3db8.entry.js +0 -1
- package/dist/cobalt/p-7457aa89.entry.js +0 -1
- package/dist/cobalt/p-77719335.entry.js +0 -1
- package/dist/cobalt/p-8f600512.entry.js +0 -1
- package/dist/cobalt/p-CQAYgqA6.js +0 -1
- package/dist/cobalt/p-a02db338.entry.js +0 -1
- package/dist/cobalt/p-a51d8775.entry.js +0 -1
- package/dist/cobalt/p-aafb9d11.entry.js +0 -1
- package/dist/cobalt/p-b28dc0fe.entry.js +0 -1
- package/dist/cobalt/p-c6b9b9d6.entry.js +0 -1
- package/dist/cobalt/p-d4debd6f.entry.js +0 -1
- package/dist/cobalt/p-d629fb4e.entry.js +0 -1
- package/dist/components/p-B3TkpLNh.js +0 -1
- package/dist/components/p-B7hvddnu.js +0 -1
- package/dist/components/p-BT5tSgZa.js +0 -1
- package/dist/components/p-CXLX8DpD.js +0 -1
- package/dist/components/p-DTvb412h.js +0 -1
- package/dist/components/p-DvhR3H61.js +0 -1
- package/dist/components/p-KWTokD0C.js +0 -1
- package/dist/components/p-Q6caHt79.js +0 -1
- package/dist/components/p-WWlZiln1.js +0 -1
- package/dist/esm/fieldChip-BZDUy9Z-.js +0 -5
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { h } from "@stencil/core";
|
|
2
|
-
import { formatTime, resamplePeaks, fallbackPeaks, scrubFraction } from "./co-audio-player.utils";
|
|
2
|
+
import { formatTime, resamplePeaks, fallbackPeaks, scrubFraction, resolveDuration } from "./co-audio-player.utils";
|
|
3
3
|
export class CoAudioPlayer {
|
|
4
4
|
constructor() {
|
|
5
5
|
/** Compact variant: narrower, hides the download button. */
|
|
@@ -21,7 +21,10 @@ export class CoAudioPlayer {
|
|
|
21
21
|
this.isLoading = false;
|
|
22
22
|
this.hasError = false;
|
|
23
23
|
this.currentTime = 0;
|
|
24
|
-
|
|
24
|
+
/** Playback length in seconds, or `NaN` while it is not known — which is the
|
|
25
|
+
* normal state for a `MediaRecorder` blob until the decode lands. Everything
|
|
26
|
+
* that does arithmetic with it goes through `knownDuration`. */
|
|
27
|
+
this.duration = NaN;
|
|
25
28
|
this.peaks = [];
|
|
26
29
|
/** Measured wave width (px); 0 until the ResizeObserver reports it. */
|
|
27
30
|
this.containerWidth = 0;
|
|
@@ -29,7 +32,13 @@ export class CoAudioPlayer {
|
|
|
29
32
|
this.playbackRate = 1;
|
|
30
33
|
// Decoded mono PCM, kept so bars can be re-resampled on resize without re-fetching.
|
|
31
34
|
this.rawChannelData = null;
|
|
32
|
-
|
|
35
|
+
// Length reported by the WebAudio decode; NaN when nothing has decoded (yet).
|
|
36
|
+
// It is the only trustworthy duration for a container without a duration header.
|
|
37
|
+
this.decodedDuration = NaN;
|
|
38
|
+
// Bumped by every init(); an in-flight extractPeaks whose token no longer matches
|
|
39
|
+
// belongs to a superseded `src` and must not write peaks/duration.
|
|
40
|
+
this.loadToken = 0;
|
|
41
|
+
this.onMeta = () => { this.syncDuration(); };
|
|
33
42
|
this.onCanPlay = () => { this.isLoading = false; };
|
|
34
43
|
this.onWaiting = () => { this.isLoading = true; };
|
|
35
44
|
this.onTime = () => { var _a; this.currentTime = ((_a = this.audio) === null || _a === void 0 ? void 0 : _a.currentTime) || 0; };
|
|
@@ -56,11 +65,15 @@ export class CoAudioPlayer {
|
|
|
56
65
|
if (this.audio)
|
|
57
66
|
this.audio.playbackRate = this.playbackRate;
|
|
58
67
|
};
|
|
68
|
+
// Scrubbing needs a real total to map the click fraction onto: with an unknown
|
|
69
|
+
// duration `frac * duration` is Infinity (or NaN at frac 0), and assigning that
|
|
70
|
+
// to `currentTime` throws. No total → no scrub.
|
|
59
71
|
this.seekAt = (clientX) => {
|
|
60
|
-
|
|
72
|
+
const total = this.knownDuration;
|
|
73
|
+
if (!this.audio || !this.waveEl || !total)
|
|
61
74
|
return;
|
|
62
75
|
const frac = scrubFraction(clientX, this.waveEl.getBoundingClientRect());
|
|
63
|
-
this.audio.currentTime = frac *
|
|
76
|
+
this.audio.currentTime = frac * total;
|
|
64
77
|
this.currentTime = this.audio.currentTime;
|
|
65
78
|
};
|
|
66
79
|
this.onWaveKey = (e) => {
|
|
@@ -69,17 +82,29 @@ export class CoAudioPlayer {
|
|
|
69
82
|
this.toggle();
|
|
70
83
|
return;
|
|
71
84
|
}
|
|
72
|
-
if (!this.audio
|
|
85
|
+
if (!this.audio)
|
|
73
86
|
return;
|
|
87
|
+
// Relative nudges stay available even with an unknown duration (the media
|
|
88
|
+
// element clamps a forward seek to its own seekable end); only the upper
|
|
89
|
+
// clamp needs a total, and both operands stay finite either way.
|
|
90
|
+
const total = this.knownDuration;
|
|
91
|
+
const at = this.audio.currentTime;
|
|
74
92
|
if (e.key === 'ArrowRight')
|
|
75
|
-
this.audio.currentTime = Math.min(
|
|
93
|
+
this.audio.currentTime = total ? Math.min(total, at + 5) : at + 5;
|
|
76
94
|
else if (e.key === 'ArrowLeft')
|
|
77
|
-
this.audio.currentTime = Math.max(0,
|
|
95
|
+
this.audio.currentTime = Math.max(0, at - 5);
|
|
78
96
|
else
|
|
79
97
|
return;
|
|
80
98
|
this.currentTime = this.audio.currentTime;
|
|
81
99
|
};
|
|
82
100
|
}
|
|
101
|
+
/** Duration usable for arithmetic (seek target, progress %, ARIA): `0` when it
|
|
102
|
+
* is not known, so every caller can bail on a plain falsy check instead of
|
|
103
|
+
* letting `Infinity`/`NaN` reach `audio.currentTime` — that setter takes a
|
|
104
|
+
* restricted `double` and THROWS a TypeError on a non-finite value. */
|
|
105
|
+
get knownDuration() {
|
|
106
|
+
return Number.isFinite(this.duration) && this.duration > 0 ? this.duration : 0;
|
|
107
|
+
}
|
|
83
108
|
/** Bar count: derived from the measured width (V1-style density) once known,
|
|
84
109
|
* else the `bars` fallback. Floored at 24 so a narrow player still reads as a wave. */
|
|
85
110
|
get effectiveBars() {
|
|
@@ -117,6 +142,7 @@ export class CoAudioPlayer {
|
|
|
117
142
|
}
|
|
118
143
|
disconnectedCallback() {
|
|
119
144
|
var _a;
|
|
145
|
+
this.loadToken++; // orphan any in-flight decode so it can't write to a dead instance
|
|
120
146
|
this.teardown();
|
|
121
147
|
(_a = this.resizeObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
|
|
122
148
|
this.resizeObserver = undefined;
|
|
@@ -131,16 +157,18 @@ export class CoAudioPlayer {
|
|
|
131
157
|
}
|
|
132
158
|
init() {
|
|
133
159
|
this.teardown();
|
|
160
|
+
const token = ++this.loadToken;
|
|
134
161
|
this.rawChannelData = null;
|
|
162
|
+
this.decodedDuration = NaN;
|
|
135
163
|
this.peaks = fallbackPeaks(this.effectiveBars); // draw immediately; refine with real peaks
|
|
136
164
|
this.currentTime = 0;
|
|
137
|
-
this.duration =
|
|
165
|
+
this.duration = NaN; // unknown until `loadedmetadata` or the decode says otherwise
|
|
138
166
|
this.playbackRate = 1; // a fresh source starts at 1x (new <audio> defaults to 1)
|
|
139
167
|
this.hasError = false;
|
|
140
168
|
if (!this.src)
|
|
141
169
|
return;
|
|
142
170
|
this.isLoading = true;
|
|
143
|
-
this.extractPeaks(this.src);
|
|
171
|
+
this.extractPeaks(this.src, token);
|
|
144
172
|
if (typeof Audio === 'undefined')
|
|
145
173
|
return; // test/SSR guard
|
|
146
174
|
const el = new Audio();
|
|
@@ -169,22 +197,52 @@ export class CoAudioPlayer {
|
|
|
169
197
|
}
|
|
170
198
|
this.isPlaying = false;
|
|
171
199
|
}
|
|
172
|
-
async extractPeaks(url) {
|
|
200
|
+
async extractPeaks(url, token) {
|
|
201
|
+
let ctx;
|
|
173
202
|
try {
|
|
174
203
|
const Ctx = window.AudioContext || window.webkitAudioContext;
|
|
175
204
|
if (!Ctx || typeof fetch === 'undefined')
|
|
176
205
|
return; // keep fallback
|
|
177
|
-
|
|
206
|
+
ctx = new Ctx();
|
|
178
207
|
const buf = await (await fetch(url)).arrayBuffer();
|
|
179
208
|
const audioBuf = await ctx.decodeAudioData(buf);
|
|
209
|
+
if (token !== this.loadToken)
|
|
210
|
+
return; // a newer src superseded this decode
|
|
180
211
|
this.rawChannelData = audioBuf.getChannelData(0);
|
|
212
|
+
this.decodedDuration = audioBuf.duration;
|
|
181
213
|
this.peaks = resamplePeaks(this.rawChannelData, this.effectiveBars);
|
|
182
|
-
ctx.close();
|
|
183
214
|
}
|
|
184
215
|
catch (_a) {
|
|
216
|
+
if (token !== this.loadToken)
|
|
217
|
+
return;
|
|
218
|
+
// Undecodable (codec WebAudio can't handle, CORS, network): keep the
|
|
219
|
+
// deterministic fallback wave and let <audio> own the duration — playback,
|
|
220
|
+
// seek and the time readout all still work, or degrade to `--:--`.
|
|
185
221
|
this.rawChannelData = null;
|
|
222
|
+
this.decodedDuration = NaN;
|
|
186
223
|
this.peaks = fallbackPeaks(this.effectiveBars); // stays functional
|
|
187
224
|
}
|
|
225
|
+
finally {
|
|
226
|
+
// Always release the context. Chrome caps concurrent AudioContexts per
|
|
227
|
+
// document, so leaking one per failed decode would eventually stop every
|
|
228
|
+
// later player on the page from decoding at all.
|
|
229
|
+
try {
|
|
230
|
+
await (ctx === null || ctx === void 0 ? void 0 : ctx.close());
|
|
231
|
+
}
|
|
232
|
+
catch ( /* already closed */_b) { /* already closed */ }
|
|
233
|
+
}
|
|
234
|
+
if (token === this.loadToken)
|
|
235
|
+
this.syncDuration();
|
|
236
|
+
}
|
|
237
|
+
/** Re-derive the shown duration from whichever source knows it. Called from both
|
|
238
|
+
* `loadedmetadata` and the end of the decode, because either can land first. */
|
|
239
|
+
syncDuration() {
|
|
240
|
+
var _a, _b;
|
|
241
|
+
const next = resolveDuration((_b = (_a = this.audio) === null || _a === void 0 ? void 0 : _a.duration) !== null && _b !== void 0 ? _b : NaN, this.decodedDuration);
|
|
242
|
+
// Object.is, not !==: Stencil's @State setter re-renders on any `oldVal !== newVal`,
|
|
243
|
+
// and NaN !== NaN — so a plain assignment would re-render on every no-op sync.
|
|
244
|
+
if (!Object.is(next, this.duration))
|
|
245
|
+
this.duration = next;
|
|
188
246
|
}
|
|
189
247
|
async download() {
|
|
190
248
|
if (!this.src || typeof fetch === 'undefined')
|
|
@@ -205,14 +263,20 @@ export class CoAudioPlayer {
|
|
|
205
263
|
render() {
|
|
206
264
|
const H = 40, UNIT = 3, W = 1.8, R = 0.6;
|
|
207
265
|
const vbW = Math.max(1, this.peaks.length * UNIT);
|
|
208
|
-
const
|
|
209
|
-
const
|
|
210
|
-
|
|
266
|
+
const total = this.knownDuration; // 0 while unknown → no progress fill, no ARIA max
|
|
267
|
+
const pct = total ? (this.currentTime / total) * 100 : 0;
|
|
268
|
+
// Undefined drops the attribute (Stencil removes null/undefined attrs). An
|
|
269
|
+
// `aria-valuemax="Infinity"`/`"NaN"` is invalid, and a stand-in `"0"` would sit
|
|
270
|
+
// below `aria-valuenow` the moment playback advances. ARIA 1.2 makes valuemax
|
|
271
|
+
// optional for `slider`, so leaving it off while unknown is well-formed.
|
|
272
|
+
const ariaMax = total ? Math.floor(total) : undefined;
|
|
273
|
+
const bar = (hFrac, i, played = false) => (h("rect", { key: '997dcefc32f5188c41afa0a7b5135c70cceeed1d', x: i * UNIT, y: H / 2 - (hFrac * H) / 2, width: W, height: Math.max(hFrac * H, 2), rx: R, class: { 'co-audio-player__bar': true, 'co-audio-player__bar--played': played } }));
|
|
274
|
+
return (h("div", { key: '8ac91ab3f8eb417df5e7cf4d1e016d6ca7eb45a3', class: {
|
|
211
275
|
'co-audio-player': true,
|
|
212
276
|
'co-audio-player--compact': this.compact,
|
|
213
277
|
'co-audio-player--error': this.hasError,
|
|
214
278
|
'co-audio-player--playing': this.isPlaying,
|
|
215
|
-
}, part: "player" }, h("button", { key: '
|
|
279
|
+
}, part: "player" }, h("button", { key: '0e5897dc64c91ee67432f17d7b0d57a27f44cd0e', type: "button", class: "co-audio-player__play", part: "play", "aria-label": this.hasError ? 'Audio no disponible' : this.isPlaying ? 'Pausar' : 'Reproducir', "aria-busy": this.isLoading ? 'true' : 'false', disabled: this.hasError || !this.src, onClick: this.toggle }, h("co-icon", { key: '4843751a2a3709942a49ae03ff9ba0eb08ab7d97', name: this.hasError ? 'warning-circle' : this.isPlaying ? 'pause' : 'play', weight: "fill", size: this.compact ? 12 : 14 })), h("div", { key: 'b7aebd0e0c5197b0dec6257bbec7c9dd8dd55d6f', class: "co-audio-player__wave", part: "wave", role: "slider", tabindex: "0", "aria-label": "Progreso de reproducci\u00F3n", "aria-valuemin": 0, "aria-valuemax": ariaMax, "aria-valuenow": Math.floor(this.currentTime), ref: (el) => (this.waveEl = el), onClick: (e) => this.seekAt(e.clientX), onKeyDown: this.onWaveKey }, h("svg", { key: 'a504d058a27bb33dd57733ec32db306eb0ed87e9', class: "co-audio-player__svg", viewBox: `0 0 ${vbW} ${H}`, preserveAspectRatio: "none", "aria-hidden": "true" }, this.peaks.map((hFrac, i) => bar(hFrac, i)), h("clipPath", { key: '9cfa69ba28801893baf62c8f66b18a31319fdebe', id: "co-ap-clip" }, h("rect", { key: '6e5cb34baeb2b7dddd6ef7e94f8585ef912e0315', x: "0", y: "0", width: (pct / 100) * vbW, height: H })), h("g", { key: '4b78a29dde762cabc85d7921aacbcd261d8fa6b5', "clip-path": "url(#co-ap-clip)" }, this.peaks.map((hFrac, i) => bar(hFrac, i, true))))), h("span", { key: '9067d22b0e2994e46ef49c60356292ec59f61a21', class: "co-audio-player__time", part: "time" }, formatTime(this.currentTime), " / ", formatTime(this.duration)), !this.compact && (h("button", { key: 'f6149a9996e6ce15230221c2f6fcec95fec5f485', type: "button", class: "co-audio-player__speed", part: "speed", "aria-label": `Velocidad de reproducción ${this.playbackRate}x`, onClick: this.cycleSpeed }, this.playbackRate, "x")), !this.compact && !this.noDownload && (h("button", { key: '73d291d31d92bcb0e18e230fe0ac35d3ab4077b2', type: "button", class: "co-audio-player__dl", part: "download", "aria-label": "Descargar", onClick: () => this.download() }, h("co-icon", { key: '9ab09f5d6b975f4f5544019f70bd2ca1ee521068', name: "download-simple", size: 14 })))));
|
|
216
280
|
}
|
|
217
281
|
static get is() { return "co-audio-player"; }
|
|
218
282
|
static get encapsulation() { return "shadow"; }
|
|
@@ -1,10 +1,35 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** Rendered in place of `m:ss` while a time is not known.
|
|
2
|
+
*
|
|
3
|
+
* Not an error state: `<audio>.duration` is `Infinity` for any container whose
|
|
4
|
+
* header carries no duration — every blob `MediaRecorder` produces, i.e. every
|
|
5
|
+
* freshly recorded voice note — and Chrome keeps it that way until the clip has
|
|
6
|
+
* been played end to end. `0:00` there would read as a zero-length recording;
|
|
7
|
+
* `--:--` reads as "not known yet", which is the truth. */
|
|
8
|
+
export const UNKNOWN_TIME = '--:--';
|
|
9
|
+
/** Seconds → "m:ss" (floors). Anything that is not a finite, non-negative number
|
|
10
|
+
* (`Infinity`, `NaN`, negatives) → `UNKNOWN_TIME` — never `"Infinity:NaN"`. */
|
|
2
11
|
export function formatTime(seconds) {
|
|
3
|
-
|
|
12
|
+
if (!Number.isFinite(seconds) || seconds < 0)
|
|
13
|
+
return UNKNOWN_TIME;
|
|
14
|
+
const s = Math.floor(seconds);
|
|
4
15
|
const m = Math.floor(s / 60);
|
|
5
16
|
const sec = s % 60;
|
|
6
17
|
return `${m}:${sec.toString().padStart(2, '0')}`;
|
|
7
18
|
}
|
|
19
|
+
/** Picks the duration to trust (seconds), or `NaN` when neither source knows it.
|
|
20
|
+
*
|
|
21
|
+
* `<audio>.duration` is authoritative *when it is a real number*. It is not for
|
|
22
|
+
* a duration-less container (see `UNKNOWN_TIME`), where it reports `Infinity`.
|
|
23
|
+
* The WebAudio decode the player already runs to draw the waveform does know the
|
|
24
|
+
* real length (`AudioBuffer.duration`), so it is the fallback. `0` from either
|
|
25
|
+
* source means "empty/unknown" too, so it never wins. */
|
|
26
|
+
export function resolveDuration(fromAudio, fromDecode) {
|
|
27
|
+
if (Number.isFinite(fromAudio) && fromAudio > 0)
|
|
28
|
+
return fromAudio;
|
|
29
|
+
if (Number.isFinite(fromDecode) && fromDecode > 0)
|
|
30
|
+
return fromDecode;
|
|
31
|
+
return NaN;
|
|
32
|
+
}
|
|
8
33
|
/** Downsample raw PCM to `bars` peak heights, each normalized to a 0..1 fraction. */
|
|
9
34
|
export function resamplePeaks(channelData, bars) {
|
|
10
35
|
const len = channelData.length;
|
|
@@ -418,7 +418,7 @@ export class CoAutocomplete {
|
|
|
418
418
|
"references": {
|
|
419
419
|
"AutocompleteOption": {
|
|
420
420
|
"location": "local",
|
|
421
|
-
"path": "/Users/agox/
|
|
421
|
+
"path": "/Users/agox/Documents/Prolibu/prolibu-cobalt/packages/core/src/components/co-autocomplete/co-autocomplete.tsx",
|
|
422
422
|
"id": "src/components/co-autocomplete/co-autocomplete.tsx::AutocompleteOption"
|
|
423
423
|
}
|
|
424
424
|
}
|
|
@@ -37,6 +37,10 @@ export class CoAvatar {
|
|
|
37
37
|
const iconSize = (_b = ICON_SIZE_MAP[this.size]) !== null && _b !== void 0 ? _b : 24;
|
|
38
38
|
const fontSize = (_c = FONT_SIZE_MAP[this.size]) !== null && _c !== void 0 ? _c : 'var(--co-font-size-18)';
|
|
39
39
|
const showImage = this.type === 'image' && this.src && !this.imgFailed;
|
|
40
|
+
// Broken/missing image without initials falls back to the ICON (default
|
|
41
|
+
// "user") — never an empty disc. With `text` present, initials win (they
|
|
42
|
+
// identify the record better than a generic glyph).
|
|
43
|
+
const showIcon = this.type === 'icon' || (this.type === 'image' && !showImage && !this.text);
|
|
40
44
|
const ariaLabel = this.type === 'text'
|
|
41
45
|
? this.text
|
|
42
46
|
: this.type === 'image'
|
|
@@ -50,7 +54,7 @@ export class CoAvatar {
|
|
|
50
54
|
'co-avatar--light': !!this.color && isLightColor(this.color),
|
|
51
55
|
}, style: Object.assign(Object.assign({ width: `var(--co-avatar-size, ${dim}px)`, height: `var(--co-avatar-size, ${dim}px)`, fontSize }, (this.color ? { background: this.color } : {})), (this.ringColor
|
|
52
56
|
? { '--co-avatar-border-color': this.ringColor, '--co-avatar-border-width': '2px' }
|
|
53
|
-
: {})), part: "avatar", role: "img", "aria-label": ariaLabel }, showImage ? (h("img", { class: "co-avatar__img", src: this.src, alt: (_g = (_f = this.alt) !== null && _f !== void 0 ? _f : this.text) !== null && _g !== void 0 ? _g : '', onError: this.handleImgError })) :
|
|
57
|
+
: {})), part: "avatar", role: "img", "aria-label": ariaLabel }, showImage ? (h("img", { class: "co-avatar__img", src: this.src, alt: (_g = (_f = this.alt) !== null && _f !== void 0 ? _f : this.text) !== null && _g !== void 0 ? _g : '', onError: this.handleImgError })) : showIcon ? (h("co-icon", { name: this.icon, weight: this.iconWeight, size: iconSize })) : (h("span", { class: "co-avatar__text" }, this.text))));
|
|
54
58
|
// No tooltip when `name` is unset/empty — renders exactly as before, no wrapper.
|
|
55
59
|
if (!this.name)
|
|
56
60
|
return disc;
|
|
@@ -87,7 +87,7 @@ export class CoAvatarGroup {
|
|
|
87
87
|
"references": {
|
|
88
88
|
"CoAvatarGroupItem": {
|
|
89
89
|
"location": "local",
|
|
90
|
-
"path": "/Users/agox/
|
|
90
|
+
"path": "/Users/agox/Documents/Prolibu/prolibu-cobalt/packages/core/src/components/co-avatar-group/co-avatar-group.tsx",
|
|
91
91
|
"id": "src/components/co-avatar-group/co-avatar-group.tsx::CoAvatarGroupItem"
|
|
92
92
|
}
|
|
93
93
|
}
|
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
display: inline-flex;
|
|
3
3
|
}
|
|
4
4
|
|
|
5
|
+
/* Disabled must block clicks AT THE HOST: the inner button's
|
|
6
|
+
pointer-events:none re-targets clicks to the host element, where consumer
|
|
7
|
+
listeners (Vue @click) live — without this, "disabled" buttons still fire. */
|
|
8
|
+
:host([disabled]) {
|
|
9
|
+
pointer-events: none;
|
|
10
|
+
}
|
|
11
|
+
|
|
5
12
|
.co-button {
|
|
6
13
|
display: inline-flex;
|
|
7
14
|
align-items: center;
|
|
@@ -22,7 +22,9 @@ export class CoButton {
|
|
|
22
22
|
* `variant="danger" surface="dark"`.
|
|
23
23
|
*/
|
|
24
24
|
this.surface = 'light';
|
|
25
|
-
/** Disabled state
|
|
25
|
+
/** Disabled state. Reflected so :host([disabled]) can kill pointer-events —
|
|
26
|
+
* the inner button's own pointer-events:none re-targets clicks to the host,
|
|
27
|
+
* where consumers' click listeners live. */
|
|
26
28
|
this.disabled = false;
|
|
27
29
|
/** Icon weight applied to both iconLeft and iconRight */
|
|
28
30
|
this.iconWeight = 'regular';
|
|
@@ -53,7 +55,7 @@ export class CoButton {
|
|
|
53
55
|
customStyle.background = this.color;
|
|
54
56
|
if (this.textColor)
|
|
55
57
|
customStyle.color = this.textColor;
|
|
56
|
-
return (h("button", { key: '
|
|
58
|
+
return (h("button", { key: '651f8d079c09cf21d076aa20564a462a15ca8c4a', class: {
|
|
57
59
|
'co-button': true,
|
|
58
60
|
[`co-button--${this.variant}`]: true,
|
|
59
61
|
[`co-button--${this.shape}`]: true,
|
|
@@ -63,7 +65,7 @@ export class CoButton {
|
|
|
63
65
|
'co-button--disabled': this.disabled,
|
|
64
66
|
}, style: Object.keys(customStyle).length ? customStyle : undefined, type: this.type, disabled: this.disabled, "aria-label": this.ariaLabelText || undefined, onClick: this.handleClick, part: "button" }, this.iconLeft
|
|
65
67
|
? h("co-icon", { name: this.iconLeft, weight: this.iconWeight, size: iconSize })
|
|
66
|
-
: null, h("slot", { key: '
|
|
68
|
+
: null, h("slot", { key: 'b8c5b230a473fe4a57aa087533186e410db64bed' }, this.label), this.iconRight
|
|
67
69
|
? h("co-icon", { name: this.iconRight, weight: this.iconWeight, size: iconSize })
|
|
68
70
|
: null));
|
|
69
71
|
}
|
|
@@ -193,11 +195,11 @@ export class CoButton {
|
|
|
193
195
|
"optional": false,
|
|
194
196
|
"docs": {
|
|
195
197
|
"tags": [],
|
|
196
|
-
"text": "Disabled state"
|
|
198
|
+
"text": "Disabled state. Reflected so :host([disabled]) can kill pointer-events \u2014\nthe inner button's own pointer-events:none re-targets clicks to the host,\nwhere consumers' click listeners live."
|
|
197
199
|
},
|
|
198
200
|
"getter": false,
|
|
199
201
|
"setter": false,
|
|
200
|
-
"reflect":
|
|
202
|
+
"reflect": true,
|
|
201
203
|
"attribute": "disabled",
|
|
202
204
|
"defaultValue": "false"
|
|
203
205
|
},
|
|
@@ -2,6 +2,11 @@ import { h, } from "@stencil/core";
|
|
|
2
2
|
import { addDays, compareYMD, getMonthMatrix, getWeekdayLabels, isDisabled, parseISO, todayYMD, } from "@prolibu-suite/cobalt-date-utils";
|
|
3
3
|
import { layoutMonth, getWallClock, wallClockToEpoch, toUtcISO, zonedDateKey, zonedMinutesOfDay, } from "@prolibu-suite/cobalt-calendar-utils";
|
|
4
4
|
import { readableTextToken } from "../../utils/eventColor";
|
|
5
|
+
// Distancia (px) que el puntero debe recorrer tras el pointerdown para que un drag
|
|
6
|
+
// de evento se comprometa. Por debajo de esto el gesto es un CLICK: el drag no
|
|
7
|
+
// arranca, el chip conserva sus pointer-events y su click abre el popover. Mismo
|
|
8
|
+
// literal/mecanismo que co-calendar-timegrid.tsx (DRAG_THRESHOLD_PX ahí, línea ~28).
|
|
9
|
+
const DRAG_THRESHOLD_PX = 5;
|
|
5
10
|
/**
|
|
6
11
|
* Month grid for the event calendar. Forks the day grid, roving-tabindex and
|
|
7
12
|
* keyboard model from `co-calendar`, then overlays multi-day event bars laid out
|
|
@@ -38,6 +43,21 @@ export class CoCalendarMonth {
|
|
|
38
43
|
/** Epoch ms of "now", refreshed on a 60s timer so `past` styling stays live. */
|
|
39
44
|
this.nowMs = 0;
|
|
40
45
|
this.onPointerUp = () => this.finishDrag();
|
|
46
|
+
this.onPointerMove = (e) => this.trackDrag(e);
|
|
47
|
+
// Drag ARMADO pero aún no comprometido: `startDrag` guarda el id y el punto de
|
|
48
|
+
// agarre acá; el drag pasa a activo (`dragId`) recién cuando el puntero cruza
|
|
49
|
+
// DRAG_THRESHOLD_PX en `trackDrag` (mismo patrón que co-calendar-timegrid.tsx,
|
|
50
|
+
// `dragArmedId`/`startMove`). Así un click simple en un evento arrastrable no
|
|
51
|
+
// arranca drag (que pone `pointer-events:none` en los chips vía
|
|
52
|
+
// `cal-month--interacting`) y su popover abre normal.
|
|
53
|
+
this.dragArmedId = null;
|
|
54
|
+
this.dragStartPointer = null;
|
|
55
|
+
// Set right before coEventDrop so the trailing click no-ops (mismo patrón que
|
|
56
|
+
// co-calendar-timegrid.tsx#suppressNextClick): un drag COMPROMETIDO deja el
|
|
57
|
+
// chip con pointer-events restaurados (`setInteracting(false)`) ANTES de que
|
|
58
|
+
// el navegador dispare el `click` sintético que sigue al `pointerup` — sin
|
|
59
|
+
// este flag ese click reabriría el popover justo después de soltar.
|
|
60
|
+
this.suppressNextClick = false;
|
|
41
61
|
// ── Selection / activation ──────────────────────────────────────────────
|
|
42
62
|
/** Día seleccionado que PERSISTE resaltado mientras el host tiene abierto su
|
|
43
63
|
* form/bubble de creación (referencia visual, paridad Google). */
|
|
@@ -82,9 +102,11 @@ export class CoCalendarMonth {
|
|
|
82
102
|
}
|
|
83
103
|
connectedCallback() {
|
|
84
104
|
window.addEventListener('pointerup', this.onPointerUp);
|
|
105
|
+
window.addEventListener('pointermove', this.onPointerMove);
|
|
85
106
|
}
|
|
86
107
|
disconnectedCallback() {
|
|
87
108
|
window.removeEventListener('pointerup', this.onPointerUp);
|
|
109
|
+
window.removeEventListener('pointermove', this.onPointerMove);
|
|
88
110
|
if (this.nowTimer)
|
|
89
111
|
window.clearInterval(this.nowTimer);
|
|
90
112
|
}
|
|
@@ -141,16 +163,43 @@ export class CoCalendarMonth {
|
|
|
141
163
|
return;
|
|
142
164
|
// Ver la nota en co-calendar-timegrid#startMove: preventDefault en
|
|
143
165
|
// pointerdown mata el click y el chip arrastrable no podía activarse.
|
|
166
|
+
// Tampoco se toca dragId/interacting acá: eso arrancaba
|
|
167
|
+
// `cal-month--interacting` (pointer-events:none en los chips, css:249) en el
|
|
168
|
+
// pointerdown mismo y se comía el click de todo evento FUTURO arrastrable —
|
|
169
|
+
// el chip nunca veía el click nativo. Se arma un candidato nada más; el
|
|
170
|
+
// commit real ocurre en `trackDrag` al cruzar DRAG_THRESHOLD_PX.
|
|
144
171
|
e.stopPropagation();
|
|
145
|
-
this.
|
|
172
|
+
this.dragArmedId = id;
|
|
173
|
+
this.dragStartPointer = { x: e.clientX, y: e.clientY };
|
|
174
|
+
}
|
|
175
|
+
/** Arma → activa el drag al cruzar el umbral (ver DRAG_THRESHOLD_PX). Antes de
|
|
176
|
+
* eso no toca dragId/interacting, así el click nativo del chip sigue vivo. */
|
|
177
|
+
trackDrag(e) {
|
|
178
|
+
if (!this.dragArmedId || this.dragId)
|
|
179
|
+
return;
|
|
180
|
+
const sp = this.dragStartPointer;
|
|
181
|
+
if (!sp)
|
|
182
|
+
return;
|
|
183
|
+
if (Math.hypot(e.clientX - sp.x, e.clientY - sp.y) < DRAG_THRESHOLD_PX)
|
|
184
|
+
return;
|
|
185
|
+
this.dragId = this.dragArmedId;
|
|
186
|
+
this.dragArmedId = null;
|
|
146
187
|
this.dragOverDate = null;
|
|
147
188
|
this.setInteracting(true);
|
|
148
189
|
}
|
|
149
190
|
finishDrag() {
|
|
191
|
+
// pointerup habiendo solo ARMADO (sin cruzar el umbral) = fue un click, no un
|
|
192
|
+
// drag: desarmar en silencio y dejar pasar el click nativo → abre el popover.
|
|
193
|
+
if (this.dragArmedId && !this.dragId) {
|
|
194
|
+
this.dragArmedId = null;
|
|
195
|
+
this.dragStartPointer = null;
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
150
198
|
const id = this.dragId;
|
|
151
199
|
const target = this.dragOverDate;
|
|
152
200
|
this.dragId = null;
|
|
153
201
|
this.dragOverDate = null;
|
|
202
|
+
this.dragStartPointer = null;
|
|
154
203
|
this.setInteracting(false);
|
|
155
204
|
if (!id || !target)
|
|
156
205
|
return;
|
|
@@ -165,6 +214,10 @@ export class CoCalendarMonth {
|
|
|
165
214
|
return;
|
|
166
215
|
if (!this.isDayInteractable(target))
|
|
167
216
|
return;
|
|
217
|
+
this.suppressNextClick = true;
|
|
218
|
+
// Clear on the next macrotask: a same-target synthetic click fires before this
|
|
219
|
+
// (and is suppressed); a later unrelated click is not.
|
|
220
|
+
setTimeout(() => { this.suppressNextClick = false; }, 0);
|
|
168
221
|
const shifted = shiftEventByDays(ev, deltaDays, this.timeZone);
|
|
169
222
|
this.coEventDrop.emit({
|
|
170
223
|
id,
|
|
@@ -256,6 +309,10 @@ export class CoCalendarMonth {
|
|
|
256
309
|
'cal-chip--cancelled': ev.responseState === 'cancelled' || ev.responseState === 'declined',
|
|
257
310
|
}, style: style, title: ev.title, "aria-label": ev.title, onClick: (e) => {
|
|
258
311
|
e.stopPropagation();
|
|
312
|
+
if (this.suppressNextClick) {
|
|
313
|
+
this.suppressNextClick = false;
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
259
316
|
this.activateEvent(ev.id, e.currentTarget.getBoundingClientRect());
|
|
260
317
|
}, onDblClick: (e) => {
|
|
261
318
|
e.stopPropagation();
|
|
@@ -269,7 +326,7 @@ export class CoCalendarMonth {
|
|
|
269
326
|
const short = getWeekdayLabels(this.locale, this.firstDayOfWeek, 'short');
|
|
270
327
|
const long = getWeekdayLabels(this.locale, this.firstDayOfWeek, 'long');
|
|
271
328
|
const today = todayYMD();
|
|
272
|
-
return (h("div", { key: '
|
|
329
|
+
return (h("div", { key: '7cb63a84f7005ee7b5552a5d9b88c598f579b7be', class: "cal-month", part: "grid", role: "grid", onKeyDown: this.handleKeyDown, ref: (el) => (this.rootEl = el) }, h("div", { key: '70281a5799b9b95957d152e15a374001a2ec8fa2', class: "cal-month__dow", role: "row" }, short.map((lbl, i) => (h("span", { class: "cal-month__dow-cell", role: "columnheader", "aria-label": long[i] }, lbl)))), matrix.map((week, wi) => {
|
|
273
330
|
var _a;
|
|
274
331
|
return (h("div", { class: "cal-month__week", role: "row" }, h("div", { class: "cal-month__days" }, week.map((date) => {
|
|
275
332
|
const ymd = parseISO(date);
|
|
@@ -219,3 +219,25 @@
|
|
|
219
219
|
@supports not (background: color-mix(in srgb, red, transparent)) {
|
|
220
220
|
.co-checkbox:not(.co-checkbox--checked) { background: transparent; }
|
|
221
221
|
}
|
|
222
|
+
|
|
223
|
+
/* Anillo de foco por teclado — solo :focus-visible, el click no lo enciende.
|
|
224
|
+
Clase repetida a propósito: la regla del chip overlay apagado
|
|
225
|
+
(.co-checkbox--overlay:not(--checked):not(--indeterminate)) pesa 0,3,0 y con un
|
|
226
|
+
solo .co-checkbox este bloque quedaba por debajo — el foco no se pintaba en el
|
|
227
|
+
estado más frecuente del overlay (WCAG 2.4.7). Con la clase duplicada empata en
|
|
228
|
+
0,3,0 y gana por orden de fuente, por eso este bloque debe quedarse AL FINAL.
|
|
229
|
+
|
|
230
|
+
El halo va a --co-semantic-border-focus (50%), NO al --...-focus-ring (8%):
|
|
231
|
+
en los hermanos (input/select/…) el foco se percibe porque SUMAN el flip del
|
|
232
|
+
borde al 50% y el halo solo lo acompaña; acá el borde está anulado por los
|
|
233
|
+
propios estados (checked / indeterminate / overlay: `border: none`), así que
|
|
234
|
+
el border-color de abajo es un no-op y el halo al 8% (~1,1:1) quedaba
|
|
235
|
+
invisible justo en los estados más frecuentes. Con el 50% el anillo es la
|
|
236
|
+
única señal, y es perceptible.
|
|
237
|
+
NOTA: si diseño decide subir globalmente --co-semantic-border-focus-ring,
|
|
238
|
+
esta regla vuelve al token de halo — la decisión global sigue abierta. */
|
|
239
|
+
.co-checkbox.co-checkbox:focus-visible {
|
|
240
|
+
outline: none;
|
|
241
|
+
border-color: var(--co-semantic-border-focus);
|
|
242
|
+
box-shadow: 0 0 0 2px var(--co-semantic-border-focus);
|
|
243
|
+
}
|
|
@@ -30,6 +30,12 @@ export class CoCheckbox {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
};
|
|
33
|
+
this.handleKeyDown = (e) => {
|
|
34
|
+
if (e.key === ' ') {
|
|
35
|
+
e.preventDefault(); // Space no debe scrollear la página
|
|
36
|
+
this.handleClick(); // ya guarda disabled y resuelve indeterminate
|
|
37
|
+
}
|
|
38
|
+
};
|
|
33
39
|
}
|
|
34
40
|
render() {
|
|
35
41
|
const isOverlay = this.appearance === 'overlay';
|
|
@@ -37,17 +43,17 @@ export class CoCheckbox {
|
|
|
37
43
|
// Only override the fill when a custom color is provided AND the box is "on".
|
|
38
44
|
// Leaving this undefined keeps the default appearance byte-identical to before.
|
|
39
45
|
const boxStyle = this.onColor && isOn ? { background: this.onColor } : undefined;
|
|
40
|
-
return (h("div", { key: '
|
|
46
|
+
return (h("div", { key: 'caf200e3c53efa2a00d28db0e149da953e4432da', class: {
|
|
41
47
|
'co-checkbox-row': true,
|
|
42
48
|
'co-checkbox-row--disabled': this.disabled,
|
|
43
|
-
}, onClick: this.handleClick }, h("span", { key: '
|
|
49
|
+
}, onClick: this.handleClick }, h("span", { key: 'a8fe01958d81587129f304ce33f1f5fef882dd9f', role: "checkbox", "aria-checked": this.indeterminate ? 'mixed' : String(this.checked), "aria-label": this.label || '', "aria-disabled": this.disabled ? 'true' : undefined, tabIndex: this.disabled ? -1 : 0, onKeyDown: this.handleKeyDown, class: {
|
|
44
50
|
'co-checkbox': true,
|
|
45
51
|
[`co-checkbox--${this.size}`]: true,
|
|
46
52
|
'co-checkbox--overlay': isOverlay,
|
|
47
53
|
'co-checkbox--checked': this.checked && !this.indeterminate,
|
|
48
54
|
'co-checkbox--indeterminate': this.indeterminate,
|
|
49
55
|
'co-checkbox--disabled': this.disabled,
|
|
50
|
-
}, style: boxStyle, part: "box" }, h("svg", { key: '
|
|
56
|
+
}, style: boxStyle, part: "box" }, h("svg", { key: 'd9ea60be2c2e58c227aeca8ae3b8e8f459f3522d', class: "co-checkbox__glyph", viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true" }, h("path", { key: 'd25f835b95a2200d44b5e4afdeb727d758e0650f', class: "co-checkbox__check", d: "M3.5 8.5l3 3 6-6.5", pathLength: "1", "stroke-linecap": "round", "stroke-linejoin": "round" }), h("path", { key: 'b7a043b9910736a8bdd15720fd7857c01250086d', class: "co-checkbox__dash", d: "M3 8h10", pathLength: "1", "stroke-linecap": "round" }))), this.label && (h("span", { key: '2e256fb7fd1515bf9dbe3dc380a0d7d09d5347ac', class: "co-checkbox__label", part: "label" }, this.label))));
|
|
51
57
|
}
|
|
52
58
|
static get is() { return "co-checkbox"; }
|
|
53
59
|
static get encapsulation() { return "shadow"; }
|
|
@@ -231,7 +231,7 @@ export class CoCheckboxGroup {
|
|
|
231
231
|
"references": {
|
|
232
232
|
"CheckboxOption": {
|
|
233
233
|
"location": "local",
|
|
234
|
-
"path": "/Users/agox/
|
|
234
|
+
"path": "/Users/agox/Documents/Prolibu/prolibu-cobalt/packages/core/src/components/co-checkbox-group/co-checkbox-group.tsx",
|
|
235
235
|
"id": "src/components/co-checkbox-group/co-checkbox-group.tsx::CheckboxOption"
|
|
236
236
|
}
|
|
237
237
|
}
|
|
@@ -100,6 +100,13 @@
|
|
|
100
100
|
box-shadow: 0 0 0 2px color-mix(in srgb, var(--co-semantic-border-focus) 15%, transparent);
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
/* Anillo de foco por teclado — mismo trazo que el estado abierto. */
|
|
104
|
+
.co-color-picker__trigger:focus-visible {
|
|
105
|
+
outline: none;
|
|
106
|
+
border-color: var(--co-semantic-border-focus);
|
|
107
|
+
box-shadow: 0 0 0 2px color-mix(in srgb, var(--co-semantic-border-focus) 15%, transparent);
|
|
108
|
+
}
|
|
109
|
+
|
|
103
110
|
.co-color-picker--error .co-color-picker__trigger {
|
|
104
111
|
border-color: var(--co-color-status-error);
|
|
105
112
|
}
|
|
@@ -38,6 +38,19 @@ export class CoColorPicker {
|
|
|
38
38
|
};
|
|
39
39
|
this.handlePopoverClose = () => {
|
|
40
40
|
this.isOpen = false;
|
|
41
|
+
this.restoreTriggerFocus();
|
|
42
|
+
};
|
|
43
|
+
this.handleTriggerKeyDown = (e) => {
|
|
44
|
+
if (this.disabled)
|
|
45
|
+
return;
|
|
46
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
47
|
+
e.preventDefault();
|
|
48
|
+
this.isOpen = !this.isOpen;
|
|
49
|
+
}
|
|
50
|
+
else if (e.key === 'ArrowDown' && !this.isOpen) {
|
|
51
|
+
e.preventDefault();
|
|
52
|
+
this.isOpen = true;
|
|
53
|
+
}
|
|
41
54
|
};
|
|
42
55
|
}
|
|
43
56
|
get parsedPresets() {
|
|
@@ -55,10 +68,21 @@ export class CoColorPicker {
|
|
|
55
68
|
}
|
|
56
69
|
return DEFAULT_PRESETS;
|
|
57
70
|
}
|
|
71
|
+
/** Devuelve el foco al trigger al cerrar el panel (Escape, selección),
|
|
72
|
+
* salvo que el cierre venga de un click-outside que ya movió el foco a
|
|
73
|
+
* otro elemento — en ese caso no robarlo. */
|
|
74
|
+
restoreTriggerFocus() {
|
|
75
|
+
var _a;
|
|
76
|
+
const active = document.activeElement;
|
|
77
|
+
if (active === this.el || this.el.contains(active)) {
|
|
78
|
+
(_a = this.triggerEl) === null || _a === void 0 ? void 0 : _a.focus();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
58
81
|
handleSwatchClick(hex) {
|
|
59
82
|
this.value = hex;
|
|
60
83
|
this.coChange.emit(hex);
|
|
61
84
|
this.isOpen = false;
|
|
85
|
+
this.restoreTriggerFocus();
|
|
62
86
|
}
|
|
63
87
|
get statusIcon() {
|
|
64
88
|
const map = {
|
|
@@ -82,10 +106,10 @@ export class CoColorPicker {
|
|
|
82
106
|
const presets = this.parsedPresets;
|
|
83
107
|
const hasValue = !!this.value;
|
|
84
108
|
const statusClass = this.status !== 'default' ? `co-color-picker--${this.status}` : '';
|
|
85
|
-
return (h("div", { key: '
|
|
109
|
+
return (h("div", { key: '5b99c6a39399a6731d09115e7fd2ce2d64baffb2', class: `co-color-picker co-color-picker--${this.size} ${statusClass} ${this.isOpen ? 'co-color-picker--open' : ''} ${this.disabled ? 'co-color-picker--disabled' : ''}` }, this.label && (h("label", { key: '816392c0b3cebcb2e84ea28ea7e6b7976503b6f9', class: "co-color-picker__label" }, this.label, h(RequiredMark, { key: 'e0e1c97522f4516763fc663878203e2dafb783b3', required: this.required }))), h("co-popover", { key: 'd49ba1f9cd64d2498940247f3ba02897dcc9d2d7', open: this.isOpen, placement: "bottom-start", matchWidth: true, offset: 4, onCoOpen: this.handlePopoverOpen, onCoClose: this.handlePopoverClose }, h("div", { key: '1121b429c89fb9cc066cc66564ae547687bf8ed3', slot: "trigger", class: "co-color-picker__trigger", role: "combobox", "aria-expanded": this.isOpen ? 'true' : 'false', "aria-haspopup": "dialog", "aria-controls": this.isOpen ? CoColorPicker.PANEL_ID : undefined, "aria-label": this.label || undefined, "aria-disabled": this.disabled ? 'true' : undefined, tabIndex: this.disabled ? -1 : 0, ref: (el) => (this.triggerEl = el), onKeyDown: this.handleTriggerKeyDown }, h("span", { key: '2567db35022b5ffdd9921d8bec263c90103fd5f3', class: "co-color-picker__swatch", style: { background: hasValue ? this.value : 'transparent' } }), h("span", { key: '7fecc93b5ecc91acf2c874a26ed1f9d8770d9adf', class: `co-color-picker__value ${!hasValue ? 'co-color-picker__value--empty' : ''}` }, hasValue ? this.value : this.placeholder)), this.isOpen && (h("div", { key: '44d904e4156fac15b302ffe86d68cef72bc7518e', class: "co-color-picker__panel", id: CoColorPicker.PANEL_ID, role: "dialog", "aria-label": this.label || this.placeholder }, h("div", { key: 'a566f189c87792f0af3e493c00eed7240dcd49c9', class: "co-color-picker__grid" }, presets.map((preset) => {
|
|
86
110
|
var _a;
|
|
87
111
|
return (h("button", { type: "button", key: preset.value, class: `co-color-picker__grid-item ${preset.value.toUpperCase() === ((_a = this.value) === null || _a === void 0 ? void 0 : _a.toUpperCase()) ? 'co-color-picker__grid-item--selected' : ''}`, style: { background: preset.value }, title: preset.name, onClick: () => this.handleSwatchClick(preset.value) }));
|
|
88
|
-
})), hasValue && (h("div", { key: '
|
|
112
|
+
})), hasValue && (h("div", { key: 'bae8ffe387fe30710b654975379e70a4b3724ec9', class: "co-color-picker__hex-row" }, h("span", { key: '671a5a64abc2f824c8d1ef8c2404023d8d51c28d', class: "co-color-picker__hex-prefix" }, "#"), h("span", { key: '3a6742eb528c5a92fe16b2f2e5d8b2ebdf885450', class: "co-color-picker__hex-value" }, this.value.replace(/^#/, '')), h("button", { key: 'f53957b494dbed0648d32fcb743673a2ada063b4', type: "button", class: `co-color-picker__copy-btn ${this.copied ? 'co-color-picker__copy-btn--copied' : ''}`, title: "Copy", onClick: () => this.handleCopy() }, h("co-icon", { key: 'c99e611beddc2108f3973ecee4f6012423cffd23', name: this.copied ? 'check' : 'copy', size: 16 }))))))), this.helperText && (h("div", { key: '1d6a6e97eab1eafcb9265944693cb0d1121e04ee', class: "co-color-picker__helper" }, this.statusIcon && (h("co-icon", { key: 'bcf943836fddf264cede2f339844f35aa167002f', name: this.statusIcon, size: 14, weight: "fill", class: "co-color-picker__helper-icon" })), h("span", { key: 'a439c4d102d17d0d6c9e55757dcf43ec8b3ade8f', class: "co-color-picker__helper-text" }, this.helperText)))));
|
|
89
113
|
}
|
|
90
114
|
static get is() { return "co-color-picker"; }
|
|
91
115
|
static get encapsulation() { return "shadow"; }
|
|
@@ -353,3 +377,12 @@ export class CoColorPicker {
|
|
|
353
377
|
}
|
|
354
378
|
static get elementRef() { return "el"; }
|
|
355
379
|
}
|
|
380
|
+
/**
|
|
381
|
+
* Id del panel de presets, target de `aria-controls` del trigger. Constante
|
|
382
|
+
* (no aleatoria): vive en el shadow root de ESTA instancia, donde los ids no
|
|
383
|
+
* colisionan con los de otras.
|
|
384
|
+
*
|
|
385
|
+
* El trigger solo emite `aria-controls` mientras `isOpen`: el panel se monta
|
|
386
|
+
* on-demand, así que cerrado el IDREF quedaría colgante.
|
|
387
|
+
*/
|
|
388
|
+
CoColorPicker.PANEL_ID = 'co-color-picker-panel';
|