@leavepulse/ui 0.82.0 → 0.83.1
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/dist/assets/LpVideoTrim-BcGs3Sh0.css +8 -0
- package/dist/assets/VideoFlash-VuPkC7QV.css +23 -0
- package/dist/chunks/{ContextMenuItems-kRVTuSZd.js → ContextMenuItems-h-apPY2d.js} +1 -1
- package/dist/chunks/{DropzoneLinkField-CvMUu8HQ.js → DropzoneLinkField-_Op8b_ee.js} +1 -1
- package/dist/chunks/{FieldLeading-CNN_5Dp1.js → FieldLeading-mOMcwNqi.js} +1 -1
- package/dist/chunks/{FieldWrapper-BBJPEVHr.js → FieldWrapper-DtT-AIcn.js} +1 -1
- package/dist/chunks/{IconCloseButton-C59_etp2.js → IconCloseButton-YmisskyZ.js} +1 -1
- package/dist/chunks/{LayoutCanvas-BIY74SUe.js → LayoutCanvas-BrISZEBr.js} +1 -1
- package/dist/chunks/{LayoutNode-CTlKlhb-.js → LayoutNode-CwMQ8ACa.js} +1 -1
- package/dist/chunks/{LpActionBar-Cowb-28D.js → LpActionBar-CVVC6YbC.js} +1 -1
- package/dist/chunks/{LpAddressInput-nTJlUQ6K.js → LpAddressInput-tiuJ-bru.js} +7 -7
- package/dist/chunks/{LpAlert-DhZdjekN.js → LpAlert-8-x4H_Nk.js} +1 -1
- package/dist/chunks/{LpAppShell-CK9hTgQ1.js → LpAppShell-BW2YMq-A.js} +4 -4
- package/dist/chunks/{LpAutocomplete--PaZkQoU.js → LpAutocomplete-BZODIops.js} +4 -4
- package/dist/chunks/{LpAvatar-DVCld_L2.js → LpAvatar-BZq4nH53.js} +1 -1
- package/dist/chunks/{LpBadge-DXvpxk6z.js → LpBadge-Cz0pSSUu.js} +1 -1
- package/dist/chunks/{LpBottomNav-COQiBNUN.js → LpBottomNav-DVdR5Mry.js} +1 -1
- package/dist/chunks/{LpBreadcrumbs-Dt1O5G4O.js → LpBreadcrumbs-B2jphAel.js} +1 -1
- package/dist/chunks/{LpCalendar-C8Xlukd2.js → LpCalendar-C0_N5LVT.js} +1 -1
- package/dist/chunks/{LpCheckbox-Dpybd3oV.js → LpCheckbox-CwqLNdw4.js} +2 -2
- package/dist/chunks/{LpCodeBlock-C1Hu5RyT.js → LpCodeBlock-Dcss6bnl.js} +2 -2
- package/dist/chunks/{LpCodeDisplay-BVruT3-A.js → LpCodeDisplay-CLms1zDY.js} +4 -4
- package/dist/chunks/{LpColorPicker-BK-NRaGK.js → LpColorPicker-CbIY450X.js} +3 -3
- package/dist/chunks/{LpConfirmDialog-2CQFKtqU.js → LpConfirmDialog-RmP4z6va.js} +2 -2
- package/dist/chunks/{LpConfirmHost-CTkSytVl.js → LpConfirmHost-DheBgRMB.js} +1 -1
- package/dist/chunks/{LpContextMenu-DcHPdW-t.js → LpContextMenu-DIYhlap8.js} +2 -2
- package/dist/chunks/{LpContextSwitcher-C_nnDbeI.js → LpContextSwitcher-fDA6ERl-.js} +2 -2
- package/dist/chunks/{LpCopyButton-De7_elaO.js → LpCopyButton-DSqKFGEb.js} +1 -1
- package/dist/chunks/{LpDataList-BjZPfQjO.js → LpDataList-f9UjSx9m.js} +2 -2
- package/dist/chunks/{LpDatePicker-wOrHd0fg.js → LpDatePicker-3kDQLVbg.js} +5 -5
- package/dist/chunks/{LpDateRangePicker-CVMvvo5t.js → LpDateRangePicker-DUPiTt0_.js} +3 -3
- package/dist/chunks/{LpDisclosure-CtQ63LeO.js → LpDisclosure-C5WZjhbQ.js} +1 -1
- package/dist/chunks/{LpDrawer-TZBPzyOM.js → LpDrawer-CkYvIGIa.js} +1 -1
- package/dist/chunks/{LpDropdownMenu-DL9LgkS8.js → LpDropdownMenu-zSsK2Xra.js} +6 -3
- package/dist/chunks/{LpEditable-DuPE2tkl.js → LpEditable-CP7Y_lKg.js} +2 -2
- package/dist/chunks/{LpEmptyState-W1U_tKg-.js → LpEmptyState-dsc5x-ya.js} +1 -1
- package/dist/chunks/{LpFieldNotice-BLKlYLwu.js → LpFieldNotice-TOTbWvgv.js} +1 -1
- package/dist/chunks/{LpFileTree-CEiRIpj1.js → LpFileTree-BPjnOTln.js} +4 -4
- package/dist/chunks/{LpFileTreeNode-C0292AU2.js → LpFileTreeNode-_-MiWmie.js} +4 -4
- package/dist/chunks/{LpFormField-Bhgi0idH.js → LpFormField-BJwD1iEF.js} +1 -1
- package/dist/chunks/{LpHintLayer-D8ZKsvsT.js → LpHintLayer-CmfGnxYu.js} +1 -1
- package/dist/chunks/{LpHints-DbIV-wk4.js → LpHints-C2Xvc1Fd.js} +3 -3
- package/dist/chunks/{LpIcon-CytpcLmS.js → LpIcon-CoOLz1oJ.js} +1 -1
- package/dist/chunks/{LpInfraNode-8cGoY87Q.js → LpInfraNode-CW3qsM-U.js} +2 -2
- package/dist/chunks/{LpInput-P2C12X7s.js → LpInput-BVWhkPuN.js} +3 -3
- package/dist/chunks/{LpInspector-DjfMa4tD.js → LpInspector-REmoX_es.js} +4 -4
- package/dist/chunks/{LpLightbox-BEQtCz0m.js → LpLightbox-C3FFTYP5.js} +65 -20
- package/dist/chunks/{LpLogInspector-CyxK1d-V.js → LpLogInspector-Cs8Z_fM0.js} +2 -2
- package/dist/chunks/{LpLogViewer-CZrc_HG7.js → LpLogViewer-DQRGe-ZK.js} +6 -6
- package/dist/chunks/{LpMapPicker-CbTfFcEu.js → LpMapPicker-CQnQPgKm.js} +1 -1
- package/dist/chunks/{LpMediaGallery-C_SwNR4y.js → LpMediaGallery-DkKqTy9P.js} +20 -8
- package/dist/chunks/{LpModal-B6Yt0G7E.js → LpModal-DwejR7VP.js} +1 -1
- package/dist/chunks/{LpModalNav-CMYWEiUs.js → LpModalNav-D1-dSe6f.js} +3 -3
- package/dist/chunks/{LpNotificationBell-BJKPx0X1.js → LpNotificationBell-EKLkN0sp.js} +4 -4
- package/dist/chunks/{LpNotificationFeed-orWISVez.js → LpNotificationFeed-Dc-yQ4NH.js} +4 -4
- package/dist/chunks/{LpNumberField-C5MeLsoE.js → LpNumberField-DvUjEMp-.js} +3 -3
- package/dist/chunks/{LpOtpInput-6SCQ64uK.js → LpOtpInput-xvDO14f4.js} +1 -1
- package/dist/chunks/{LpPagination-CkwbdFa9.js → LpPagination-BFZY8dFn.js} +1 -1
- package/dist/chunks/{LpPaletteTheme-RWgbWFOz.js → LpPaletteTheme-EBJv0Bwz.js} +2 -2
- package/dist/chunks/{LpPasswordInput-DJBRo5lt.js → LpPasswordInput-DRJR3fF1.js} +2 -2
- package/dist/chunks/{LpPhoneInput-DcmcJwos.js → LpPhoneInput-BGOOtUoW.js} +3 -3
- package/dist/chunks/{LpPie-DmMlhgo2.js → LpPie-Deyn8h5J.js} +2 -2
- package/dist/chunks/{LpPipeline-BXcp2shV.js → LpPipeline-9ChjeNh5.js} +2 -2
- package/dist/chunks/{LpQuota-CtEiqRg4.js → LpQuota-CNh5eF_p.js} +1 -1
- package/dist/chunks/{LpRadioGroup-Ubkk_eSq.js → LpRadioGroup-Br2Nl9Gr.js} +2 -2
- package/dist/chunks/{LpRangeCalendar-DCswVYbU.js → LpRangeCalendar-D66Y3U_w.js} +1 -1
- package/dist/chunks/{LpRangeSlider-DM1qy5Tp.js → LpRangeSlider-eX-fIlOw.js} +1 -1
- package/dist/chunks/{LpRepeater-BMo2Eabn.js → LpRepeater-0XvnlVd_.js} +2 -2
- package/dist/chunks/{LpSecretField-SSRGVqRc.js → LpSecretField-1RCooChq.js} +5 -5
- package/dist/chunks/{LpSection-C3bTxD7t.js → LpSection-B9gbJJcP.js} +1 -1
- package/dist/chunks/{LpSegmented-C1DF6m6A.js → LpSegmented-hIjGzGTo.js} +2 -2
- package/dist/chunks/{LpSelect-DvDr1VPn.js → LpSelect-DugubwTc.js} +4 -4
- package/dist/chunks/{LpSidebar-Bql881r6.js → LpSidebar-DevSoN8W.js} +4 -4
- package/dist/chunks/{LpSidebarNav-CKx2KJvr.js → LpSidebarNav-DwQQ3KXe.js} +3 -3
- package/dist/chunks/{LpSidebarRow-BMOL9kOG.js → LpSidebarRow-CAGgtJCS.js} +3 -3
- package/dist/chunks/{LpSlider-DsSMe4b5.js → LpSlider-1fHpVxpB.js} +1 -1
- package/dist/chunks/{LpStat-CQYYDMS6.js → LpStat-Cm9-FaWQ.js} +2 -2
- package/dist/chunks/{LpStatus-g5nSJfU3.js → LpStatus-CE8FSAT1.js} +1 -1
- package/dist/chunks/{LpStepMark-seVNOuNJ.js → LpStepMark-BWSlV69R.js} +1 -1
- package/dist/chunks/{LpStepper-D6qEXihZ.js → LpStepper-1D0ZA7Nx.js} +1 -1
- package/dist/chunks/{LpSwitch-DM5CVm31.js → LpSwitch-D_kvE0oQ.js} +1 -1
- package/dist/chunks/{LpTabFace-ZWwelqRU.js → LpTabFace-CpbFKi-N.js} +2 -2
- package/dist/chunks/{LpTableFilter-CWU9Dmk-.js → LpTableFilter-BYHyCu38.js} +2 -2
- package/dist/chunks/{LpTagsInput-C2qVF2ax.js → LpTagsInput-Cqm38t5N.js} +5 -5
- package/dist/chunks/{LpTextarea-C3EoV_xv.js → LpTextarea-DaSKBkiV.js} +1 -1
- package/dist/chunks/{LpThemeStudio-BIBahaIs.js → LpThemeStudio-BNdfi-Ds.js} +5 -5
- package/dist/chunks/{LpThemeSwitcher-D4MqFLGS.js → LpThemeSwitcher-CbC1nqBP.js} +3 -3
- package/dist/chunks/{LpTimeField-DPOkbvyP.js → LpTimeField-CCfPCWPT.js} +2 -2
- package/dist/chunks/{LpToaster-Dj1VKxFv.js → LpToaster-BhlI7h-h.js} +2 -2
- package/dist/chunks/{LpTopologyCanvas-Bspzwm7x.js → LpTopologyCanvas-DlKbu7E5.js} +4 -4
- package/dist/chunks/{LpTour-CwpVdVN-.js → LpTour-CSLerA1B.js} +1 -1
- package/dist/chunks/{LpTruncate-JSaIW25q.js → LpTruncate-Ct26BSYB.js} +1 -1
- package/dist/chunks/{LpVideoBackdrop-Ct4St4ZL.js → LpVideoBackdrop-57i8RY8g.js} +1 -2
- package/dist/chunks/LpVideoPlayer-DcWd9DDW.js +813 -0
- package/dist/chunks/{LpWindow-RVcP436Z.js → LpWindow-YnF_knSj.js} +2 -2
- package/dist/chunks/{LpWorkspaceSwitcher-D0WOz1Fg.js → LpWorkspaceSwitcher-B-5UU6o4.js} +3 -3
- package/dist/chunks/{MenuTitle-D8Umab23.js → MenuTitle-BnZbFSwq.js} +1 -1
- package/dist/chunks/{OptionContent-DCnX_KRK.js → OptionContent-D5e_eklA.js} +1 -1
- package/dist/chunks/{PickerField-ChjY27bm.js → PickerField-KpHiowNI.js} +1 -1
- package/dist/chunks/{RevealToggle-MaL7PEjr.js → RevealToggle-CE1hAIJB.js} +1 -1
- package/dist/chunks/SteadyText-CwH-kduj.js +25 -0
- package/dist/chunks/{TourCard-oXZSpHKb.js → TourCard-t2csiTDS.js} +1 -1
- package/dist/chunks/VideoThumb-Csf9bUz3.js +67 -0
- package/dist/chunks/VideoVolume-B4_DNmNi.js +68 -0
- package/dist/chunks/icons-CdSlCrle.js +59 -0
- package/dist/component-names.d.ts +1 -1
- package/dist/components/ContextMenuItems.vue.js +1 -1
- package/dist/components/DropzoneLinkField.vue.js +1 -1
- package/dist/components/FieldLeading.vue.js +1 -1
- package/dist/components/FieldWrapper.vue.js +1 -1
- package/dist/components/IconCloseButton.vue.js +1 -1
- package/dist/components/LayoutCanvas.vue.js +1 -1
- package/dist/components/LayoutNode.vue.js +1 -1
- package/dist/components/LpActionBar.vue.js +1 -1
- package/dist/components/LpAddressInput.vue.js +1 -1
- package/dist/components/LpAlert.vue.js +1 -1
- package/dist/components/LpAppShell.vue.js +1 -1
- package/dist/components/LpAutocomplete.vue.js +1 -1
- package/dist/components/LpAvatar.vue.js +1 -1
- package/dist/components/LpBadge.vue.js +1 -1
- package/dist/components/LpBottomNav.vue.js +1 -1
- package/dist/components/LpBreadcrumbs.vue.js +1 -1
- package/dist/components/LpCalendar.vue.js +1 -1
- package/dist/components/LpChart.vue.js +2 -2
- package/dist/components/LpCheckbox.vue.js +1 -1
- package/dist/components/LpCodeBlock.vue.js +1 -1
- package/dist/components/LpCodeDisplay.vue.js +1 -1
- package/dist/components/LpColorPicker.vue.js +1 -1
- package/dist/components/LpCommandPalette.vue.js +2 -2
- package/dist/components/LpConfirmDialog.vue.js +1 -1
- package/dist/components/LpConfirmHost.vue.js +1 -1
- package/dist/components/LpContextMenu.vue.js +1 -1
- package/dist/components/LpContextSwitcher.vue.js +1 -1
- package/dist/components/LpCopyButton.vue.js +1 -1
- package/dist/components/LpDataList.vue.js +1 -1
- package/dist/components/LpDatePicker.vue.js +1 -1
- package/dist/components/LpDateRangePicker.vue.js +1 -1
- package/dist/components/LpDisclosure.vue.js +1 -1
- package/dist/components/LpDock.vue.js +1 -1
- package/dist/components/LpDrawer.vue.js +1 -1
- package/dist/components/LpDropdownMenu.vue.d.ts +5 -1
- package/dist/components/LpDropdownMenu.vue.js +1 -1
- package/dist/components/LpDropzone.vue.js +2 -2
- package/dist/components/LpEditable.vue.js +1 -1
- package/dist/components/LpEmptyState.vue.js +1 -1
- package/dist/components/LpFieldNotice.vue.js +1 -1
- package/dist/components/LpFileTree.vue.js +1 -1
- package/dist/components/LpFileTreeNode.vue.js +1 -1
- package/dist/components/LpFormField.vue.js +1 -1
- package/dist/components/LpHintLayer.vue.js +1 -1
- package/dist/components/LpHints.vue.js +1 -1
- package/dist/components/LpIcon.vue.js +1 -1
- package/dist/components/LpImageEditor.vue.js +1 -1
- package/dist/components/LpInfraNode.vue.js +1 -1
- package/dist/components/LpInput.vue.js +1 -1
- package/dist/components/LpInspector.vue.js +1 -1
- package/dist/components/LpLightbox.vue.js +1 -1
- package/dist/components/LpLogInspector.vue.js +1 -1
- package/dist/components/LpLogViewer.vue.js +1 -1
- package/dist/components/LpMapPicker.vue.js +1 -1
- package/dist/components/LpMediaGallery.vue.d.ts +1 -1
- package/dist/components/LpMediaGallery.vue.js +1 -1
- package/dist/components/LpModal.vue.js +1 -1
- package/dist/components/LpModalNav.vue.js +1 -1
- package/dist/components/LpNotificationBell.vue.js +1 -1
- package/dist/components/LpNotificationFeed.vue.js +1 -1
- package/dist/components/LpNumberField.vue.js +1 -1
- package/dist/components/LpOtpInput.vue.js +1 -1
- package/dist/components/LpPagination.vue.js +1 -1
- package/dist/components/LpPaletteFacts.vue.js +1 -1
- package/dist/components/LpPaletteHeader.vue.js +2 -2
- package/dist/components/LpPaletteTheme.vue.js +1 -1
- package/dist/components/LpPasswordInput.vue.js +1 -1
- package/dist/components/LpPhoneInput.vue.js +1 -1
- package/dist/components/LpPie.vue.js +1 -1
- package/dist/components/LpPipeline.vue.js +1 -1
- package/dist/components/LpQuota.vue.js +1 -1
- package/dist/components/LpRadioGroup.vue.js +1 -1
- package/dist/components/LpRangeCalendar.vue.js +1 -1
- package/dist/components/LpRangeSlider.vue.js +1 -1
- package/dist/components/LpRepeater.vue.js +1 -1
- package/dist/components/LpSecretField.vue.js +1 -1
- package/dist/components/LpSection.vue.js +1 -1
- package/dist/components/LpSegmented.vue.js +1 -1
- package/dist/components/LpSelect.vue.js +1 -1
- package/dist/components/LpShortcutsDialog.vue.js +1 -1
- package/dist/components/LpSidebar.vue.js +1 -1
- package/dist/components/LpSidebarNav.vue.js +1 -1
- package/dist/components/LpSidebarRow.vue.js +1 -1
- package/dist/components/LpSlider.vue.js +1 -1
- package/dist/components/LpStat.vue.js +1 -1
- package/dist/components/LpStatus.vue.js +1 -1
- package/dist/components/LpStepMark.vue.js +1 -1
- package/dist/components/LpStepper.vue.js +1 -1
- package/dist/components/LpSwitch.vue.js +1 -1
- package/dist/components/LpTabFace.vue.js +1 -1
- package/dist/components/LpTable.vue.js +9 -9
- package/dist/components/LpTableFilter.vue.js +1 -1
- package/dist/components/LpTabs.vue.js +2 -2
- package/dist/components/LpTagsInput.vue.js +1 -1
- package/dist/components/LpTextarea.vue.js +1 -1
- package/dist/components/LpThemeStudio.vue.js +1 -1
- package/dist/components/LpThemeSwitcher.vue.js +1 -1
- package/dist/components/LpTimeField.vue.js +1 -1
- package/dist/components/LpToaster.vue.js +1 -1
- package/dist/components/LpTopologyCanvas.vue.js +1 -1
- package/dist/components/LpTour.vue.js +1 -1
- package/dist/components/LpTruncate.vue.js +1 -1
- package/dist/components/LpVideoBackdrop.vue.js +1 -1
- package/dist/components/LpVideoPlayer.vue.d.ts +111 -0
- package/dist/components/LpVideoPlayer.vue.js +4 -0
- package/dist/components/LpVideoTrim.vue.d.ts +19 -5
- package/dist/components/LpVideoTrim.vue.js +596 -445
- package/dist/components/LpWindow.vue.js +1 -1
- package/dist/components/LpWorkspaceSwitcher.vue.js +1 -1
- package/dist/components/MenuTitle.vue.js +1 -1
- package/dist/components/OptionContent.vue.js +1 -1
- package/dist/components/PickerField.vue.js +1 -1
- package/dist/components/RevealToggle.vue.js +1 -1
- package/dist/components/SteadyText.vue.d.ts +7 -0
- package/dist/components/SteadyText.vue.js +4 -0
- package/dist/components/TourCard.vue.js +1 -1
- package/dist/components/VideoFlash.vue.d.ts +10 -0
- package/dist/components/VideoFlash.vue.js +33 -0
- package/dist/components/VideoThumb.vue.d.ts +12 -0
- package/dist/components/VideoThumb.vue.js +4 -0
- package/dist/components/VideoVolume.vue.d.ts +15 -0
- package/dist/components/VideoVolume.vue.js +4 -0
- package/dist/components/lightbox.d.ts +13 -0
- package/dist/components/lightbox.js +11 -1
- package/dist/components/menuWhen.js +1 -1
- package/dist/components/videoBackdrop.d.ts +15 -2
- package/dist/components/videoBackdrop.js +8 -2
- package/dist/components/videoMotion.d.ts +28 -0
- package/dist/components/videoMotion.js +26 -0
- package/dist/components/videoTrim.d.ts +2 -9
- package/dist/components/videoTrim.js +0 -6
- package/dist/composables/useVideoMedia.d.ts +74 -0
- package/dist/composables/useVideoMedia.js +291 -0
- package/dist/icons-all.js +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +173 -168
- package/package.json +5 -2
- package/src/component-names.ts +1 -0
- package/src/components/LpDropdownMenu.vue +4 -0
- package/src/components/LpLightbox.vue +61 -8
- package/src/components/LpMediaGallery.vue +19 -2
- package/src/components/LpVideoBackdrop.vue +1 -7
- package/src/components/LpVideoPlayer.vue +774 -0
- package/src/components/LpVideoTrim.vue +437 -295
- package/src/components/SteadyText.vue +21 -0
- package/src/components/VideoFlash.vue +52 -0
- package/src/components/VideoThumb.vue +66 -0
- package/src/components/VideoVolume.vue +63 -0
- package/src/components/lightbox.ts +23 -0
- package/src/components/videoBackdrop.ts +20 -3
- package/src/components/videoMotion.ts +38 -0
- package/src/components/videoTrim.ts +2 -14
- package/src/composables/useVideoMedia.ts +411 -0
- package/src/index.ts +4 -1
- package/src/kit-icons.ts +1 -1
- package/dist/assets/LpVideoTrim-Ci6IIHW0.css +0 -8
- package/dist/chunks/icons-BOdqDUii.js +0 -59
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/*
|
|
3
|
+
* Text that changes while it is read (a clock, a length under a dragged
|
|
4
|
+
* handle) in a box as wide as the widest it will get: an invisible twin of
|
|
5
|
+
* that widest text sits in the same grid cell, the way LpEditable sizes its
|
|
6
|
+
* field. Without it each new value resized the box and everything after it
|
|
7
|
+
* on the line shifted, and a row that wraps re-wrapped and took the whole
|
|
8
|
+
* layout under it up and down with it.
|
|
9
|
+
*
|
|
10
|
+
* `widest` is the longest the text can be, its digits as zeros: with
|
|
11
|
+
* tabular figures every digit is as wide as a zero.
|
|
12
|
+
*/
|
|
13
|
+
defineProps<{ text: string; widest: string }>()
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<span class="inline-grid whitespace-nowrap tabular-nums">
|
|
18
|
+
<span aria-hidden="true" class="invisible col-start-1 row-start-1">{{ widest }}</span>
|
|
19
|
+
<span class="col-start-1 row-start-1">{{ text }}</span>
|
|
20
|
+
</span>
|
|
21
|
+
</template>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/*
|
|
3
|
+
* The answer to a play or pause on LpVideoPlayer and LpVideoTrim: a round
|
|
4
|
+
* badge with what just happened, in the middle of the picture, that swells a
|
|
5
|
+
* little and fades. A press on the picture, or Space with the eyes on it,
|
|
6
|
+
* otherwise has only the motion itself to confirm it, and a pause on a still
|
|
7
|
+
* scene shows nothing at all. Each new `flash` replays it; reduced motion has none.
|
|
8
|
+
*/
|
|
9
|
+
import LpIcon from "./LpIcon.vue"
|
|
10
|
+
|
|
11
|
+
defineProps<{
|
|
12
|
+
/** What happened, and a count that changes on every press so the same icon twice still plays twice. */
|
|
13
|
+
flash: { icon: string; n: number } | null
|
|
14
|
+
}>()
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<template>
|
|
18
|
+
<div class="pointer-events-none absolute inset-0 flex items-center justify-center motion-reduce:hidden" aria-hidden="true">
|
|
19
|
+
<span
|
|
20
|
+
v-if="flash"
|
|
21
|
+
:key="flash.n"
|
|
22
|
+
class="lp-video-flash flex size-16 items-center justify-center rounded-full bg-black/50 text-white opacity-0"
|
|
23
|
+
>
|
|
24
|
+
<LpIcon :name="flash.icon" :size="28" :class="flash.icon === 'lucide:play' ? 'translate-x-0.5' : ''" />
|
|
25
|
+
</span>
|
|
26
|
+
</div>
|
|
27
|
+
</template>
|
|
28
|
+
|
|
29
|
+
<style scoped>
|
|
30
|
+
/* Rises into view, holds a moment, then swells away: arriving at full strength in one frame read as a blink. */
|
|
31
|
+
.lp-video-flash {
|
|
32
|
+
animation: lp-video-flash 700ms var(--ease-emphasized) both;
|
|
33
|
+
}
|
|
34
|
+
@keyframes lp-video-flash {
|
|
35
|
+
0% {
|
|
36
|
+
opacity: 0;
|
|
37
|
+
scale: 0.8;
|
|
38
|
+
}
|
|
39
|
+
25% {
|
|
40
|
+
opacity: 1;
|
|
41
|
+
scale: 1;
|
|
42
|
+
}
|
|
43
|
+
45% {
|
|
44
|
+
opacity: 1;
|
|
45
|
+
scale: 1;
|
|
46
|
+
}
|
|
47
|
+
100% {
|
|
48
|
+
opacity: 0;
|
|
49
|
+
scale: 1.15;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
</style>
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/*
|
|
3
|
+
* A video's face in a grid or a strip (LpMediaGallery's tile, LpLightbox's
|
|
4
|
+
* filmstrip): its poster, or without one its first frame, under a round play
|
|
5
|
+
* badge and, when there is room and it is known, its length.
|
|
6
|
+
*
|
|
7
|
+
* Without a poster the frame comes from the video itself: metadata and the
|
|
8
|
+
* one frame at a tenth of a second (a media fragment), never the whole file.
|
|
9
|
+
* Its length comes along with the metadata when the item did not say it.
|
|
10
|
+
*/
|
|
11
|
+
import { computed, ref } from "vue"
|
|
12
|
+
import LpIcon from "./LpIcon.vue"
|
|
13
|
+
import type { LightboxItem } from "./lightbox"
|
|
14
|
+
import { formatClipTime } from "./videoBackdrop"
|
|
15
|
+
|
|
16
|
+
const props = defineProps<{
|
|
17
|
+
item: LightboxItem
|
|
18
|
+
/** Classes for the picture itself (the gallery's blur and dimming ride on it). */
|
|
19
|
+
mediaClass?: string | (string | false | undefined)[]
|
|
20
|
+
/** A small thumbnail: the badge shrinks and the length is left out. */
|
|
21
|
+
compact?: boolean
|
|
22
|
+
alt?: string
|
|
23
|
+
}>()
|
|
24
|
+
|
|
25
|
+
const measured = ref<number | null>(null)
|
|
26
|
+
const length = computed(() => props.item.duration ?? measured.value)
|
|
27
|
+
const still = computed(() => props.item.poster ?? props.item.thumb)
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<template>
|
|
31
|
+
<img
|
|
32
|
+
v-if="still"
|
|
33
|
+
:src="still"
|
|
34
|
+
:alt="alt ?? ''"
|
|
35
|
+
loading="lazy"
|
|
36
|
+
decoding="async"
|
|
37
|
+
class="size-full object-cover"
|
|
38
|
+
:class="mediaClass"
|
|
39
|
+
draggable="false"
|
|
40
|
+
/>
|
|
41
|
+
<video
|
|
42
|
+
v-else
|
|
43
|
+
:src="`${item.src}#t=0.1`"
|
|
44
|
+
preload="metadata"
|
|
45
|
+
muted
|
|
46
|
+
playsinline
|
|
47
|
+
tabindex="-1"
|
|
48
|
+
aria-hidden="true"
|
|
49
|
+
class="pointer-events-none size-full object-cover"
|
|
50
|
+
:class="mediaClass"
|
|
51
|
+
@loadedmetadata="(e) => Number.isFinite((e.target as HTMLVideoElement).duration) && (measured = (e.target as HTMLVideoElement).duration)"
|
|
52
|
+
/>
|
|
53
|
+
<span class="pointer-events-none absolute inset-0 flex items-center justify-center" aria-hidden="true">
|
|
54
|
+
<span
|
|
55
|
+
class="flex items-center justify-center rounded-full bg-black/50 text-white ring-1 ring-white/25 transition-transform duration-(--duration-fast) ease-(--ease-emphasized) group-hover:scale-110 motion-reduce:transition-none motion-reduce:group-hover:scale-100"
|
|
56
|
+
:class="compact ? 'size-6' : 'size-10'"
|
|
57
|
+
>
|
|
58
|
+
<LpIcon name="lucide:play" :size="compact ? 12 : 18" class="translate-x-px" />
|
|
59
|
+
</span>
|
|
60
|
+
</span>
|
|
61
|
+
<span
|
|
62
|
+
v-if="!compact && length"
|
|
63
|
+
class="pointer-events-none absolute right-1.5 bottom-1.5 rounded-pill bg-black/60 px-1.5 py-0.5 font-mono text-[10px] text-white tabular-nums"
|
|
64
|
+
aria-hidden="true"
|
|
65
|
+
>{{ formatClipTime(length) }}</span>
|
|
66
|
+
</template>
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/*
|
|
3
|
+
* Sound for LpVideoPlayer and LpVideoTrim: the mute button, and beside it a
|
|
4
|
+
* volume slider that opens while the pointer or the focus is on either, the
|
|
5
|
+
* way most players keep it out of the way until it is reached for. On a touch
|
|
6
|
+
* screen the slider stays closed: a phone's own buttons set the volume, and a
|
|
7
|
+
* thin slider under a thumb only gets in the way.
|
|
8
|
+
*/
|
|
9
|
+
import LpButton from "./LpButton.vue"
|
|
10
|
+
import LpIcon from "./LpIcon.vue"
|
|
11
|
+
import LpSlider from "./LpSlider.vue"
|
|
12
|
+
import type { VideoMedia } from "../composables/useVideoMedia"
|
|
13
|
+
import { ICON_SWAP, OVERLAY_BUTTON } from "./videoMotion"
|
|
14
|
+
|
|
15
|
+
const props = defineProps<{
|
|
16
|
+
media: VideoMedia
|
|
17
|
+
labels: { mute: string; unmute: string; volume: string }
|
|
18
|
+
disabled?: boolean
|
|
19
|
+
/** Over the picture (white on the video) rather than on the page. */
|
|
20
|
+
overlay?: boolean
|
|
21
|
+
}>()
|
|
22
|
+
|
|
23
|
+
const icon = () => {
|
|
24
|
+
const { muted, volume } = props.media
|
|
25
|
+
if (muted.value || volume.value === 0) return "lucide:volume-x"
|
|
26
|
+
return volume.value < 0.5 ? "lucide:volume-1" : "lucide:volume-2"
|
|
27
|
+
}
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<template>
|
|
31
|
+
<div class="group/volume flex items-center">
|
|
32
|
+
<LpButton
|
|
33
|
+
size="sm"
|
|
34
|
+
variant="ghost"
|
|
35
|
+
square
|
|
36
|
+
:class="overlay ? OVERLAY_BUTTON : ''"
|
|
37
|
+
:disabled="disabled"
|
|
38
|
+
:aria-label="media.muted.value ? labels.unmute : labels.mute"
|
|
39
|
+
:title="media.muted.value ? labels.unmute : labels.mute"
|
|
40
|
+
:aria-pressed="media.muted.value"
|
|
41
|
+
@click="media.setMuted(!media.muted.value)"
|
|
42
|
+
>
|
|
43
|
+
<!-- Muting, and the volume crossing a half, hand the icon over instead of swapping it in one frame. -->
|
|
44
|
+
<Transition v-bind="ICON_SWAP">
|
|
45
|
+
<LpIcon :key="icon()" :name="icon()" :size="16" />
|
|
46
|
+
</Transition>
|
|
47
|
+
</LpButton>
|
|
48
|
+
<div
|
|
49
|
+
class="w-0 overflow-hidden opacity-0 transition-[width,opacity] duration-(--duration-medium) ease-(--ease-emphasized) group-hover/volume:w-20 group-hover/volume:opacity-100 group-focus-within/volume:w-20 group-focus-within/volume:opacity-100 pointer-coarse:hidden motion-reduce:transition-none"
|
|
50
|
+
>
|
|
51
|
+
<LpSlider
|
|
52
|
+
class="mx-2 w-16"
|
|
53
|
+
:model-value="media.muted.value ? 0 : Math.round(media.volume.value * 100)"
|
|
54
|
+
:min="0"
|
|
55
|
+
:max="100"
|
|
56
|
+
:step="5"
|
|
57
|
+
:disabled="disabled"
|
|
58
|
+
:aria-label="labels.volume"
|
|
59
|
+
@update:model-value="(v: number) => media.setVolume(v / 100)"
|
|
60
|
+
/>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
</template>
|
|
@@ -24,6 +24,29 @@ export interface LightboxItem {
|
|
|
24
24
|
* a marked picture sitting beside plain ones.
|
|
25
25
|
*/
|
|
26
26
|
spoiler?: boolean
|
|
27
|
+
/**
|
|
28
|
+
* A video rather than a picture: shown in LpVideoPlayer, with a play badge
|
|
29
|
+
* on its tile and its strip thumbnail. Left out, an address ending in a
|
|
30
|
+
* video extension (.mp4, .webm, .mov…) counts as one; set it for an address
|
|
31
|
+
* that does not say (an API route, a blob: URL).
|
|
32
|
+
*/
|
|
33
|
+
kind?: "image" | "video"
|
|
34
|
+
/** A still for a video's tile and thumbnail; the video's first frame when absent. */
|
|
35
|
+
poster?: string
|
|
36
|
+
/** A video's length in seconds, for the badge on its tile, when known up front. */
|
|
37
|
+
duration?: number
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const VIDEO_EXTENSION = /\.(mp4|m4v|webm|mov|ogv|ogg|mkv)$/i
|
|
41
|
+
|
|
42
|
+
/** Whether the item is a video: said outright by `kind`, else told by its address's extension. */
|
|
43
|
+
export function isVideoItem(item: Pick<LightboxItem, "src" | "kind">): boolean {
|
|
44
|
+
if (item.kind) return item.kind === "video"
|
|
45
|
+
try {
|
|
46
|
+
return VIDEO_EXTENSION.test(new URL(item.src, "http://x").pathname)
|
|
47
|
+
} catch {
|
|
48
|
+
return false
|
|
49
|
+
}
|
|
27
50
|
}
|
|
28
51
|
|
|
29
52
|
/** Last path segment of a URL, or a fallback — used to name a download. */
|
|
@@ -64,12 +64,29 @@ export function isNativeLoop(plan: LoopPlan, duration: number): boolean {
|
|
|
64
64
|
return plan.fade === 0 && plan.from === 0 && plan.to >= duration
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
/**
|
|
68
|
-
|
|
67
|
+
/**
|
|
68
|
+
* "1:05", "0:07.5": a position on a clip as a range slider labels it.
|
|
69
|
+
* `precise` adds tenths when there are any; "fixed" always writes them, for a
|
|
70
|
+
* time that changes while it is read, so it keeps one length ("0:05.0", not
|
|
71
|
+
* "0:05" between "0:04.9" and "0:05.1").
|
|
72
|
+
*/
|
|
73
|
+
export function formatClipTime(seconds: number, precise: boolean | "fixed" = false): string {
|
|
69
74
|
const t = splitTime(Number.isFinite(seconds) ? seconds * 1000 : 0)
|
|
70
75
|
// Hours fold into minutes: a slider over a clip reads "75:00", not "1:15:00".
|
|
71
76
|
const minutes = (t.days * 24 + t.hours) * 60 + t.minutes
|
|
72
77
|
const secs = String(t.seconds).padStart(2, "0")
|
|
73
78
|
const tenth = Math.floor(t.ms / 100)
|
|
74
|
-
return precise && tenth ? `${minutes}:${secs}.${tenth}` : `${minutes}:${secs}`
|
|
79
|
+
return precise === "fixed" || (precise && tenth) ? `${minutes}:${secs}.${tenth}` : `${minutes}:${secs}`
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** The widest a clip time up to `seconds` gets, its digits as zeros: what SteadyText reserves. */
|
|
83
|
+
export function widestClipTime(seconds: number): string {
|
|
84
|
+
return formatClipTime(seconds, "fixed").replace(/\d/g, "0")
|
|
75
85
|
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The slowest rate a pause eases down to before the element is paused, and a
|
|
89
|
+
* start eases up from. Chromium refuses a rate below a sixteenth; past that
|
|
90
|
+
* point the picture is all but still anyway.
|
|
91
|
+
*/
|
|
92
|
+
export const SLOWEST_RATE = 1 / 16
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* How things come and go in LpVideoPlayer and LpVideoTrim, bound on a
|
|
3
|
+
* <Transition> with v-bind so every control, overlay and hint in both appears
|
|
4
|
+
* and leaves the same way, never in a single frame. Entrances take the
|
|
5
|
+
* medium duration and the emphasized curve; exits are quicker, since what is
|
|
6
|
+
* leaving no longer needs reading. Reduced motion swaps instantly.
|
|
7
|
+
*/
|
|
8
|
+
export const FADE = {
|
|
9
|
+
enterActiveClass: "transition-opacity duration-(--duration-medium) ease-(--ease-emphasized) motion-reduce:transition-none",
|
|
10
|
+
leaveActiveClass: "transition-opacity duration-(--duration-fast) ease-(--ease-emphasized) motion-reduce:transition-none",
|
|
11
|
+
enterFromClass: "opacity-0",
|
|
12
|
+
leaveToClass: "opacity-0",
|
|
13
|
+
} as const
|
|
14
|
+
|
|
15
|
+
/** A fade with a small settle in scale: for a button or a box, which reads as arriving rather than blinking on. */
|
|
16
|
+
export const FADE_SCALE = {
|
|
17
|
+
enterActiveClass: "transition-[opacity,scale] duration-(--duration-medium) ease-(--ease-emphasized) motion-reduce:transition-none",
|
|
18
|
+
leaveActiveClass: "transition-[opacity,scale] duration-(--duration-fast) ease-(--ease-emphasized) motion-reduce:transition-none",
|
|
19
|
+
enterFromClass: "scale-90 opacity-0",
|
|
20
|
+
leaveToClass: "scale-90 opacity-0",
|
|
21
|
+
} as const
|
|
22
|
+
|
|
23
|
+
/** One icon handing over to another in the same spot (play and pause, sound on and off). */
|
|
24
|
+
export const ICON_SWAP = {
|
|
25
|
+
mode: "out-in",
|
|
26
|
+
enterActiveClass: "transition-[opacity,scale] duration-(--duration-fast) ease-(--ease-emphasized) motion-reduce:transition-none",
|
|
27
|
+
leaveActiveClass: "transition-[opacity,scale] duration-(--duration-fast) ease-(--ease-emphasized) motion-reduce:transition-none",
|
|
28
|
+
enterFromClass: "scale-75 opacity-0",
|
|
29
|
+
leaveToClass: "scale-75 opacity-0",
|
|
30
|
+
} as const
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* A ghost button over the picture: white on the video in every theme, as the
|
|
34
|
+
* lightbox's are. The players' own buttons use it, and their `#actions` and
|
|
35
|
+
* `#controls` slots hand it out as `buttonClass`, so an app's button there
|
|
36
|
+
* (a bin that takes the video back out of a dropzone) looks like theirs.
|
|
37
|
+
*/
|
|
38
|
+
export const OVERLAY_BUTTON = "text-white/85 hover:bg-white/15 hover:text-white"
|
|
@@ -182,17 +182,5 @@ export function adjustRegion(r: FrameRegion, handle: RegionHandle, dx: number, d
|
|
|
182
182
|
return { x: fix(x0), y: fix(y0), w: fix(x1 - x0), h: fix(y1 - y0) }
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
-
/**
|
|
186
|
-
|
|
187
|
-
* - "load": the address did not answer (network, 404, an expired session);
|
|
188
|
-
* - "decode": the file arrived but is damaged;
|
|
189
|
-
* - "unsupported": a format or codec this browser cannot play (H.265 in
|
|
190
|
-
* Chromium without hardware support, ProRes anywhere).
|
|
191
|
-
*/
|
|
192
|
-
export type VideoTrimErrorCause = "load" | "decode" | "unsupported"
|
|
193
|
-
|
|
194
|
-
export function mediaErrorCause(error: MediaError | null): VideoTrimErrorCause {
|
|
195
|
-
if (error?.code === 3) return "decode"
|
|
196
|
-
if (error?.code === 4) return "unsupported"
|
|
197
|
-
return "load"
|
|
198
|
-
}
|
|
185
|
+
/** Why the editor has no video to show; see `VideoErrorCause`. */
|
|
186
|
+
export type { VideoErrorCause as VideoTrimErrorCause } from "../composables/useVideoMedia"
|