wsemi 1.9.0 → 1.9.2
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/README.md +1 -1
- package/dist/wsemi.umd.js +4 -4
- package/dist/wsemi.umd.js.map +1 -1
- package/docs/-_class.mjs.html +1 -1
- package/docs/-_const.mjs.html +1 -1
- package/docs/ab2blob.mjs.html +1 -1
- package/docs/ab2u8arr.mjs.html +1 -1
- package/docs/addExcelWorksheetFromData.mjs.html +1 -1
- package/docs/addMonth.mjs.html +1 -1
- package/docs/aes2str.mjs.html +1 -1
- package/docs/alive.mjs.html +1 -1
- package/docs/arr2dt.mjs.html +1 -1
- package/docs/arrAccum.mjs.html +1 -1
- package/docs/arrAdd.mjs.html +1 -1
- package/docs/arrAt.mjs.html +1 -1
- package/docs/arrDiff.mjs.html +1 -1
- package/docs/arrFilterByKeywords.mjs.html +1 -1
- package/docs/arrFilterByNum.mjs.html +1 -1
- package/docs/arrFilterByPnum.mjs.html +1 -1
- package/docs/arrFilterByPnumAndToLog.mjs.html +1 -1
- package/docs/arrFind.mjs.html +1 -1
- package/docs/arrGroupByMaxmin.mjs.html +1 -1
- package/docs/arrHas.mjs.html +1 -1
- package/docs/arrInsert.mjs.html +1 -1
- package/docs/arrLookupByMaxmin.mjs.html +1 -1
- package/docs/arrMax.mjs.html +1 -1
- package/docs/arrMin.mjs.html +1 -1
- package/docs/arrMulti.mjs.html +1 -1
- package/docs/arrNorm.mjs.html +1 -1
- package/docs/arrProduct.mjs.html +1 -1
- package/docs/arrPull.mjs.html +1 -1
- package/docs/arrPullAt.mjs.html +1 -1
- package/docs/arrReduce.mjs.html +1 -1
- package/docs/arrReverse.mjs.html +1 -1
- package/docs/arrSep.mjs.html +1 -1
- package/docs/arrSort.mjs.html +1 -1
- package/docs/asind.mjs.html +1 -1
- package/docs/atan2d.mjs.html +1 -1
- package/docs/atand.mjs.html +1 -1
- package/docs/attstr.mjs.html +1 -1
- package/docs/b642obj.mjs.html +1 -1
- package/docs/b642str.mjs.html +1 -1
- package/docs/b642u16arr.mjs.html +1 -1
- package/docs/b642u8arr.mjs.html +1 -1
- package/docs/binstr.mjs.html +1 -1
- package/docs/blob2ab.mjs.html +1 -1
- package/docs/blob2b64.mjs.html +1 -1
- package/docs/blob2str.mjs.html +1 -1
- package/docs/blob2u8arr.mjs.html +1 -1
- package/docs/blobs2b64s.mjs.html +1 -1
- package/docs/blobs2u8arrs.mjs.html +1 -1
- package/docs/bodyLog.mjs.html +1 -1
- package/docs/browserView.mjs.html +1 -1
- package/docs/browserViewFromBlob.mjs.html +1 -1
- package/docs/browserViewFromText.mjs.html +1 -1
- package/docs/browserViewFromU8arr.mjs.html +1 -1
- package/docs/bs2u8arr.mjs.html +1 -1
- package/docs/bufRead.mjs.html +1 -1
- package/docs/bufReadDbl.mjs.html +1 -1
- package/docs/bufWrite.mjs.html +1 -1
- package/docs/bufWriteDbl.mjs.html +1 -1
- package/docs/cache.mjs.html +1 -1
- package/docs/cacheBd.mjs.html +1 -1
- package/docs/cacheSt.mjs.html +1 -1
- package/docs/cbol.mjs.html +1 -1
- package/docs/cdbl.mjs.html +1 -1
- package/docs/ceil.mjs.html +1 -1
- package/docs/cfilesize.mjs.html +1 -1
- package/docs/cfinancial.mjs.html +1 -1
- package/docs/cint.mjs.html +1 -1
- package/docs/clearXSS.mjs.html +1 -1
- package/docs/clearXSSDyn.mjs.html +1 -1
- package/docs/color.mjs.html +1 -1
- package/docs/composeToTree.mjs.html +1 -1
- package/docs/convertToTree.mjs.html +1 -1
- package/docs/copyStrToClipboard.mjs.html +1 -1
- package/docs/cosd.mjs.html +1 -1
- package/docs/cotd.mjs.html +1 -1
- package/docs/createExcelWorkbook.mjs.html +1 -1
- package/docs/cscd.mjs.html +1 -1
- package/docs/cstr.mjs.html +1 -1
- package/docs/day2cht.mjs.html +1 -1
- package/docs/debounce.mjs.html +1 -1
- package/docs/delay.mjs.html +1 -1
- package/docs/dig.mjs.html +1 -1
- package/docs/digExp.mjs.html +1 -1
- package/docs/domAlert.mjs.html +1 -1
- package/docs/domAppend.mjs.html +1 -1
- package/docs/domCancelEvent.mjs.html +1 -1
- package/docs/domCompareText.mjs.html +1 -1
- package/docs/domConvertToPic.mjs.html +1 -1
- package/docs/domConvertToPicDyn.mjs.html +1 -1
- package/docs/domDetect.mjs.html +1 -1
- package/docs/domDrag.mjs.html +1 -1
- package/docs/domDragBarAndScroll.mjs.html +476 -333
- package/docs/domDragPreview.mjs.html +1 -1
- package/docs/domDropFiles.mjs.html +1 -1
- package/docs/domElementsFromPoint.mjs.html +1 -1
- package/docs/domFadeIn.mjs.html +1 -1
- package/docs/domFadeOut.mjs.html +1 -1
- package/docs/domFind.mjs.html +1 -1
- package/docs/domFinds.mjs.html +1 -1
- package/docs/domGetAttr.mjs.html +1 -1
- package/docs/domGetBoudRect.mjs.html +1 -1
- package/docs/domGetBoudRectRefSelf.mjs.html +1 -1
- package/docs/domGetFileAccept.mjs.html +1 -1
- package/docs/domGetFiles.mjs.html +1 -1
- package/docs/domGetParents.mjs.html +1 -1
- package/docs/domGetPointFromEvent.mjs.html +1 -1
- package/docs/domGetScrollableParent.mjs.html +1 -1
- package/docs/domGetWindowSize.mjs.html +1 -1
- package/docs/domIsClientXYIn.mjs.html +1 -1
- package/docs/domIsFocused.mjs.html +1 -1
- package/docs/domIsStable.mjs.html +1 -1
- package/docs/domIsVisible.mjs.html +1 -1
- package/docs/domPrepend.mjs.html +1 -1
- package/docs/domRemove.mjs.html +1 -1
- package/docs/domRenderMarkdownMind.mjs.html +1 -1
- package/docs/domRenderMarkdownMindDyn.mjs.html +1 -1
- package/docs/domRipple.mjs.html +1 -1
- package/docs/domScrollIntoView.mjs.html +1 -1
- package/docs/domScrollTo.mjs.html +1 -1
- package/docs/domSetAttr.mjs.html +1 -1
- package/docs/domShowImages.mjs.html +1 -1
- package/docs/domShowImagesDyn.mjs.html +1 -1
- package/docs/domShowInputAndGetFiles.mjs.html +1 -1
- package/docs/domShowInputAndGetFilesU8Arrs.mjs.html +1 -1
- package/docs/domShowInputDatatime.mjs.html +1 -1
- package/docs/domTooltip.mjs.html +1 -1
- package/docs/domTriggerEvent.mjs.html +1 -1
- package/docs/domVirtualCreate.mjs.html +1 -1
- package/docs/domVirtualCreateQueue.mjs.html +1 -1
- package/docs/downloadExcelFileFromData.mjs.html +1 -1
- package/docs/downloadExcelFileFromDataDyn.mjs.html +1 -1
- package/docs/downloadFileFromB64.mjs.html +1 -1
- package/docs/downloadFileFromBlob.mjs.html +1 -1
- package/docs/downloadFileFromText.mjs.html +1 -1
- package/docs/downloadFileFromU8Arr.mjs.html +1 -1
- package/docs/dtmapping.mjs.html +1 -1
- package/docs/dtpick.mjs.html +1 -1
- package/docs/estimateTicks.mjs.html +1 -1
- package/docs/evEmit.mjs.html +1 -1
- package/docs/evEmitDelay.mjs.html +1 -1
- package/docs/evem.mjs.html +1 -1
- package/docs/examples/ex-blob2b64.html +1 -1
- package/docs/examples/ex-clearXSS.html +1 -1
- package/docs/examples/ex-clearXSSDyn.html +1 -1
- package/docs/examples/ex-colorConverter.html +1 -1
- package/docs/examples/ex-domCompareText.html +1 -1
- package/docs/examples/ex-domConvertToPic.html +1 -1
- package/docs/examples/ex-domDrag.html +1 -1
- package/docs/examples/ex-domDropFiles.html +1 -1
- package/docs/examples/ex-domIsVisible.html +1 -1
- package/docs/examples/ex-domRenderMarkdownMind.html +1 -1
- package/docs/examples/ex-domRenderMarkdownMindDyn.html +1 -1
- package/docs/examples/ex-domRipple.html +1 -1
- package/docs/examples/ex-domScrollIntoView.html +1 -1
- package/docs/examples/ex-domScrollTo.html +1 -1
- package/docs/examples/ex-domShowImages.html +1 -1
- package/docs/examples/ex-domShowImagesDyn.html +1 -1
- package/docs/examples/ex-domShowInputAndGetFiles.html +1 -1
- package/docs/examples/ex-domTooltip.html +1 -1
- package/docs/examples/ex-getFileHash.html +1 -1
- package/docs/examples/ex-getUserAgent.html +1 -1
- package/docs/examples/ex-getUserAgentDyn.html +1 -1
- package/docs/examples/ex-html2str.html +1 -1
- package/docs/examples/ex-obj2u8arr-u8arr2obj.html +1 -1
- package/docs/examples/ex-openLink.html +1 -1
- package/docs/examples/ex-str2aes-aes2str.html +1 -1
- package/docs/examples/ex-str2b64-b642str.html +1 -1
- package/docs/examples/ex-strFindFuzz.html +1 -1
- package/docs/examples/ex-timeTZ2expire-timeTZ2past.html +1 -1
- package/docs/examples/ex-xlsx.html +1 -1
- package/docs/execCli.mjs.html +1 -1
- package/docs/execCliCore.mjs.html +1 -1
- package/docs/execCliJsonRpc.mjs.html +1 -1
- package/docs/execCliSession.mjs.html +1 -1
- package/docs/execJsCode.mjs.html +1 -1
- package/docs/execProcess.mjs.html +1 -1
- package/docs/execProcessKillPid.mjs.html +1 -1
- package/docs/filepathToTree.mjs.html +1 -1
- package/docs/files2data.mjs.html +1 -1
- package/docs/flattenToConn.mjs.html +1 -1
- package/docs/flattenTree.mjs.html +1 -1
- package/docs/floor.mjs.html +1 -1
- package/docs/fsBuildReadStreamText.mjs.html +1 -1
- package/docs/fsBuildWriteStreamText.mjs.html +1 -1
- package/docs/fsCleanFolder.mjs.html +1 -1
- package/docs/fsCleanFolderCore.mjs.html +1 -1
- package/docs/fsCopyFile.mjs.html +1 -1
- package/docs/fsCopyFileCore.mjs.html +1 -1
- package/docs/fsCopyFolder.mjs.html +1 -1
- package/docs/fsCopyFolderCore.mjs.html +1 -1
- package/docs/fsCreateFile.mjs.html +1 -1
- package/docs/fsCreateFileCore.mjs.html +1 -1
- package/docs/fsCreateFolder.mjs.html +1 -1
- package/docs/fsCreateFolderCore.mjs.html +1 -1
- package/docs/fsDeleteFile.mjs.html +1 -1
- package/docs/fsDeleteFileCore.mjs.html +1 -1
- package/docs/fsDeleteFileSafe.mjs.html +1 -1
- package/docs/fsDeleteFileSafeCore.mjs.html +1 -1
- package/docs/fsDeleteFolder.mjs.html +1 -1
- package/docs/fsDeleteFolderCore.mjs.html +1 -1
- package/docs/fsDeleteFolderSafe.mjs.html +1 -1
- package/docs/fsDeleteFolderSafeCore.mjs.html +1 -1
- package/docs/fsDownloadFile.mjs.html +1 -1
- package/docs/fsDownloadFileCore.mjs.html +1 -1
- package/docs/fsEvem.mjs.html +1 -1
- package/docs/fsExists.mjs.html +1 -1
- package/docs/fsExistsCore.mjs.html +1 -1
- package/docs/fsGetFileBasicHash.mjs.html +1 -1
- package/docs/fsGetFileBasicHashCore.mjs.html +1 -1
- package/docs/fsGetFileHash.mjs.html +1 -1
- package/docs/fsGetFileHashCore.mjs.html +1 -1
- package/docs/fsGetFileXxHash.mjs.html +1 -1
- package/docs/fsGetFileXxHashCore.mjs.html +1 -1
- package/docs/fsGetFilesInFolder.mjs.html +1 -1
- package/docs/fsGetFilesInFolderCore.mjs.html +1 -1
- package/docs/fsGetFilesWithHashInFolder.mjs.html +1 -1
- package/docs/fsGetFilesWithHashInFolderCore.mjs.html +1 -1
- package/docs/fsGetFolderBasicHash.mjs.html +1 -1
- package/docs/fsGetFolderBasicHashCore.mjs.html +1 -1
- package/docs/fsGetFoldersInFolder.mjs.html +1 -1
- package/docs/fsGetFoldersInFolderCore.mjs.html +1 -1
- package/docs/fsIsFile.mjs.html +1 -1
- package/docs/fsIsFileCore.mjs.html +1 -1
- package/docs/fsIsFolder.mjs.html +1 -1
- package/docs/fsIsFolderCore.mjs.html +1 -1
- package/docs/fsMergeFiles.mjs.html +1 -1
- package/docs/fsMergeFilesCore.mjs.html +1 -1
- package/docs/fsReadJson.mjs.html +1 -1
- package/docs/fsReadText.mjs.html +1 -1
- package/docs/fsReadTextCore.mjs.html +1 -1
- package/docs/fsRenameFile.mjs.html +1 -1
- package/docs/fsRenameFileCore.mjs.html +1 -1
- package/docs/fsRenameFolder.mjs.html +1 -1
- package/docs/fsRenameFolderCore.mjs.html +1 -1
- package/docs/fsSrlog.mjs.html +1 -1
- package/docs/fsSyncFolder.mjs.html +1 -1
- package/docs/fsSyncFolderCore.mjs.html +1 -1
- package/docs/fsTask.mjs.html +1 -1
- package/docs/fsTaskCp.mjs.html +1 -1
- package/docs/fsTreeFolder.mjs.html +1 -1
- package/docs/fsTreeFolderCore.mjs.html +1 -1
- package/docs/fsTreeFolderWithHash.mjs.html +1 -1
- package/docs/fsTreeFolderWithHashCore.mjs.html +1 -1
- package/docs/fsWatchFile.mjs.html +1 -1
- package/docs/fsWatchFolder.mjs.html +1 -1
- package/docs/fsWriteJson.mjs.html +1 -1
- package/docs/fsWriteText.mjs.html +1 -1
- package/docs/fsWriteTextCore.mjs.html +1 -1
- package/docs/genGradientColor.mjs.html +1 -1
- package/docs/genID.mjs.html +1 -1
- package/docs/genIDSeq.mjs.html +1 -1
- package/docs/genPm.mjs.html +1 -1
- package/docs/getBufferSize.mjs.html +1 -1
- package/docs/getClearPathName.mjs.html +1 -1
- package/docs/getCsvStrFromData.mjs.html +1 -1
- package/docs/getDataFromExcelFileU8Arr.mjs.html +1 -1
- package/docs/getDataFromExcelFileU8ArrDyn.mjs.html +1 -1
- package/docs/getEnv.mjs.html +1 -1
- package/docs/getErrorMessage.mjs.html +1 -1
- package/docs/getExcelU8ArrFromData.mjs.html +1 -1
- package/docs/getExcelU8ArrFromWorkbook.mjs.html +1 -1
- package/docs/getExcelWorkbookFromData.mjs.html +1 -1
- package/docs/getExcelWorkbookFromWorksheet.mjs.html +1 -1
- package/docs/getExcelWorksheetFromData.mjs.html +1 -1
- package/docs/getFileAccept.mjs.html +1 -1
- package/docs/getFileBasicHash.mjs.html +1 -1
- package/docs/getFileHash.mjs.html +1 -1
- package/docs/getFileName.mjs.html +1 -1
- package/docs/getFileNameExt.mjs.html +1 -1
- package/docs/getFileTrueName.mjs.html +1 -1
- package/docs/getFileType.mjs.html +1 -1
- package/docs/getFileXxHash.mjs.html +1 -1
- package/docs/getGlobal.mjs.html +1 -1
- package/docs/getObjSize.mjs.html +1 -1
- package/docs/getPathParent.mjs.html +1 -1
- package/docs/getTimeBetween.mjs.html +1 -1
- package/docs/getTimeFromUnit.mjs.html +1 -1
- package/docs/getTimeHp.mjs.html +1 -1
- package/docs/getTimeObject.mjs.html +1 -1
- package/docs/getTimeRange.mjs.html +1 -1
- package/docs/getUserAgent.mjs.html +1 -1
- package/docs/getUserAgentDyn.mjs.html +1 -1
- package/docs/getltdtkeys.mjs.html +1 -1
- package/docs/global.html +1 -1
- package/docs/haskey.mjs.html +1 -1
- package/docs/html2str.mjs.html +1 -1
- package/docs/htmlDecode.mjs.html +1 -1
- package/docs/htmlEncode.mjs.html +1 -1
- package/docs/importResources.mjs.html +1 -1
- package/docs/index.html +1 -1
- package/docs/interp1.mjs.html +1 -1
- package/docs/isDate.mjs.html +1 -1
- package/docs/isDev.mjs.html +1 -1
- package/docs/isEle.mjs.html +1 -1
- package/docs/isEleExistByID.mjs.html +1 -1
- package/docs/isEmail.mjs.html +1 -1
- package/docs/isErr.mjs.html +1 -1
- package/docs/isIE.mjs.html +1 -1
- package/docs/isNarrow.mjs.html +1 -1
- package/docs/isStrHasCapital.mjs.html +1 -1
- package/docs/isStrHasLetter.mjs.html +1 -1
- package/docs/isStrHasLowerCase.mjs.html +1 -1
- package/docs/isStrHasNumber.mjs.html +1 -1
- package/docs/isStrHasSpace.mjs.html +1 -1
- package/docs/isStrHasSymbol.mjs.html +1 -1
- package/docs/isUserIdentify.mjs.html +1 -1
- package/docs/isUserName.mjs.html +1 -1
- package/docs/isUserPw.mjs.html +1 -1
- package/docs/isWindow.mjs.html +1 -1
- package/docs/isab.mjs.html +1 -1
- package/docs/isarr.mjs.html +1 -1
- package/docs/isarr0.mjs.html +1 -1
- package/docs/isarr1.mjs.html +1 -1
- package/docs/isbigint.mjs.html +1 -1
- package/docs/isblob.mjs.html +42 -23
- package/docs/isbol.mjs.html +1 -1
- package/docs/isday.mjs.html +1 -1
- package/docs/isearr.mjs.html +1 -1
- package/docs/iseobj.mjs.html +1 -1
- package/docs/iser.mjs.html +1 -1
- package/docs/isernot.mjs.html +1 -1
- package/docs/isestr.mjs.html +1 -1
- package/docs/isfile.mjs.html +2 -2
- package/docs/isfun.mjs.html +1 -1
- package/docs/isint.mjs.html +1 -1
- package/docs/ismonth.mjs.html +1 -1
- package/docs/isn0int.mjs.html +1 -1
- package/docs/isn0num.mjs.html +1 -1
- package/docs/isnan.mjs.html +1 -1
- package/docs/isnbr.mjs.html +1 -1
- package/docs/isnint.mjs.html +1 -1
- package/docs/isnnum.mjs.html +1 -1
- package/docs/isnull.mjs.html +1 -1
- package/docs/isnum.mjs.html +1 -1
- package/docs/isobj.mjs.html +1 -1
- package/docs/isobj0.mjs.html +1 -1
- package/docs/isp0int.mjs.html +1 -1
- package/docs/isp0num.mjs.html +1 -1
- package/docs/ispint.mjs.html +1 -1
- package/docs/ispm.mjs.html +1 -1
- package/docs/ispnum.mjs.html +1 -1
- package/docs/isstr.mjs.html +1 -1
- package/docs/isstr0.mjs.html +1 -1
- package/docs/istime.mjs.html +1 -1
- package/docs/istimeTZ.mjs.html +1 -1
- package/docs/istimems.mjs.html +1 -1
- package/docs/istimemsTZ.mjs.html +1 -1
- package/docs/isu16arr.mjs.html +1 -1
- package/docs/isu8arr.mjs.html +1 -1
- package/docs/isundefined.mjs.html +1 -1
- package/docs/j2o.mjs.html +1 -1
- package/docs/judge.mjs.html +1 -1
- package/docs/keysmat2ltdt.mjs.html +1 -1
- package/docs/ltdt2vrtsable.mjs.html +1 -1
- package/docs/ltdtDiff.mjs.html +1 -1
- package/docs/ltdtDiffByKey.mjs.html +1 -1
- package/docs/ltdtkeys2mat.mjs.html +1 -1
- package/docs/ltdtkeysheads2mat.mjs.html +1 -1
- package/docs/ltdtmapping.mjs.html +1 -1
- package/docs/ltdtmerge.mjs.html +1 -1
- package/docs/ltdtpick.mjs.html +1 -1
- package/docs/ltdtpull.mjs.html +1 -1
- package/docs/mat2ltdt.mjs.html +1 -1
- package/docs/matConcat.mjs.html +1 -1
- package/docs/matDiff.mjs.html +1 -1
- package/docs/matat.mjs.html +1 -1
- package/docs/now2str.mjs.html +1 -1
- package/docs/now2strp.mjs.html +1 -1
- package/docs/nowDay2str.mjs.html +1 -1
- package/docs/nowms2str.mjs.html +1 -1
- package/docs/nowms2strp.mjs.html +1 -1
- package/docs/o2j.mjs.html +1 -1
- package/docs/obj2b64.mjs.html +1 -1
- package/docs/obj2pb64.mjs.html +1 -1
- package/docs/obj2str.mjs.html +1 -1
- package/docs/obj2stru8arr.mjs.html +1 -1
- package/docs/obj2u8arr.mjs.html +1 -1
- package/docs/objSortBy.mjs.html +1 -1
- package/docs/objSortByKeys.mjs.html +1 -1
- package/docs/oo.mjs.html +1 -1
- package/docs/openLink.mjs.html +1 -1
- package/docs/pb642obj.mjs.html +1 -1
- package/docs/pmChain.mjs.html +1 -1
- package/docs/pmConvertResolve.mjs.html +1 -1
- package/docs/pmDebounce.mjs.html +1 -1
- package/docs/pmHook.mjs.html +1 -1
- package/docs/pmHookReject.mjs.html +1 -1
- package/docs/pmIni.mjs.html +1 -1
- package/docs/pmInvResolve.mjs.html +1 -1
- package/docs/pmKeyMutex.mjs.html +1 -1
- package/docs/pmMap.mjs.html +1 -1
- package/docs/pmQueue.mjs.html +1 -1
- package/docs/pmSeries.mjs.html +1 -1
- package/docs/pmThrottle.mjs.html +1 -1
- package/docs/pmTimeout.mjs.html +1 -1
- package/docs/preciseNum.mjs.html +1 -1
- package/docs/pseudoRandom.mjs.html +1 -1
- package/docs/pseudoRandomIntRange.mjs.html +1 -1
- package/docs/pseudoRandomIntsNdpRange.mjs.html +1 -1
- package/docs/pseudoRandomIntsRange.mjs.html +1 -1
- package/docs/pseudoRandomRange.mjs.html +1 -1
- package/docs/pseudoRandomsRange.mjs.html +1 -1
- package/docs/queue.mjs.html +1 -1
- package/docs/random.mjs.html +1 -1
- package/docs/randomIntRange.mjs.html +1 -1
- package/docs/randomIntsNdpRange.mjs.html +1 -1
- package/docs/randomIntsRange.mjs.html +1 -1
- package/docs/randomRange.mjs.html +1 -1
- package/docs/randomsRange.mjs.html +1 -1
- package/docs/rang.mjs.html +1 -1
- package/docs/rep.mjs.html +1 -1
- package/docs/repObj.mjs.html +1 -1
- package/docs/replace.mjs.html +1 -1
- package/docs/replaceObj.mjs.html +1 -1
- package/docs/replacePlus.mjs.html +1 -1
- package/docs/round.mjs.html +1 -1
- package/docs/secd.mjs.html +1 -1
- package/docs/sep.mjs.html +1 -1
- package/docs/sepInt.mjs.html +1 -1
- package/docs/setIntervalHp.mjs.html +1 -1
- package/docs/setTimeoutHp.mjs.html +1 -1
- package/docs/sind.mjs.html +1 -1
- package/docs/split.mjs.html +1 -1
- package/docs/str2aes.mjs.html +1 -1
- package/docs/str2b64.mjs.html +1 -1
- package/docs/str2hint.mjs.html +1 -1
- package/docs/str2md5.mjs.html +1 -1
- package/docs/str2obj.mjs.html +1 -1
- package/docs/str2sha.mjs.html +1 -1
- package/docs/str2sha512.mjs.html +1 -1
- package/docs/str2u8arr.mjs.html +1 -1
- package/docs/strCompare.mjs.html +1 -1
- package/docs/strCompareForVersion.mjs.html +1 -1
- package/docs/strDiff.mjs.html +1 -1
- package/docs/strDiffToHtml.mjs.html +1 -1
- package/docs/strFindFuzz.mjs.html +1 -1
- package/docs/strFindSimilar.mjs.html +1 -1
- package/docs/strTruncate.mjs.html +1 -1
- package/docs/strdelleft.mjs.html +1 -1
- package/docs/strdelright.mjs.html +1 -1
- package/docs/strleft.mjs.html +1 -1
- package/docs/strmid.mjs.html +1 -1
- package/docs/strright.mjs.html +1 -1
- package/docs/stru8arr2obj.mjs.html +1 -1
- package/docs/tand.mjs.html +1 -1
- package/docs/time2day.mjs.html +1 -1
- package/docs/time2hour.mjs.html +1 -1
- package/docs/time2min.mjs.html +1 -1
- package/docs/timeTZ2day.mjs.html +1 -1
- package/docs/timeTZ2expire.mjs.html +1 -1
- package/docs/timeTZ2hour.mjs.html +1 -1
- package/docs/timeTZ2min.mjs.html +1 -1
- package/docs/timeTZ2past.mjs.html +1 -1
- package/docs/timems2day.mjs.html +1 -1
- package/docs/timems2hour.mjs.html +1 -1
- package/docs/timems2min.mjs.html +1 -1
- package/docs/timems2time.mjs.html +1 -1
- package/docs/timemsTZ2day.mjs.html +1 -1
- package/docs/timemsTZ2expire.mjs.html +1 -1
- package/docs/timemsTZ2hour.mjs.html +1 -1
- package/docs/timemsTZ2min.mjs.html +1 -1
- package/docs/timemsTZ2past.mjs.html +1 -1
- package/docs/timemsTZ2time.mjs.html +1 -1
- package/docs/treeObj.mjs.html +1 -1
- package/docs/treeToFilepath.mjs.html +1 -1
- package/docs/trim.mjs.html +1 -1
- package/docs/u16arr2b64.mjs.html +1 -1
- package/docs/u16arr2u8arr.mjs.html +1 -1
- package/docs/u8arr2ab.mjs.html +1 -1
- package/docs/u8arr2b64.mjs.html +1 -1
- package/docs/u8arr2blob.mjs.html +1 -1
- package/docs/u8arr2bs.mjs.html +1 -1
- package/docs/u8arr2obj.mjs.html +1 -1
- package/docs/u8arr2str.mjs.html +1 -1
- package/docs/u8arr2u16arr.mjs.html +1 -1
- package/docs/urlParse.mjs.html +1 -1
- package/docs/verifyValue.mjs.html +1 -1
- package/docs/waitFun.mjs.html +1 -1
- package/docs/wsemi.html +66 -11
- package/package.json +1 -1
- package/src/_getDiff2Html.mjs +44 -38
- package/src/domDragBarAndScroll.mjs +475 -332
- package/src/isblob.mjs +41 -22
- package/src/isfile.mjs +1 -1
- package/test/_getDiff2Html.test.mjs +56 -0
- package/test/blob2ab.test.mjs +6 -0
- package/test/domDragBarAndScroll.test.mjs +1163 -11
- package/test/isblob.test.mjs +28 -1
- package/test/isfile.test.mjs +11 -1
- package/toolfp/dist/wsemip.umd.js +2 -2
- package/toolg/gDistRollup.mjs +2 -0
- package/toolparfor/dist/wpf.umd.js +1 -1
- package/toolparfor/dist/wpf_pmSeries.umd.js +1 -1
|
@@ -1,11 +1,1163 @@
|
|
|
1
|
-
import assert from 'assert'
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
import assert from 'assert'
|
|
2
|
+
import domDragBarAndScroll from '../src/domDragBarAndScroll.mjs'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
//本函數於建構時即存取window與document, nodejs無此二者, 故以假物件覆蓋全域再建構
|
|
6
|
+
// 假元素只記錄addEventListener之處理器, 由測試直接派發合成事件, 可精確斷言事件種類/次數/順序,
|
|
7
|
+
// 以及preventDefault與stopPropagation是否被呼叫, 這些於真瀏覽器只能間接觀察
|
|
8
|
+
// bar為panel之子節點(六個消費端皆如此), 故dispatch會逐層往上跑以模擬冒泡
|
|
9
|
+
// lastEvent保留最近一次派發之事件物件, 供處理器拋錯(dispatch無回傳)時仍可檢查該事件
|
|
10
|
+
class Ele {
|
|
11
|
+
constructor(name, parent) {
|
|
12
|
+
this.name = name
|
|
13
|
+
this.parent = parent || null
|
|
14
|
+
this.hs = {}
|
|
15
|
+
this.lastEvent = null
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
addEventListener(type, fn, opts) {
|
|
19
|
+
if (!this.hs[type]) {
|
|
20
|
+
this.hs[type] = []
|
|
21
|
+
}
|
|
22
|
+
this.hs[type].push({ fn, opts })
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
removeEventListener(type, fn) {
|
|
26
|
+
if (this.hs[type]) {
|
|
27
|
+
this.hs[type] = this.hs[type].filter((h) => h.fn !== fn)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
nListeners() {
|
|
32
|
+
return Object.keys(this.hs).reduce((n, k) => n + this.hs[k].length, 0)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
types() {
|
|
36
|
+
return Object.keys(this.hs).filter((k) => this.hs[k].length > 0).sort()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
dispatch(type, init = {}) {
|
|
40
|
+
let e = {
|
|
41
|
+
type,
|
|
42
|
+
cancelable: true,
|
|
43
|
+
...init,
|
|
44
|
+
defaultPrevented: false,
|
|
45
|
+
propagationStopped: false,
|
|
46
|
+
}
|
|
47
|
+
e.preventDefault = () => {
|
|
48
|
+
e.defaultPrevented = true
|
|
49
|
+
}
|
|
50
|
+
e.stopPropagation = () => {
|
|
51
|
+
e.propagationStopped = true
|
|
52
|
+
}
|
|
53
|
+
this.lastEvent = e
|
|
54
|
+
let node = this
|
|
55
|
+
while (node !== null) {
|
|
56
|
+
for (let h of (node.hs[type] || []).slice()) {
|
|
57
|
+
h.fn(e)
|
|
58
|
+
}
|
|
59
|
+
if (e.propagationStopped) {
|
|
60
|
+
break
|
|
61
|
+
}
|
|
62
|
+
node = node.parent
|
|
63
|
+
}
|
|
64
|
+
return e
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
//tc, 建立假觸點
|
|
70
|
+
function tc(id, x, y = 0) {
|
|
71
|
+
return { identifier: id, clientX: x, clientY: y }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
//te, 建立假觸控事件之內容; touches為當前全部接觸點, changedTouches為本次變動者
|
|
76
|
+
function te(touches, changed) {
|
|
77
|
+
return { touches, changedTouches: (changed !== undefined) ? changed : touches }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
//mk, 建立一組panel/bar/window/document與das, 並記錄其發出之事件
|
|
82
|
+
// seq只記clientX供比對事件序列, full記完整事件名與參數供比對座標內容
|
|
83
|
+
function mk(opt) {
|
|
84
|
+
let win = new Ele('window')
|
|
85
|
+
let doc = new Ele('document')
|
|
86
|
+
globalThis.window = win
|
|
87
|
+
globalThis.document = doc
|
|
88
|
+
let panel = new Ele('panel')
|
|
89
|
+
let bar = new Ele('bar', panel)
|
|
90
|
+
let seq = []
|
|
91
|
+
let full = []
|
|
92
|
+
let das = domDragBarAndScroll(panel, bar, opt)
|
|
93
|
+
das.on('pressBar', (...args) => {
|
|
94
|
+
seq.push(`press:${args[0].clientX}`)
|
|
95
|
+
full.push(['pressBar', ...args])
|
|
96
|
+
})
|
|
97
|
+
das.on('dragBar', (...args) => {
|
|
98
|
+
seq.push(`drag:${args[0].clientX}`)
|
|
99
|
+
full.push(['dragBar', ...args])
|
|
100
|
+
})
|
|
101
|
+
das.on('freeBar', (...args) => {
|
|
102
|
+
seq.push('free')
|
|
103
|
+
full.push(['freeBar', ...args])
|
|
104
|
+
})
|
|
105
|
+
das.on('scrollPanel', (...args) => {
|
|
106
|
+
seq.push(`scroll:${args[0].ratioY}/${args[0].ratioX}`)
|
|
107
|
+
full.push(['scrollPanel', ...args])
|
|
108
|
+
})
|
|
109
|
+
return { panel, bar, win, doc, das, seq, full }
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
//probeState, 以不改變狀態之探測事件讀出目前之鎖: 滑鼠移動(buttons=1)只在barMouse時發dragBar,
|
|
114
|
+
// 於bar派發指定identifier之touchmove, 在barTouch時發絕對座標, 在panelTouch時(冒泡至panel)發縮放座標
|
|
115
|
+
function probeState(t) {
|
|
116
|
+
let rs = []
|
|
117
|
+
let n = t.seq.length
|
|
118
|
+
t.win.dispatch('mousemove', { clientX: 77, buttons: 1 })
|
|
119
|
+
if (t.seq.length > n) {
|
|
120
|
+
rs.push('barMouse')
|
|
121
|
+
}
|
|
122
|
+
for (let id of [1, 2]) {
|
|
123
|
+
n = t.seq.length
|
|
124
|
+
t.bar.dispatch('touchmove', te([tc(id, 77)], [tc(id, 77)]))
|
|
125
|
+
let got = t.seq.slice(n).join(',')
|
|
126
|
+
if (got === 'drag:77') {
|
|
127
|
+
rs.push(`barTouch#${id}`)
|
|
128
|
+
}
|
|
129
|
+
else if (got === 'drag:-77') {
|
|
130
|
+
rs.push(`panelTouch#${id}`)
|
|
131
|
+
}
|
|
132
|
+
else if (got !== '') {
|
|
133
|
+
rs.push(`unexpected(${got})`)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return rs.length > 0 ? rs.join('+') : 'idle'
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
//STATES, 狀態機之四個前置狀態; 起手指identifier為1, 起手座標10
|
|
141
|
+
let STATES = {
|
|
142
|
+
idle: () => {},
|
|
143
|
+
barMouse: (t) => {
|
|
144
|
+
t.bar.dispatch('mousedown', { button: 0, clientX: 10 })
|
|
145
|
+
},
|
|
146
|
+
barTouch: (t) => {
|
|
147
|
+
t.bar.dispatch('touchstart', te([tc(1, 10)]))
|
|
148
|
+
},
|
|
149
|
+
panelTouch: (t) => {
|
|
150
|
+
t.panel.dispatch('touchstart', te([tc(1, 10)]))
|
|
151
|
+
},
|
|
152
|
+
}
|
|
153
|
+
let STATE_LABEL = { idle: 'idle', barMouse: 'barMouse', barTouch: 'barTouch#1', panelTouch: 'panelTouch#1' }
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
//TOUCH_EVENTS, 觸控事件十一種, bar與panel共用同一組定義
|
|
157
|
+
// 起手指仍在者其touches含identifier=1, 已不在者不含; 同identifier再落下代表原起手指早已離開
|
|
158
|
+
let TOUCH_EVENTS = {
|
|
159
|
+
'touchstart of another finger while the starting finger is still down': ['touchstart', te([tc(1, 10), tc(2, 20)], [tc(2, 20)])],
|
|
160
|
+
'touchstart of another finger after the starting finger is gone': ['touchstart', te([tc(2, 20)], [tc(2, 20)])],
|
|
161
|
+
'touchstart reusing the identifier of the starting finger': ['touchstart', te([tc(1, 20)], [tc(1, 20)])],
|
|
162
|
+
'touchstart without any touch point': ['touchstart', te([], [])],
|
|
163
|
+
'touchmove of the starting finger': ['touchmove', te([tc(1, 20)], [tc(1, 20)])],
|
|
164
|
+
'touchmove of another finger': ['touchmove', te([tc(1, 10), tc(2, 20)], [tc(2, 20)])],
|
|
165
|
+
'touchend of the starting finger': ['touchend', te([], [tc(1, 20)])],
|
|
166
|
+
'touchend of another finger': ['touchend', te([tc(1, 10)], [tc(2, 20)])],
|
|
167
|
+
'touchcancel of the starting finger': ['touchcancel', { ...te([], [tc(1, 20)]), cancelable: false }],
|
|
168
|
+
'touchcancel of another finger': ['touchcancel', { ...te([tc(1, 10)], [tc(2, 20)]), cancelable: false }],
|
|
169
|
+
'touchcancel without any touch point': ['touchcancel', { ...te([], []), cancelable: false }],
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
//TABLE, 狀態×事件之全表; 每格為'發出之事件序列|事件後之狀態', same表狀態不變
|
|
174
|
+
// 欄序為 idle, barMouse, barTouch, panelTouch
|
|
175
|
+
// 第二欄為函數者直接執行; 為'bar'或'panel'者, 以標題去掉該前綴後查TOUCH_EVENTS並派發於該元素
|
|
176
|
+
let S4 = (v) => [v, v, v, v]
|
|
177
|
+
let TAKE_OVER_BY_MOUSE = ['press:20|barMouse', 'free,press:20|barMouse', 'free,press:20|barMouse', 'free,press:20|barMouse']
|
|
178
|
+
let FREE_IF_LOCKED = ['|same', 'free|idle', 'free|idle', 'free|idle']
|
|
179
|
+
let TABLE = [
|
|
180
|
+
|
|
181
|
+
//--- 滑鼠 ---
|
|
182
|
+
['bar mousedown with the primary button', (t) => t.bar.dispatch('mousedown', { button: 0, clientX: 20 }), TAKE_OVER_BY_MOUSE],
|
|
183
|
+
['bar mousedown without a button property', (t) => t.bar.dispatch('mousedown', { clientX: 20 }), TAKE_OVER_BY_MOUSE],
|
|
184
|
+
['bar mousedown with the middle button', (t) => t.bar.dispatch('mousedown', { button: 1, clientX: 20 }), S4('|same')],
|
|
185
|
+
['bar mousedown with the right button', (t) => t.bar.dispatch('mousedown', { button: 2, clientX: 20 }), S4('|same')],
|
|
186
|
+
['bar mouseup', (t) => t.bar.dispatch('mouseup', { button: 0 }), FREE_IF_LOCKED],
|
|
187
|
+
['window mouseup', (t) => t.win.dispatch('mouseup', { button: 0 }), FREE_IF_LOCKED],
|
|
188
|
+
['window mouseup of the right button', (t) => t.win.dispatch('mouseup', { button: 2 }), FREE_IF_LOCKED],
|
|
189
|
+
['window mousemove with a button held', (t) => t.win.dispatch('mousemove', { clientX: 20, buttons: 1 }),
|
|
190
|
+
['|same', 'drag:20|same', '|same', '|same']],
|
|
191
|
+
['window mousemove without a buttons property', (t) => t.win.dispatch('mousemove', { clientX: 20 }),
|
|
192
|
+
['|same', 'drag:20|same', '|same', '|same']],
|
|
193
|
+
['window mousemove with no button held', (t) => t.win.dispatch('mousemove', { clientX: 20, buttons: 0 }),
|
|
194
|
+
['|same', 'free|idle', '|same', '|same']],
|
|
195
|
+
|
|
196
|
+
//--- bar 觸控(事件派發於bar, 會冒泡至panel) ---
|
|
197
|
+
['bar touchstart of another finger while the starting finger is still down', 'bar',
|
|
198
|
+
['press:20|barTouch#2', '|same', '|same', '|same']],
|
|
199
|
+
['bar touchstart of another finger after the starting finger is gone', 'bar',
|
|
200
|
+
['press:20|barTouch#2', '|same', 'free,press:20|barTouch#2', 'free,press:20|barTouch#2']],
|
|
201
|
+
['bar touchstart reusing the identifier of the starting finger', 'bar',
|
|
202
|
+
['press:20|barTouch#1', '|same', 'free,press:20|barTouch#1', 'free,press:20|barTouch#1']],
|
|
203
|
+
['bar touchstart without any touch point', 'bar', S4('|same')],
|
|
204
|
+
['bar touchmove of the starting finger', 'bar',
|
|
205
|
+
['|same', '|same', 'drag:20|same', 'drag:-20|same']],
|
|
206
|
+
['bar touchmove of another finger', 'bar', S4('|same')],
|
|
207
|
+
['bar touchend of the starting finger', 'bar',
|
|
208
|
+
['|same', '|same', 'free|idle', 'free|idle']],
|
|
209
|
+
['bar touchend of another finger', 'bar', S4('|same')],
|
|
210
|
+
['bar touchcancel of the starting finger', 'bar',
|
|
211
|
+
['|same', '|same', 'free|idle', 'free|idle']],
|
|
212
|
+
['bar touchcancel of another finger', 'bar', S4('|same')],
|
|
213
|
+
['bar touchcancel without any touch point', 'bar',
|
|
214
|
+
['|same', '|same', 'free|idle', 'free|idle']],
|
|
215
|
+
|
|
216
|
+
//--- panel 觸控(事件派發於panel, 不經bar) ---
|
|
217
|
+
['panel touchstart of another finger while the starting finger is still down', 'panel',
|
|
218
|
+
['press:-20|panelTouch#2', '|same', '|same', '|same']],
|
|
219
|
+
['panel touchstart of another finger after the starting finger is gone', 'panel',
|
|
220
|
+
['press:-20|panelTouch#2', '|same', 'free,press:-20|panelTouch#2', 'free,press:-20|panelTouch#2']],
|
|
221
|
+
['panel touchstart reusing the identifier of the starting finger', 'panel',
|
|
222
|
+
['press:-20|panelTouch#1', '|same', 'free,press:-20|panelTouch#1', 'free,press:-20|panelTouch#1']],
|
|
223
|
+
['panel touchstart without any touch point', 'panel', S4('|same')],
|
|
224
|
+
['panel touchmove of the starting finger', 'panel',
|
|
225
|
+
['|same', '|same', '|same', 'drag:-20|same']],
|
|
226
|
+
['panel touchmove of another finger', 'panel', S4('|same')],
|
|
227
|
+
['panel touchend of the starting finger', 'panel',
|
|
228
|
+
['|same', '|same', '|same', 'free|idle']],
|
|
229
|
+
['panel touchend of another finger', 'panel', S4('|same')],
|
|
230
|
+
['panel touchcancel of the starting finger', 'panel',
|
|
231
|
+
['|same', '|same', '|same', 'free|idle']],
|
|
232
|
+
['panel touchcancel of another finger', 'panel', S4('|same')],
|
|
233
|
+
['panel touchcancel without any touch point', 'panel',
|
|
234
|
+
['|same', '|same', '|same', 'free|idle']],
|
|
235
|
+
|
|
236
|
+
//--- 滾輪與清理 ---
|
|
237
|
+
['panel wheel', (t) => t.panel.dispatch('wheel', { deltaY: -120, deltaX: 60 }), S4('scroll:-1/1|same')],
|
|
238
|
+
['clear', (t) => t.das.clear(), S4('|idle')],
|
|
239
|
+
|
|
240
|
+
]
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
//runCell, 建立前置狀態後執行事件, 回傳'發出之事件序列|事件後之狀態'
|
|
244
|
+
function runCell(opt, state, act) {
|
|
245
|
+
let t = mk(opt)
|
|
246
|
+
STATES[state](t)
|
|
247
|
+
let n = t.seq.length
|
|
248
|
+
if (typeof act === 'function') {
|
|
249
|
+
act(t)
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
let [type, init] = TOUCH_EVENTS[act.name]
|
|
253
|
+
t[act.on].dispatch(type, init)
|
|
254
|
+
}
|
|
255
|
+
let emitted = t.seq.slice(n).join(',')
|
|
256
|
+
return `${emitted}|${probeState(t)}`
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
describe(`domDragBarAndScroll`, function() {
|
|
261
|
+
|
|
262
|
+
after(function() {
|
|
263
|
+
delete globalThis.window
|
|
264
|
+
delete globalThis.document
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
//--- 狀態×事件全表 ---
|
|
268
|
+
|
|
269
|
+
describe(`state machine, every state against every event`, function() {
|
|
270
|
+
let names = Object.keys(STATES)
|
|
271
|
+
for (let [title, act, expects] of TABLE) {
|
|
272
|
+
let a = act
|
|
273
|
+
if (typeof act === 'string') {
|
|
274
|
+
a = { on: act, name: title.slice(act.length + 1) }
|
|
275
|
+
}
|
|
276
|
+
names.forEach((state, i) => {
|
|
277
|
+
let exp = expects[i].replace('|same', `|${STATE_LABEL[state]}`)
|
|
278
|
+
it(`should handle [${state}] x [${title}] as [${exp}]`, function() {
|
|
279
|
+
assert.strict.deepStrictEqual(runCell({}, state, a), exp)
|
|
280
|
+
})
|
|
281
|
+
})
|
|
282
|
+
}
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
describe(`state machine, panel touch events with useTouchDragForPanel false`, function() {
|
|
286
|
+
//c4 現有八個呼叫點皆傳false, 此時panel之觸控事件一律不得影響任何狀態
|
|
287
|
+
for (let name of Object.keys(TOUCH_EVENTS)) {
|
|
288
|
+
for (let state of ['idle', 'barMouse', 'barTouch']) {
|
|
289
|
+
let exp = `|${STATE_LABEL[state]}`
|
|
290
|
+
it(`should handle [${state}] x [panel ${name}] as [${exp}]`, function() {
|
|
291
|
+
assert.strict.deepStrictEqual(runCell({ useTouchDragForPanel: false }, state, { on: 'panel', name }), exp)
|
|
292
|
+
})
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
//--- 滑鼠通道 ---
|
|
298
|
+
|
|
299
|
+
it(`should emit pressBar, dragBar and freeBar for a primary button drag`, function() {
|
|
300
|
+
//a1 基準路徑
|
|
301
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
302
|
+
t.bar.dispatch('mousedown', { button: 0, clientX: 130 })
|
|
303
|
+
t.win.dispatch('mousemove', { clientX: 250, buttons: 1 })
|
|
304
|
+
t.win.dispatch('mousemove', { clientX: 300, buttons: 1 })
|
|
305
|
+
t.win.dispatch('mouseup', { button: 0 })
|
|
306
|
+
assert.strict.deepStrictEqual(t.seq, ['press:130', 'drag:250', 'drag:300', 'free'])
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
it(`should ignore the middle and right button on mousedown`, function() {
|
|
310
|
+
//a2、a3 中鍵與右鍵不得上鎖
|
|
311
|
+
for (let button of [1, 2]) {
|
|
312
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
313
|
+
t.bar.dispatch('mousedown', { button, clientX: 130 })
|
|
314
|
+
t.win.dispatch('mousemove', { clientX: 300, buttons: 4 })
|
|
315
|
+
assert.strict.deepStrictEqual(t.seq, [], `button=${button}`)
|
|
316
|
+
}
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
it(`should still lock for a synthetic event without a button property`, function() {
|
|
320
|
+
//n12 合成事件之button為undefined, 以'button' in e判定故維持原本可上鎖之行為
|
|
321
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
322
|
+
t.bar.dispatch('mousedown', { clientX: 130 })
|
|
323
|
+
assert.strict.deepStrictEqual(t.seq, ['press:130'])
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
it(`should unlock itself when a mousemove arrives with no button held`, function() {
|
|
327
|
+
//a5 拖出瀏覽器視窗外放開後收不到mouseup, 回到視窗內移動時自癒
|
|
328
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
329
|
+
t.bar.dispatch('mousedown', { button: 0, clientX: 130 })
|
|
330
|
+
t.win.dispatch('mousemove', { clientX: 250, buttons: 1 })
|
|
331
|
+
t.win.dispatch('mousemove', { clientX: 330, buttons: 0 })
|
|
332
|
+
t.win.dispatch('mousemove', { clientX: 400, buttons: 0 })
|
|
333
|
+
assert.strict.deepStrictEqual(t.seq, ['press:130', 'drag:250', 'free'])
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
it(`should unlock on mouseup from any button, as a last resort rescue`, function() {
|
|
337
|
+
//a4 刻意不判按鍵, 與split.js及MUI之終止側一致
|
|
338
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
339
|
+
t.bar.dispatch('mousedown', { button: 0, clientX: 130 })
|
|
340
|
+
t.win.dispatch('mouseup', { button: 2 })
|
|
341
|
+
t.win.dispatch('mousemove', { clientX: 300, buttons: 1 })
|
|
342
|
+
assert.strict.deepStrictEqual(t.seq, ['press:130', 'free'])
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
it(`should unlock on a mouseup received by the bar itself`, function() {
|
|
346
|
+
//a7 嵌入panel攔截mouseup時window收不到, 故bar自身亦須解鎖
|
|
347
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
348
|
+
t.bar.dispatch('mousedown', { button: 0, clientX: 130 })
|
|
349
|
+
t.bar.dispatch('mouseup', { button: 0 })
|
|
350
|
+
assert.strict.deepStrictEqual(t.seq, ['press:130', 'free'])
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
it(`should emit exactly one freeBar per lock`, function() {
|
|
354
|
+
//契約: 一次上鎖只對應一次freeBar
|
|
355
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
356
|
+
t.bar.dispatch('mousedown', { button: 0, clientX: 130 })
|
|
357
|
+
t.bar.dispatch('mouseup', { button: 0 })
|
|
358
|
+
t.win.dispatch('mouseup', { button: 0 })
|
|
359
|
+
assert.strict.deepStrictEqual(t.seq.filter((v) => v === 'free').length, 1)
|
|
360
|
+
})
|
|
361
|
+
|
|
362
|
+
it(`should close the stale lock before a new mousedown takes over`, function() {
|
|
363
|
+
//d3 mouseup遺失且其間無mousemove時, 再次按下主鍵代表前一把鎖必為殘留; 先補發freeBar再發pressBar, 使兩者恆成對
|
|
364
|
+
// 對標jQuery UI之_mouseDown:「We may have missed mouseup (out of window)」即先呼叫_mouseUp再重新開始
|
|
365
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
366
|
+
t.bar.dispatch('mousedown', { button: 0, clientX: 130 })
|
|
367
|
+
t.bar.dispatch('mousedown', { button: 0, clientX: 140 })
|
|
368
|
+
t.win.dispatch('mousemove', { clientX: 200, buttons: 1 })
|
|
369
|
+
t.win.dispatch('mouseup', { button: 0 })
|
|
370
|
+
assert.strict.deepStrictEqual(t.seq, ['press:130', 'free', 'press:140', 'drag:200', 'free'])
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
it(`should let the mouse take over a touch lock, closing it first`, function() {
|
|
374
|
+
//d3 滑鼠為觸控通道遺失touchend時之救援通道, 主鍵按下即接手; 被接手之觸控其後續事件不再作用
|
|
375
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
376
|
+
t.bar.dispatch('touchstart', te([tc(1, 130)]))
|
|
377
|
+
t.bar.dispatch('mousedown', { button: 0, clientX: 140 })
|
|
378
|
+
t.bar.dispatch('touchmove', te([tc(1, 200)]))
|
|
379
|
+
t.win.dispatch('mousemove', { clientX: 210, buttons: 1 })
|
|
380
|
+
t.bar.dispatch('touchend', te([], [tc(1, 200)]))
|
|
381
|
+
t.win.dispatch('mouseup', { button: 0 })
|
|
382
|
+
assert.strict.deepStrictEqual(t.seq, ['press:130', 'free', 'press:140', 'drag:210', 'free'])
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
it(`should ignore a touch that lands while a mouse drag is in progress`, function() {
|
|
386
|
+
//d3 滑鼠之鎖無從由觸控事件驗證是否殘留, 視為有效; 其自有buttons自癒與mouseup
|
|
387
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
388
|
+
t.bar.dispatch('mousedown', { button: 0, clientX: 130 })
|
|
389
|
+
t.bar.dispatch('touchstart', te([tc(1, 140)]))
|
|
390
|
+
t.bar.dispatch('touchmove', te([tc(1, 200)]))
|
|
391
|
+
t.bar.dispatch('touchend', te([], [tc(1, 200)]))
|
|
392
|
+
t.win.dispatch('mousemove', { clientX: 210, buttons: 1 })
|
|
393
|
+
t.win.dispatch('mouseup', { button: 0 })
|
|
394
|
+
assert.strict.deepStrictEqual(t.seq, ['press:130', 'drag:210', 'free'])
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
//--- 滾輪通道 ---
|
|
398
|
+
|
|
399
|
+
it(`should emit scrollPanel on wheel`, function() {
|
|
400
|
+
//a8
|
|
401
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
402
|
+
t.panel.dispatch('wheel', { deltaY: -120, deltaX: 60 })
|
|
403
|
+
t.panel.dispatch('wheel', { deltaY: 3, deltaX: -0.5 })
|
|
404
|
+
assert.strict.deepStrictEqual(t.seq, ['scroll:-1/1', 'scroll:1/-1'])
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
it(`should emit zero for the axis whose delta is zero`, function() {
|
|
408
|
+
//n8 一般滑鼠滾輪之deltaX恆為0, 該軸方向須為0而非NaN, 否則消費端以其累加位置時會得到NaN
|
|
409
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
410
|
+
t.panel.dispatch('wheel', { deltaY: -120, deltaX: 0 })
|
|
411
|
+
t.panel.dispatch('wheel', { deltaY: 0, deltaX: 40 })
|
|
412
|
+
t.panel.dispatch('wheel', { deltaY: -0, deltaX: 0 })
|
|
413
|
+
assert.strict.deepStrictEqual(t.full.map((v) => v[1]), [
|
|
414
|
+
{ ratioY: -1, ratioX: 0 },
|
|
415
|
+
{ ratioY: 0, ratioX: 1 },
|
|
416
|
+
{ ratioY: 0, ratioX: 0 },
|
|
417
|
+
])
|
|
418
|
+
})
|
|
419
|
+
|
|
420
|
+
it(`should emit scrollPanel from wheel on the bar, via bubbling`, function() {
|
|
421
|
+
//n5 bar上滾輪仍捲動面板, 屬刻意
|
|
422
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
423
|
+
t.bar.dispatch('wheel', { deltaY: -120, deltaX: 60 })
|
|
424
|
+
assert.strict.deepStrictEqual(t.seq, ['scroll:-1/1'])
|
|
425
|
+
})
|
|
426
|
+
|
|
427
|
+
it(`should cancel the wheel only when stopScrollPropagationForPanel is set`, function() {
|
|
428
|
+
//c6
|
|
429
|
+
let a = mk({})
|
|
430
|
+
let e1 = a.panel.dispatch('wheel', { deltaY: -120, deltaX: 0 })
|
|
431
|
+
let b = mk({ stopScrollPropagationForPanel: true })
|
|
432
|
+
let e2 = b.panel.dispatch('wheel', { deltaY: -120, deltaX: 0 })
|
|
433
|
+
assert.strict.deepStrictEqual([e1.defaultPrevented, e1.propagationStopped], [false, false])
|
|
434
|
+
assert.strict.deepStrictEqual([e2.defaultPrevented, e2.propagationStopped], [true, true])
|
|
435
|
+
})
|
|
436
|
+
|
|
437
|
+
//--- bar 觸控通道 ---
|
|
438
|
+
|
|
439
|
+
it(`should emit pressBar, dragBar and freeBar for a single finger drag`, function() {
|
|
440
|
+
//b1 基準路徑
|
|
441
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
442
|
+
t.bar.dispatch('touchstart', te([tc(1, 130)]))
|
|
443
|
+
t.bar.dispatch('touchmove', te([tc(1, 200)]))
|
|
444
|
+
t.bar.dispatch('touchend', te([], [tc(1, 200)]))
|
|
445
|
+
assert.strict.deepStrictEqual(t.seq, ['press:130', 'drag:200', 'free'])
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
it(`should work when the first finger identifier is zero`, function() {
|
|
449
|
+
//N5 Chrome之第一指identifier常為0, 判定須嚴格比null而非以真值判斷
|
|
450
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
451
|
+
t.bar.dispatch('touchstart', te([tc(0, 130)]))
|
|
452
|
+
t.bar.dispatch('touchmove', te([tc(0, 200)]))
|
|
453
|
+
t.bar.dispatch('touchend', te([], [tc(0, 200)]))
|
|
454
|
+
assert.strict.deepStrictEqual(t.seq, ['press:130', 'drag:200', 'free'])
|
|
455
|
+
})
|
|
456
|
+
|
|
457
|
+
it(`should track a starting finger whose identifier is zero against other fingers`, function() {
|
|
458
|
+
//N5 起手指identifier為0時仍須以identifier追蹤, 不可因0為假值而退回「取第一點、任一指放開皆解鎖」
|
|
459
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
460
|
+
t.bar.dispatch('touchstart', te([tc(0, 115)], [tc(0, 115)]))
|
|
461
|
+
t.bar.dispatch('touchstart', te([tc(0, 115), tc(1, 300)], [tc(1, 300)]))
|
|
462
|
+
t.bar.dispatch('touchmove', te([tc(0, 115), tc(1, 360)], [tc(1, 360)]))
|
|
463
|
+
t.bar.dispatch('touchend', te([tc(0, 115)], [tc(1, 360)]))
|
|
464
|
+
t.bar.dispatch('touchmove', te([tc(0, 160)], [tc(0, 160)]))
|
|
465
|
+
t.bar.dispatch('touchend', te([], [tc(0, 160)]))
|
|
466
|
+
assert.strict.deepStrictEqual(t.seq, ['press:115', 'drag:160', 'free'])
|
|
467
|
+
})
|
|
468
|
+
|
|
469
|
+
it(`should unlock on touchcancel without emitting a position`, function() {
|
|
470
|
+
//b2 手勢被系統中斷, 座標不可信故只解鎖不定值
|
|
471
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
472
|
+
t.bar.dispatch('touchstart', te([tc(1, 130)]))
|
|
473
|
+
t.bar.dispatch('touchmove', te([tc(1, 200)]))
|
|
474
|
+
t.bar.dispatch('touchcancel', te([], [tc(1, 200)]))
|
|
475
|
+
assert.strict.deepStrictEqual(t.seq, ['press:130', 'drag:200', 'free'])
|
|
476
|
+
})
|
|
477
|
+
|
|
478
|
+
it(`should not re-anchor when a second finger lands`, function() {
|
|
479
|
+
//b4 第二指落下不得重新定錨
|
|
480
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
481
|
+
t.bar.dispatch('touchstart', te([tc(1, 115)], [tc(1, 115)]))
|
|
482
|
+
t.bar.dispatch('touchstart', te([tc(1, 115), tc(2, 300)], [tc(2, 300)]))
|
|
483
|
+
assert.strict.deepStrictEqual(t.seq, ['press:115'])
|
|
484
|
+
})
|
|
485
|
+
|
|
486
|
+
it(`should ignore a move of a finger other than the one that started the drag`, function() {
|
|
487
|
+
//b5 第二指移動不得搶值
|
|
488
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
489
|
+
t.bar.dispatch('touchstart', te([tc(1, 115)], [tc(1, 115)]))
|
|
490
|
+
t.bar.dispatch('touchstart', te([tc(1, 115), tc(2, 300)], [tc(2, 300)]))
|
|
491
|
+
t.bar.dispatch('touchmove', te([tc(1, 115), tc(2, 360)], [tc(2, 360)]))
|
|
492
|
+
assert.strict.deepStrictEqual(t.seq, ['press:115'])
|
|
493
|
+
})
|
|
494
|
+
|
|
495
|
+
it(`should not end the drag when a finger other than the starting one is lifted`, function() {
|
|
496
|
+
//b7 非起手指抬起不得終止拖曳
|
|
497
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
498
|
+
t.bar.dispatch('touchstart', te([tc(1, 115)], [tc(1, 115)]))
|
|
499
|
+
t.bar.dispatch('touchstart', te([tc(1, 115), tc(2, 300)], [tc(2, 300)]))
|
|
500
|
+
t.bar.dispatch('touchend', te([tc(1, 115)], [tc(2, 300)]))
|
|
501
|
+
t.bar.dispatch('touchmove', te([tc(1, 160)], [tc(1, 160)]))
|
|
502
|
+
assert.strict.deepStrictEqual(t.seq, ['press:115', 'drag:160'])
|
|
503
|
+
})
|
|
504
|
+
|
|
505
|
+
it(`should end the drag when the starting finger is lifted`, function() {
|
|
506
|
+
//b6
|
|
507
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
508
|
+
t.bar.dispatch('touchstart', te([tc(1, 115)], [tc(1, 115)]))
|
|
509
|
+
t.bar.dispatch('touchstart', te([tc(1, 115), tc(2, 300)], [tc(2, 300)]))
|
|
510
|
+
t.bar.dispatch('touchend', te([tc(2, 300)], [tc(1, 115)]))
|
|
511
|
+
assert.strict.deepStrictEqual(t.seq, ['press:115', 'free'])
|
|
512
|
+
})
|
|
513
|
+
|
|
514
|
+
it(`should not end the drag when only another finger is cancelled`, function() {
|
|
515
|
+
//n6 touchcancel須比對起手指
|
|
516
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
517
|
+
t.bar.dispatch('touchstart', te([tc(1, 115)], [tc(1, 115)]))
|
|
518
|
+
t.bar.dispatch('touchstart', te([tc(1, 115), tc(2, 300)], [tc(2, 300)]))
|
|
519
|
+
t.bar.dispatch('touchcancel', te([tc(1, 115)], [tc(2, 300)]))
|
|
520
|
+
t.bar.dispatch('touchmove', te([tc(1, 160)], [tc(1, 160)]))
|
|
521
|
+
assert.strict.deepStrictEqual(t.seq, ['press:115', 'drag:160'])
|
|
522
|
+
})
|
|
523
|
+
|
|
524
|
+
it(`should unlock on a touchcancel that carries no changed touches`, function() {
|
|
525
|
+
//n13 無資訊時採安全側, 寧可解鎖也不留殘鎖
|
|
526
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
527
|
+
t.bar.dispatch('touchstart', te([tc(1, 130)]))
|
|
528
|
+
t.bar.dispatch('touchcancel', te([], []))
|
|
529
|
+
assert.strict.deepStrictEqual(t.seq, ['press:130', 'free'])
|
|
530
|
+
})
|
|
531
|
+
|
|
532
|
+
it(`should not throw when touchstart carries no touch point`, function() {
|
|
533
|
+
//b8 僅合成事件可達, 但取值防護須與panel通道對稱
|
|
534
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
535
|
+
let r = () => {
|
|
536
|
+
t.bar.dispatch('touchstart', te([], []))
|
|
537
|
+
t.bar.dispatch('touchstart', {})
|
|
538
|
+
}
|
|
539
|
+
assert.doesNotThrow(r)
|
|
540
|
+
assert.strict.deepStrictEqual(t.seq, [])
|
|
541
|
+
})
|
|
542
|
+
|
|
543
|
+
it(`should cancel the default action even for a move of another finger`, function() {
|
|
544
|
+
//R2 擋預設行為須先於起手指判定, 否則第二指移動時頁面可被捲動
|
|
545
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
546
|
+
t.bar.dispatch('touchstart', te([tc(1, 115)], [tc(1, 115)]))
|
|
547
|
+
let e = t.bar.dispatch('touchmove', te([tc(1, 115), tc(2, 300)], [tc(2, 300)]))
|
|
548
|
+
assert.strict.deepStrictEqual([e.defaultPrevented, e.propagationStopped], [true, true])
|
|
549
|
+
})
|
|
550
|
+
|
|
551
|
+
it(`should cancel only the touchmove of the bar, never its touchstart or touchend`, function() {
|
|
552
|
+
//c6 bar之touchstart與touchend不擋預設行為, 否則輕點bar後瀏覽器不再產生click
|
|
553
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
554
|
+
let e1 = t.bar.dispatch('touchstart', te([tc(1, 130)]))
|
|
555
|
+
let e2 = t.bar.dispatch('touchmove', te([tc(1, 200)]))
|
|
556
|
+
let e3 = t.bar.dispatch('touchend', te([], [tc(1, 200)]))
|
|
557
|
+
assert.strict.deepStrictEqual([e1.defaultPrevented, e2.defaultPrevented, e3.defaultPrevented], [false, true, false])
|
|
558
|
+
})
|
|
559
|
+
|
|
560
|
+
it(`should not cancel a bar touchmove while the bar touch channel is not the one locked`, function() {
|
|
561
|
+
//未上鎖或由他通道上鎖時, bar之touchmove不得擋預設行為, 否則bar上起手之一般頁面捲動會失效
|
|
562
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
563
|
+
let e1 = t.bar.dispatch('touchmove', te([tc(1, 200)]))
|
|
564
|
+
t.bar.dispatch('mousedown', { button: 0, clientX: 130 })
|
|
565
|
+
let e2 = t.bar.dispatch('touchmove', te([tc(1, 200)]))
|
|
566
|
+
assert.strict.deepStrictEqual([e1.defaultPrevented, e2.defaultPrevented], [false, false])
|
|
567
|
+
})
|
|
568
|
+
|
|
569
|
+
//--- 殘鎖之復原 ---
|
|
570
|
+
|
|
571
|
+
it(`should recover from a lost touchend on the next touch, identifiers not reused`, function() {
|
|
572
|
+
//d1 起手元素於拖曳途中被移出DOM時(消費端重繪bar之內容), 其後之touchmove與touchend不再冒泡至bar, 鎖即殘留
|
|
573
|
+
// iOS之identifier不重用; 下一次touchstart時起手指已不在touches內, 須視為殘鎖而先補發freeBar再重新上鎖
|
|
574
|
+
// 對標: jQuery UI先收尾再重新開始, split.js與MUI直接重新定錨, 三者皆不會就此失效
|
|
575
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
576
|
+
t.bar.dispatch('touchstart', te([tc(5, 130)]))
|
|
577
|
+
t.bar.dispatch('touchmove', te([tc(5, 150)]))
|
|
578
|
+
t.bar.dispatch('touchstart', te([tc(7, 140)]))
|
|
579
|
+
t.bar.dispatch('touchmove', te([tc(7, 200)]))
|
|
580
|
+
t.bar.dispatch('touchend', te([], [tc(7, 200)]))
|
|
581
|
+
t.bar.dispatch('touchstart', te([tc(9, 140)]))
|
|
582
|
+
t.bar.dispatch('touchmove', te([tc(9, 260)]))
|
|
583
|
+
t.bar.dispatch('touchend', te([], [tc(9, 260)]))
|
|
584
|
+
assert.strict.deepStrictEqual(t.seq, ['press:130', 'drag:150', 'free', 'press:140', 'drag:200', 'free', 'press:140', 'drag:260', 'free'])
|
|
585
|
+
})
|
|
586
|
+
|
|
587
|
+
it(`should recover from a lost touchend on the next touch, identifiers reused`, function() {
|
|
588
|
+
//d1 Chrome與Firefox之identifier自0起重用; 新落下之手指與起手指同identifier, 代表原起手指早已離開
|
|
589
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
590
|
+
t.bar.dispatch('touchstart', te([tc(0, 130)]))
|
|
591
|
+
t.bar.dispatch('touchmove', te([tc(0, 150)]))
|
|
592
|
+
t.bar.dispatch('touchstart', te([tc(0, 140)]))
|
|
593
|
+
t.bar.dispatch('touchmove', te([tc(0, 200)]))
|
|
594
|
+
t.bar.dispatch('touchend', te([], [tc(0, 200)]))
|
|
595
|
+
assert.strict.deepStrictEqual(t.seq, ['press:130', 'drag:150', 'free', 'press:140', 'drag:200', 'free'])
|
|
596
|
+
})
|
|
597
|
+
|
|
598
|
+
it(`should recover from a lost touchend in the panel touch channel too`, function() {
|
|
599
|
+
//d1 panel通道之起手元素為panel內任一子元素, 捲動面板重繪內容時更容易遇到
|
|
600
|
+
let t = mk({})
|
|
601
|
+
t.panel.dispatch('touchstart', te([tc(5, 300)]))
|
|
602
|
+
t.panel.dispatch('touchstart', te([tc(7, 280)]))
|
|
603
|
+
t.panel.dispatch('touchmove', te([tc(7, 240)]))
|
|
604
|
+
t.panel.dispatch('touchend', te([], [tc(7, 240)]))
|
|
605
|
+
assert.strict.deepStrictEqual(t.seq, ['press:-300', 'free', 'press:-280', 'drag:-240', 'free'])
|
|
606
|
+
})
|
|
607
|
+
|
|
608
|
+
it(`should recover a stale bar touch lock through a touch on the panel`, function() {
|
|
609
|
+
//d1 殘鎖來自bar通道, 下一次觸控落在panel非bar處
|
|
610
|
+
let t = mk({})
|
|
611
|
+
t.bar.dispatch('touchstart', te([tc(5, 130)]))
|
|
612
|
+
t.panel.dispatch('touchstart', te([tc(7, 280)]))
|
|
613
|
+
t.panel.dispatch('touchmove', te([tc(7, 240)]))
|
|
614
|
+
t.panel.dispatch('touchend', te([], [tc(7, 240)]))
|
|
615
|
+
assert.strict.deepStrictEqual(t.seq, ['press:130', 'free', 'press:-280', 'drag:-240', 'free'])
|
|
616
|
+
})
|
|
617
|
+
|
|
618
|
+
//--- 觸點資訊不完整之合成事件 ---
|
|
619
|
+
|
|
620
|
+
it(`should serve touch events that carry only touches, without identifier`, function() {
|
|
621
|
+
//d2 測試工具常見之合成事件只帶touches, 無changedTouches亦無identifier; 無從比對時沿用原本取第一點、任一touchend皆解鎖之行為
|
|
622
|
+
// 與滑鼠通道保留無button屬性之合成事件(n12)對稱
|
|
623
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
624
|
+
t.bar.dispatch('touchstart', { touches: [{ clientX: 130, clientY: 0 }] })
|
|
625
|
+
t.bar.dispatch('touchmove', { touches: [{ clientX: 200, clientY: 0 }] })
|
|
626
|
+
t.bar.dispatch('touchend', { touches: [] })
|
|
627
|
+
t.bar.dispatch('touchstart', { touches: [{ clientX: 140, clientY: 0 }] })
|
|
628
|
+
t.bar.dispatch('touchmove', { touches: [{ clientX: 220, clientY: 0 }] })
|
|
629
|
+
t.bar.dispatch('touchend', { touches: [] })
|
|
630
|
+
assert.strict.deepStrictEqual(t.seq, ['press:130', 'drag:200', 'free', 'press:140', 'drag:220', 'free'])
|
|
631
|
+
})
|
|
632
|
+
|
|
633
|
+
it(`should serve touch events that carry only touches, with identifier`, function() {
|
|
634
|
+
//d2 無changedTouches時退而自touches取起手指; touchend時起手指已不在touches內即為放開
|
|
635
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
636
|
+
t.bar.dispatch('touchstart', { touches: [tc(1, 130)] })
|
|
637
|
+
t.bar.dispatch('touchmove', { touches: [tc(1, 200), tc(2, 300)] })
|
|
638
|
+
t.bar.dispatch('touchend', { touches: [tc(1, 200)] })
|
|
639
|
+
t.bar.dispatch('touchmove', { touches: [tc(1, 210)] })
|
|
640
|
+
t.bar.dispatch('touchend', { touches: [] })
|
|
641
|
+
assert.strict.deepStrictEqual(t.seq, ['press:130', 'drag:200', 'drag:210', 'free'])
|
|
642
|
+
})
|
|
643
|
+
|
|
644
|
+
it(`should serve touch events that carry only changedTouches`, function() {
|
|
645
|
+
//d2 另一類合成事件只帶changedTouches
|
|
646
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
647
|
+
t.bar.dispatch('touchstart', { changedTouches: [tc(1, 130)] })
|
|
648
|
+
t.bar.dispatch('touchmove', { changedTouches: [tc(1, 200)] })
|
|
649
|
+
t.bar.dispatch('touchend', { changedTouches: [tc(1, 200)] })
|
|
650
|
+
assert.strict.deepStrictEqual(t.seq, ['press:130', 'drag:200', 'free'])
|
|
651
|
+
})
|
|
652
|
+
|
|
653
|
+
it(`should serve touch events that carry only changedTouches, without identifier`, function() {
|
|
654
|
+
//d2 無identifier時無從比對, 任一touchend皆解鎖; 不可因changedTouches內找不到identifier而永不解鎖
|
|
655
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
656
|
+
t.bar.dispatch('touchstart', { changedTouches: [{ clientX: 130, clientY: 0 }] })
|
|
657
|
+
t.bar.dispatch('touchmove', { changedTouches: [{ clientX: 200, clientY: 0 }] })
|
|
658
|
+
t.bar.dispatch('touchend', { changedTouches: [{ clientX: 200, clientY: 0 }] })
|
|
659
|
+
assert.strict.deepStrictEqual(t.seq, ['press:130', 'drag:200', 'free']) //freeBar須發於touchend當下, 而非等到下一次touchstart接手時才補發
|
|
660
|
+
t.bar.dispatch('touchstart', { changedTouches: [{ clientX: 140, clientY: 0 }] })
|
|
661
|
+
assert.strict.deepStrictEqual(t.seq, ['press:130', 'drag:200', 'free', 'press:140'])
|
|
662
|
+
})
|
|
663
|
+
|
|
664
|
+
it(`should fall back to touches when changedTouches is present but empty`, function() {
|
|
665
|
+
//d2 以new TouchEvent合成而只給touches時, changedTouches為空清單而非undefined, 同樣須退而取touches
|
|
666
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
667
|
+
t.bar.dispatch('touchstart', { touches: [tc(1, 130)], changedTouches: [] })
|
|
668
|
+
t.bar.dispatch('touchmove', { touches: [tc(1, 200)], changedTouches: [] })
|
|
669
|
+
t.bar.dispatch('touchend', { touches: [], changedTouches: [] })
|
|
670
|
+
assert.strict.deepStrictEqual(t.seq, ['press:130', 'drag:200', 'free'])
|
|
671
|
+
})
|
|
672
|
+
|
|
673
|
+
it(`should unlock on a touchend that carries no touch information at all`, function() {
|
|
674
|
+
//d2 無任何觸點資訊時採安全側, 寧可解鎖也不留殘鎖, 與touchcancel同一原則
|
|
675
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
676
|
+
t.bar.dispatch('touchstart', te([tc(1, 130)]))
|
|
677
|
+
t.bar.dispatch('touchend', {})
|
|
678
|
+
t.bar.dispatch('touchstart', te([tc(2, 140)]))
|
|
679
|
+
assert.strict.deepStrictEqual(t.seq, ['press:130', 'free', 'press:140'])
|
|
680
|
+
})
|
|
681
|
+
|
|
682
|
+
it(`should not keep a lock it cannot prove alive when a touchstart carries no touches`, function() {
|
|
683
|
+
//d2 touchstart未帶touches即無從驗證起手指是否仍在, 採安全側視為殘鎖
|
|
684
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
685
|
+
t.bar.dispatch('touchstart', { changedTouches: [tc(1, 115)] })
|
|
686
|
+
t.bar.dispatch('touchstart', { changedTouches: [tc(2, 300)] })
|
|
687
|
+
assert.strict.deepStrictEqual(t.seq, ['press:115', 'free', 'press:300'])
|
|
688
|
+
})
|
|
689
|
+
|
|
690
|
+
it(`should serve incomplete touch events in the panel touch channel too`, function() {
|
|
691
|
+
//d2 bar與panel共用同一組取點規則
|
|
692
|
+
let t = mk({})
|
|
693
|
+
t.panel.dispatch('touchstart', { touches: [{ clientX: 300, clientY: 0 }] })
|
|
694
|
+
t.panel.dispatch('touchmove', { touches: [{ clientX: 260, clientY: 0 }] })
|
|
695
|
+
t.panel.dispatch('touchend', { touches: [] })
|
|
696
|
+
assert.strict.deepStrictEqual(t.seq, ['press:-300', 'drag:-260', 'free'])
|
|
697
|
+
})
|
|
698
|
+
|
|
699
|
+
//--- 通道互斥 ---
|
|
700
|
+
|
|
701
|
+
it(`should emit pressBar once when a bar touch bubbles up to the panel`, function() {
|
|
702
|
+
//n1 bar位於panel之內, 且useTouchDragForPanel預設為true; 冒泡不得造成第二次pressBar
|
|
703
|
+
let t = mk({}) //不傳useTouchDragForPanel, 取其預設值true
|
|
704
|
+
t.bar.dispatch('touchstart', te([tc(1, 130)]))
|
|
705
|
+
assert.strict.deepStrictEqual(t.seq, ['press:130'])
|
|
706
|
+
})
|
|
707
|
+
|
|
708
|
+
it(`should serve a whole bar touch drag once when the panel touch channel is enabled`, function() {
|
|
709
|
+
//n1、N1b 整段手勢皆只由bar通道服務; identifier重用時第二段手勢之touchstart亦不得因冒泡而被panel通道誤判為殘鎖
|
|
710
|
+
let t = mk({})
|
|
711
|
+
t.bar.dispatch('touchstart', te([tc(0, 130)]))
|
|
712
|
+
t.bar.dispatch('touchmove', te([tc(0, 200)]))
|
|
713
|
+
t.bar.dispatch('touchend', te([], [tc(0, 200)]))
|
|
714
|
+
t.bar.dispatch('touchstart', te([tc(0, 140)]))
|
|
715
|
+
t.bar.dispatch('touchmove', te([tc(0, 210)]))
|
|
716
|
+
t.bar.dispatch('touchend', te([], [tc(0, 210)]))
|
|
717
|
+
assert.strict.deepStrictEqual(t.seq, ['press:130', 'drag:200', 'free', 'press:140', 'drag:210', 'free'])
|
|
718
|
+
})
|
|
719
|
+
|
|
720
|
+
it(`should not let a bar touchmove reach the panel channel even when the event is not cancelable`, function() {
|
|
721
|
+
//N1b 通道互斥須為顯式判定, 不可依賴domCancelEvent之stopPropagation副作用(事件不可取消時該副作用不會發生)
|
|
722
|
+
let t = mk({})
|
|
723
|
+
t.bar.dispatch('touchstart', te([tc(1, 130)]))
|
|
724
|
+
let e = t.bar.dispatch('touchmove', { ...te([tc(1, 200)]), cancelable: false })
|
|
725
|
+
assert.strict.deepStrictEqual(t.seq, ['press:130', 'drag:200'])
|
|
726
|
+
assert.strict.deepStrictEqual([e.defaultPrevented, e.propagationStopped], [false, false])
|
|
727
|
+
})
|
|
728
|
+
|
|
729
|
+
it(`should not end a bar drag when another finger is lifted on the panel`, function() {
|
|
730
|
+
//n4 第二指落在panel非bar處並抬起, 不得中斷bar之拖曳
|
|
731
|
+
let t = mk({})
|
|
732
|
+
t.bar.dispatch('touchstart', te([tc(1, 130)]))
|
|
733
|
+
t.panel.dispatch('touchstart', te([tc(1, 130), tc(2, 300)], [tc(2, 300)]))
|
|
734
|
+
t.panel.dispatch('touchend', te([tc(1, 130)], [tc(2, 300)]))
|
|
735
|
+
t.bar.dispatch('touchmove', te([tc(1, 200)]))
|
|
736
|
+
assert.strict.deepStrictEqual(t.seq, ['press:130', 'drag:200'])
|
|
737
|
+
})
|
|
738
|
+
|
|
739
|
+
it(`should not emit dragBar from mouse movement while a touch drag is in progress`, function() {
|
|
740
|
+
//b12 觸控上鎖時不得以絕對座標發dragBar, 否則污染panel通道之縮放座標語義
|
|
741
|
+
let t = mk({})
|
|
742
|
+
t.panel.dispatch('touchstart', te([tc(1, 300)]))
|
|
743
|
+
t.win.dispatch('mousemove', { clientX: 380, buttons: 0 })
|
|
744
|
+
t.win.dispatch('mousemove', { clientX: 400, buttons: 1 })
|
|
745
|
+
assert.strict.deepStrictEqual(t.seq, ['press:-300'])
|
|
746
|
+
})
|
|
747
|
+
|
|
748
|
+
it(`should keep the mouse channel working after a touch lock was cut short by a mouseup`, function() {
|
|
749
|
+
//R1 回歸釘樁: 解鎖須全部狀態成套歸零, 否則殘留之identifier會讓滑鼠通道永久失效
|
|
750
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
751
|
+
t.bar.dispatch('touchstart', te([tc(1, 130)]))
|
|
752
|
+
t.win.dispatch('mouseup', { button: 0 })
|
|
753
|
+
t.bar.dispatch('touchend', te([], [tc(1, 130)]))
|
|
754
|
+
let n = t.seq.length
|
|
755
|
+
t.bar.dispatch('mousedown', { button: 0, clientX: 130 })
|
|
756
|
+
t.win.dispatch('mousemove', { clientX: 250, buttons: 1 })
|
|
757
|
+
t.win.dispatch('mousemove', { clientX: 300, buttons: 1 })
|
|
758
|
+
assert.strict.deepStrictEqual(t.seq.slice(n), ['press:130', 'drag:250', 'drag:300'])
|
|
759
|
+
})
|
|
760
|
+
|
|
761
|
+
//--- panel 觸控通道 ---
|
|
762
|
+
|
|
763
|
+
it(`should emit scaled coordinates for the panel touch channel`, function() {
|
|
764
|
+
//座標語義: panel通道發經比例縮放之相對位移, 與bar通道之絕對座標不同
|
|
765
|
+
let t = mk({ getHeighRatio: () => 0.5, getWidthRatio: () => 0.5 })
|
|
766
|
+
t.panel.dispatch('touchstart', te([tc(1, 300, 200)]))
|
|
767
|
+
t.panel.dispatch('touchmove', te([tc(1, 260, 180)]))
|
|
768
|
+
t.panel.dispatch('touchend', te([], [tc(1, 260, 180)]))
|
|
769
|
+
assert.strict.deepStrictEqual(t.seq, ['press:-150', 'drag:-130', 'free'])
|
|
770
|
+
})
|
|
771
|
+
|
|
772
|
+
it(`should ignore the panel touch channel when useTouchDragForPanel is false`, function() {
|
|
773
|
+
//c4 現有八個呼叫點皆傳false
|
|
774
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
775
|
+
t.panel.dispatch('touchstart', te([tc(1, 300)]))
|
|
776
|
+
t.panel.dispatch('touchmove', te([tc(1, 260)]))
|
|
777
|
+
t.panel.dispatch('touchend', te([], [tc(1, 260)]))
|
|
778
|
+
assert.strict.deepStrictEqual(t.seq, [])
|
|
779
|
+
})
|
|
780
|
+
|
|
781
|
+
it(`should track the starting finger in the panel touch channel too`, function() {
|
|
782
|
+
//b11 panel通道同樣以identifier鎖定
|
|
783
|
+
let t = mk({})
|
|
784
|
+
t.panel.dispatch('touchstart', te([tc(1, 300)], [tc(1, 300)]))
|
|
785
|
+
t.panel.dispatch('touchmove', te([tc(1, 300), tc(2, 100)], [tc(2, 100)]))
|
|
786
|
+
t.panel.dispatch('touchcancel', te([], []))
|
|
787
|
+
assert.strict.deepStrictEqual(t.seq, ['press:-300', 'free'])
|
|
788
|
+
})
|
|
789
|
+
|
|
790
|
+
it(`should cancel the panel touch events only when stopTouchDragPropagationForPanel is set`, function() {
|
|
791
|
+
//c6 旗標控制panel之touchstart、touchmove、touchend三者; 未上鎖時之touchmove與touchend一律不擋
|
|
792
|
+
let run = (opt) => {
|
|
793
|
+
let t = mk(opt)
|
|
794
|
+
let e0 = t.panel.dispatch('touchmove', te([tc(1, 300)]))
|
|
795
|
+
let e1 = t.panel.dispatch('touchstart', te([tc(1, 300)]))
|
|
796
|
+
let e2 = t.panel.dispatch('touchmove', te([tc(1, 260)]))
|
|
797
|
+
let e3 = t.panel.dispatch('touchmove', te([tc(1, 260), tc(2, 100)], [tc(2, 100)]))
|
|
798
|
+
let e4 = t.panel.dispatch('touchend', te([], [tc(1, 260)]))
|
|
799
|
+
let e5 = t.panel.dispatch('touchend', te([], [tc(1, 260)]))
|
|
800
|
+
return [e0, e1, e2, e3, e4, e5].map((e) => e.defaultPrevented && e.propagationStopped)
|
|
801
|
+
}
|
|
802
|
+
assert.strict.deepStrictEqual(run({ stopTouchDragPropagationForPanel: false }), [false, false, false, false, false, false])
|
|
803
|
+
assert.strict.deepStrictEqual(run({ stopTouchDragPropagationForPanel: true }), [false, true, true, true, true, false])
|
|
804
|
+
})
|
|
805
|
+
|
|
806
|
+
it(`should not cancel a panel touchstart that is ignored`, function() {
|
|
807
|
+
//他指落下被忽略時不擋其預設行為, 與bar通道一致
|
|
808
|
+
let t = mk({ stopTouchDragPropagationForPanel: true })
|
|
809
|
+
t.panel.dispatch('touchstart', te([tc(1, 300)]))
|
|
810
|
+
let e = t.panel.dispatch('touchstart', te([tc(1, 300), tc(2, 100)], [tc(2, 100)]))
|
|
811
|
+
assert.strict.deepStrictEqual(e.defaultPrevented, false)
|
|
812
|
+
})
|
|
813
|
+
|
|
814
|
+
//--- 事件參數 ---
|
|
815
|
+
|
|
816
|
+
it(`should emit both axes of the absolute coordinates for the bar channels`, function() {
|
|
817
|
+
//bar之滑鼠與觸控通道皆發絕對座標, 兩軸皆須正確, 且不受比例函數影響; freeBar不帶參數
|
|
818
|
+
let t = mk({ useTouchDragForPanel: false, getHeighRatio: () => 0.5, getWidthRatio: () => 0.25 })
|
|
819
|
+
t.bar.dispatch('mousedown', { button: 0, clientX: 10, clientY: 11 })
|
|
820
|
+
t.win.dispatch('mousemove', { clientX: 20, clientY: 21, buttons: 1 })
|
|
821
|
+
t.win.dispatch('mouseup', { button: 0 })
|
|
822
|
+
t.bar.dispatch('touchstart', te([tc(1, 30, 31)]))
|
|
823
|
+
t.bar.dispatch('touchmove', te([tc(1, 40, 41)]))
|
|
824
|
+
t.bar.dispatch('touchend', te([], [tc(1, 40, 41)]))
|
|
825
|
+
assert.strict.deepStrictEqual(t.full, [
|
|
826
|
+
['pressBar', { clientY: 11, clientX: 10 }],
|
|
827
|
+
['dragBar', { clientY: 21, clientX: 20 }],
|
|
828
|
+
['freeBar'],
|
|
829
|
+
['pressBar', { clientY: 31, clientX: 30 }],
|
|
830
|
+
['dragBar', { clientY: 41, clientX: 40 }],
|
|
831
|
+
['freeBar'],
|
|
832
|
+
])
|
|
833
|
+
})
|
|
834
|
+
|
|
835
|
+
it(`should scale each axis by its own ratio for the panel touch channel`, function() {
|
|
836
|
+
//clientY用getHeighRatio, clientX用getWidthRatio, 兩者不得互換
|
|
837
|
+
let t = mk({ getHeighRatio: () => 0.5, getWidthRatio: () => 0.25 })
|
|
838
|
+
t.panel.dispatch('touchstart', te([tc(1, 100, 200)]))
|
|
839
|
+
t.panel.dispatch('touchmove', te([tc(1, 80, 160)]))
|
|
840
|
+
t.panel.dispatch('touchend', te([], [tc(1, 80, 160)]))
|
|
841
|
+
assert.strict.deepStrictEqual(t.full, [
|
|
842
|
+
['pressBar', { clientY: -100, clientX: -25 }],
|
|
843
|
+
['dragBar', { clientY: -80, clientX: -20 }],
|
|
844
|
+
['freeBar'],
|
|
845
|
+
])
|
|
846
|
+
})
|
|
847
|
+
|
|
848
|
+
it(`should read the ratios at the moment of each event`, function() {
|
|
849
|
+
//比例由外部函數提供, 係因組件或內容物尺寸會變動, 故每次發事件皆須重新取值
|
|
850
|
+
let r = 1
|
|
851
|
+
let t = mk({ getHeighRatio: () => r, getWidthRatio: () => r })
|
|
852
|
+
t.panel.dispatch('touchstart', te([tc(1, 100, 100)]))
|
|
853
|
+
r = 0.5
|
|
854
|
+
t.panel.dispatch('touchmove', te([tc(1, 100, 100)]))
|
|
855
|
+
assert.strict.deepStrictEqual(t.seq, ['press:-100', 'drag:-50'])
|
|
856
|
+
})
|
|
857
|
+
|
|
858
|
+
//--- 監聽器拋錯與重入 ---
|
|
859
|
+
|
|
860
|
+
it(`should cancel the default action even when a listener throws`, function() {
|
|
861
|
+
//N3a 擋預設行為須先於emit, 否則消費端拋錯時拖曳中頁面可被捲動
|
|
862
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
863
|
+
t.das.on('dragBar', () => {
|
|
864
|
+
throw new Error('consumer boom')
|
|
865
|
+
})
|
|
866
|
+
t.bar.dispatch('touchstart', te([tc(1, 130)]))
|
|
867
|
+
let r = () => {
|
|
868
|
+
t.bar.dispatch('touchmove', te([tc(1, 200)]))
|
|
869
|
+
}
|
|
870
|
+
assert.throws(r, /consumer boom/) //監聽器拋錯仍外拋至emit呼叫端, 與evem一致
|
|
871
|
+
assert.strict.deepStrictEqual(t.bar.lastEvent.defaultPrevented, true)
|
|
872
|
+
})
|
|
873
|
+
|
|
874
|
+
it(`should cancel the panel events and the wheel even when a listener throws`, function() {
|
|
875
|
+
//N3a 同一原則套用於全部會擋預設行為之處理器
|
|
876
|
+
let t = mk({ stopTouchDragPropagationForPanel: true, stopScrollPropagationForPanel: true })
|
|
877
|
+
for (let name of ['pressBar', 'dragBar', 'freeBar', 'scrollPanel']) {
|
|
878
|
+
t.das.on(name, () => {
|
|
879
|
+
throw new Error('consumer boom')
|
|
880
|
+
})
|
|
881
|
+
}
|
|
882
|
+
let rs = []
|
|
883
|
+
let run = (ele, type, init) => {
|
|
884
|
+
assert.throws(() => ele.dispatch(type, init), /consumer boom/, type)
|
|
885
|
+
rs.push(`${type}:${ele.lastEvent.defaultPrevented}`)
|
|
886
|
+
}
|
|
887
|
+
run(t.panel, 'touchstart', te([tc(1, 300)]))
|
|
888
|
+
run(t.panel, 'touchmove', te([tc(1, 260)]))
|
|
889
|
+
run(t.panel, 'touchend', te([], [tc(1, 260)]))
|
|
890
|
+
run(t.panel, 'wheel', { deltaY: -120, deltaX: 0 })
|
|
891
|
+
assert.throws(() => t.bar.dispatch('mousedown', { button: 0, clientX: 130 }), /consumer boom/)
|
|
892
|
+
run(t.win, 'mouseup', { button: 0 })
|
|
893
|
+
assert.strict.deepStrictEqual(rs, ['touchstart:true', 'touchmove:true', 'touchend:true', 'wheel:true', 'mouseup:true'])
|
|
894
|
+
})
|
|
895
|
+
|
|
896
|
+
it(`should change its state before emitting, so a throwing listener leaves no half state`, function() {
|
|
897
|
+
//pressBar之監聽器拋錯時鎖已上, 其後拖曳照常; freeBar之監聽器拋錯時鎖已解, 不留殘鎖
|
|
898
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
899
|
+
let boom = true
|
|
900
|
+
t.das.on('pressBar', () => {
|
|
901
|
+
if (boom) {
|
|
902
|
+
throw new Error('consumer boom')
|
|
903
|
+
}
|
|
904
|
+
})
|
|
905
|
+
t.das.on('freeBar', () => {
|
|
906
|
+
if (boom) {
|
|
907
|
+
throw new Error('consumer boom')
|
|
908
|
+
}
|
|
909
|
+
})
|
|
910
|
+
assert.throws(() => t.bar.dispatch('mousedown', { button: 0, clientX: 130 }), /consumer boom/)
|
|
911
|
+
t.win.dispatch('mousemove', { clientX: 200, buttons: 1 })
|
|
912
|
+
assert.throws(() => t.win.dispatch('mouseup', { button: 0 }), /consumer boom/)
|
|
913
|
+
boom = false
|
|
914
|
+
t.win.dispatch('mousemove', { clientX: 300, buttons: 1 })
|
|
915
|
+
t.win.dispatch('mouseup', { button: 0 })
|
|
916
|
+
assert.strict.deepStrictEqual(t.seq, ['press:130', 'drag:200', 'free'])
|
|
917
|
+
})
|
|
918
|
+
|
|
919
|
+
it(`should stay silent when clear is called inside a listener`, function() {
|
|
920
|
+
//監聽器內呼叫clear為合法用法(如拖曳至某處即銷毀元件)
|
|
921
|
+
for (let name of ['pressBar', 'dragBar', 'freeBar']) {
|
|
922
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
923
|
+
t.das.on(name, () => {
|
|
924
|
+
t.das.clear()
|
|
925
|
+
})
|
|
926
|
+
let r = () => {
|
|
927
|
+
t.bar.dispatch('mousedown', { button: 0, clientX: 130 })
|
|
928
|
+
t.win.dispatch('mousemove', { clientX: 200, buttons: 1 })
|
|
929
|
+
t.win.dispatch('mouseup', { button: 0 })
|
|
930
|
+
t.bar.dispatch('mousedown', { button: 0, clientX: 140 })
|
|
931
|
+
}
|
|
932
|
+
assert.doesNotThrow(r, name)
|
|
933
|
+
let exp = { pressBar: ['press:130'], dragBar: ['press:130', 'drag:200'], freeBar: ['press:130', 'drag:200', 'free'] }
|
|
934
|
+
assert.strict.deepStrictEqual(t.seq, exp[name], name)
|
|
935
|
+
assert.strict.deepStrictEqual([t.panel.nListeners(), t.bar.nListeners(), t.win.nListeners()], [0, 0, 0], name)
|
|
936
|
+
}
|
|
937
|
+
})
|
|
938
|
+
|
|
939
|
+
it(`should not emit pressBar when clear is called by the freeBar listener of a take over`, function() {
|
|
940
|
+
//d3 接手時先補發freeBar再發pressBar; 若freeBar之監聽器已呼叫clear, 不得再發pressBar
|
|
941
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
942
|
+
t.bar.dispatch('mousedown', { button: 0, clientX: 130 })
|
|
943
|
+
t.das.on('freeBar', () => {
|
|
944
|
+
t.das.clear()
|
|
945
|
+
})
|
|
946
|
+
t.bar.dispatch('mousedown', { button: 0, clientX: 140 })
|
|
947
|
+
assert.strict.deepStrictEqual(t.seq, ['press:130', 'free'])
|
|
948
|
+
})
|
|
949
|
+
|
|
950
|
+
//--- 選項與清理 ---
|
|
951
|
+
|
|
952
|
+
it(`should work without the opt argument`, function() {
|
|
953
|
+
let win = new Ele('window')
|
|
954
|
+
globalThis.window = win
|
|
955
|
+
globalThis.document = new Ele('document')
|
|
956
|
+
let panel = new Ele('panel')
|
|
957
|
+
let bar = new Ele('bar', panel)
|
|
958
|
+
let seq = []
|
|
959
|
+
let das = domDragBarAndScroll(panel, bar)
|
|
960
|
+
das.on('pressBar', (d) => seq.push(`press:${d.clientX}`))
|
|
961
|
+
panel.dispatch('touchstart', te([tc(1, 300)]))
|
|
962
|
+
assert.strict.deepStrictEqual(seq, ['press:-300'])
|
|
963
|
+
})
|
|
964
|
+
|
|
965
|
+
it(`should fall back to a ratio of one when the ratio getters are not functions`, function() {
|
|
966
|
+
//c7
|
|
967
|
+
let t = mk({ getHeighRatio: 'x', getWidthRatio: null })
|
|
968
|
+
t.panel.dispatch('touchstart', te([tc(1, 300, 200)]))
|
|
969
|
+
assert.strict.deepStrictEqual(t.full, [['pressBar', { clientY: -200, clientX: -300 }]])
|
|
970
|
+
})
|
|
971
|
+
|
|
972
|
+
it(`should fall back to the default when a flag is not a boolean`, function() {
|
|
973
|
+
//c7 useTouchDragForPanel預設true, 兩個stop旗標預設false
|
|
974
|
+
let t = mk({ useTouchDragForPanel: 'no', stopTouchDragPropagationForPanel: 1, stopScrollPropagationForPanel: 'yes' })
|
|
975
|
+
let e1 = t.panel.dispatch('touchstart', te([tc(1, 300)]))
|
|
976
|
+
let e2 = t.panel.dispatch('wheel', { deltaY: -120, deltaX: 60 })
|
|
977
|
+
assert.strict.deepStrictEqual(t.seq, ['press:-300', 'scroll:-1/1'])
|
|
978
|
+
assert.strict.deepStrictEqual([e1.defaultPrevented, e2.defaultPrevented], [false, false])
|
|
979
|
+
})
|
|
980
|
+
|
|
981
|
+
it(`should cancel the panel touch propagation only when asked`, function() {
|
|
982
|
+
//c6 stopTouchDragPropagationForPanel
|
|
983
|
+
let a = mk({ stopTouchDragPropagationForPanel: false })
|
|
984
|
+
let e1 = a.panel.dispatch('touchstart', te([tc(1, 300)]))
|
|
985
|
+
let b = mk({ stopTouchDragPropagationForPanel: true })
|
|
986
|
+
let e2 = b.panel.dispatch('touchstart', te([tc(1, 300)]))
|
|
987
|
+
assert.strict.deepStrictEqual([e1.defaultPrevented, e2.defaultPrevented], [false, true])
|
|
988
|
+
})
|
|
989
|
+
|
|
990
|
+
it(`should cancel the window mouseup only when locked and stopScrollPropagationForPanel is set`, function() {
|
|
991
|
+
//c6
|
|
992
|
+
let run = (opt) => {
|
|
993
|
+
let t = mk(opt)
|
|
994
|
+
let e1 = t.win.dispatch('mouseup', { button: 0 })
|
|
995
|
+
t.bar.dispatch('mousedown', { button: 0, clientX: 130 })
|
|
996
|
+
let e2 = t.win.dispatch('mouseup', { button: 0 })
|
|
997
|
+
return [e1.defaultPrevented, e2.defaultPrevented]
|
|
998
|
+
}
|
|
999
|
+
assert.strict.deepStrictEqual(run({}), [false, false])
|
|
1000
|
+
assert.strict.deepStrictEqual(run({ stopScrollPropagationForPanel: true }), [false, true])
|
|
1001
|
+
})
|
|
1002
|
+
|
|
1003
|
+
it(`should register only the listeners it needs`, function() {
|
|
1004
|
+
//c2 panel之觸控通道停用時不掛其監聽, 避免panel平白多一個不可被動化(passive:false)之touchmove監聽
|
|
1005
|
+
// window與document不掛scroll與wheel: 該二事件於該處無法取消, 掛了亦無作用
|
|
1006
|
+
let t1 = mk({})
|
|
1007
|
+
assert.strict.deepStrictEqual(t1.panel.types(), ['touchcancel', 'touchend', 'touchmove', 'touchstart', 'wheel'])
|
|
1008
|
+
assert.strict.deepStrictEqual(t1.bar.types(), ['mousedown', 'mouseup', 'touchcancel', 'touchend', 'touchmove', 'touchstart'])
|
|
1009
|
+
assert.strict.deepStrictEqual(t1.win.types(), ['mousemove', 'mouseup'])
|
|
1010
|
+
assert.strict.deepStrictEqual(t1.doc.types(), [])
|
|
1011
|
+
assert.strict.deepStrictEqual([t1.panel.nListeners(), t1.bar.nListeners(), t1.win.nListeners(), t1.doc.nListeners()], [5, 6, 2, 0])
|
|
1012
|
+
let t2 = mk({ useTouchDragForPanel: false })
|
|
1013
|
+
assert.strict.deepStrictEqual(t2.panel.types(), ['wheel'])
|
|
1014
|
+
assert.strict.deepStrictEqual([t2.panel.nListeners(), t2.bar.nListeners(), t2.win.nListeners(), t2.doc.nListeners()], [1, 6, 2, 0])
|
|
1015
|
+
})
|
|
1016
|
+
|
|
1017
|
+
it(`should register the touchmove listeners as non passive`, function() {
|
|
1018
|
+
//touchmove須可取消才能阻止頁面被拖曳捲動, 故必須明確給passive:false
|
|
1019
|
+
let t = mk({})
|
|
1020
|
+
let ps = [t.bar.hs.touchmove, t.panel.hs.touchmove].map((hs) => hs.map((h) => h.opts && h.opts.passive))
|
|
1021
|
+
assert.strict.deepStrictEqual(ps, [[false], [false]])
|
|
1022
|
+
})
|
|
1023
|
+
|
|
1024
|
+
it(`should remove every listener it registered`, function() {
|
|
1025
|
+
//c2 add與clear對稱
|
|
1026
|
+
for (let opt of [{}, { useTouchDragForPanel: false }]) {
|
|
1027
|
+
let t = mk(opt)
|
|
1028
|
+
t.das.clear()
|
|
1029
|
+
assert.strict.deepStrictEqual([t.panel.nListeners(), t.bar.nListeners(), t.win.nListeners(), t.doc.nListeners()], [0, 0, 0, 0])
|
|
1030
|
+
}
|
|
1031
|
+
})
|
|
1032
|
+
|
|
1033
|
+
it(`should stop emitting after clear, even when called mid drag`, function() {
|
|
1034
|
+
//a9、c1 拖曳中clear須停止且狀態歸零
|
|
1035
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
1036
|
+
t.bar.dispatch('mousedown', { button: 0, clientX: 130 })
|
|
1037
|
+
t.das.clear()
|
|
1038
|
+
t.win.dispatch('mousemove', { clientX: 300, buttons: 1 })
|
|
1039
|
+
t.win.dispatch('mouseup', { button: 0 })
|
|
1040
|
+
assert.strict.deepStrictEqual(t.seq, ['press:130'])
|
|
1041
|
+
})
|
|
1042
|
+
|
|
1043
|
+
it(`should not throw when clear is called twice`, function() {
|
|
1044
|
+
let t = mk({ useTouchDragForPanel: false })
|
|
1045
|
+
let r = () => {
|
|
1046
|
+
t.das.clear()
|
|
1047
|
+
t.das.clear()
|
|
1048
|
+
}
|
|
1049
|
+
assert.doesNotThrow(r)
|
|
1050
|
+
})
|
|
1051
|
+
|
|
1052
|
+
it(`should keep two instances on the same panel independent`, function() {
|
|
1053
|
+
//c8 兩實例之鎖互不干擾, 清理其一不影響另一; 但滾輪會各發一次, 屬已知限制
|
|
1054
|
+
let win = new Ele('window')
|
|
1055
|
+
globalThis.window = win
|
|
1056
|
+
globalThis.document = new Ele('document')
|
|
1057
|
+
let panel = new Ele('panel')
|
|
1058
|
+
let bar1 = new Ele('bar1', panel)
|
|
1059
|
+
let bar2 = new Ele('bar2', panel)
|
|
1060
|
+
let s1 = []
|
|
1061
|
+
let s2 = []
|
|
1062
|
+
let d1 = domDragBarAndScroll(panel, bar1, { useTouchDragForPanel: false })
|
|
1063
|
+
let d2 = domDragBarAndScroll(panel, bar2, { useTouchDragForPanel: false })
|
|
1064
|
+
d1.on('dragBar', (d) => s1.push(d.clientX))
|
|
1065
|
+
d2.on('dragBar', (d) => s2.push(d.clientX))
|
|
1066
|
+
d1.on('scrollPanel', () => s1.push('scroll'))
|
|
1067
|
+
d2.on('scrollPanel', () => s2.push('scroll'))
|
|
1068
|
+
bar1.dispatch('mousedown', { button: 0, clientX: 100 })
|
|
1069
|
+
win.dispatch('mousemove', { clientX: 200, buttons: 1 })
|
|
1070
|
+
bar2.dispatch('touchstart', te([tc(1, 50)]))
|
|
1071
|
+
bar2.dispatch('touchmove', te([tc(1, 60)]))
|
|
1072
|
+
panel.dispatch('wheel', { deltaY: -120, deltaX: 60 })
|
|
1073
|
+
d1.clear()
|
|
1074
|
+
win.dispatch('mousemove', { clientX: 300, buttons: 1 })
|
|
1075
|
+
bar2.dispatch('touchmove', te([tc(1, 70)]))
|
|
1076
|
+
assert.strict.deepStrictEqual([s1, s2], [[200, 'scroll'], [60, 'scroll', 70]])
|
|
1077
|
+
})
|
|
1078
|
+
|
|
1079
|
+
//--- 不變式 ---
|
|
1080
|
+
|
|
1081
|
+
it(`should keep pressBar and freeBar paired for any sequence of events, and never end up stuck`, function() {
|
|
1082
|
+
//以固定種子之亂數產生事件序列, 不論序列是否為實際裝置所能產生, 皆須滿足:
|
|
1083
|
+
// (1) pressBar與freeBar嚴格交替且由pressBar起頭, dragBar只出現於兩者之間
|
|
1084
|
+
// (2) 任何狀態下, 一次完整之滑鼠主鍵手勢必被完整服務
|
|
1085
|
+
// (3) 非滑鼠上鎖之任何狀態下, 一次完整之新觸控手勢必被完整服務
|
|
1086
|
+
let seed = 20260922
|
|
1087
|
+
let rnd = () => {
|
|
1088
|
+
seed = (seed + 0x6D2B79F5) | 0
|
|
1089
|
+
let v = Math.imul(seed ^ (seed >>> 15), 1 | seed)
|
|
1090
|
+
v = (v + Math.imul(v ^ (v >>> 7), 61 | v)) ^ v
|
|
1091
|
+
return ((v ^ (v >>> 14)) >>> 0) / 4294967296
|
|
1092
|
+
}
|
|
1093
|
+
let pick = (arr) => arr[Math.floor(rnd() * arr.length)]
|
|
1094
|
+
let ids = [0, 1, 2]
|
|
1095
|
+
let rndX = () => Math.floor(rnd() * 400)
|
|
1096
|
+
let rndTouches = () => ids.filter(() => rnd() < 0.5).map((id) => tc(id, rndX()))
|
|
1097
|
+
let acts = [
|
|
1098
|
+
(t) => t.bar.dispatch('mousedown', { button: pick([0, 0, 0, 1, 2]), clientX: rndX() }),
|
|
1099
|
+
(t) => t.bar.dispatch('mouseup', { button: 0 }),
|
|
1100
|
+
(t) => t.win.dispatch('mouseup', { button: pick([0, 2]) }),
|
|
1101
|
+
(t) => t.win.dispatch('mousemove', { clientX: rndX(), buttons: pick([0, 1, 1, 1]) }),
|
|
1102
|
+
(t) => pick([t.bar, t.panel]).dispatch('touchstart', te(rndTouches(), [tc(pick(ids), rndX())])),
|
|
1103
|
+
(t) => pick([t.bar, t.panel]).dispatch('touchmove', te(rndTouches(), [tc(pick(ids), rndX())])),
|
|
1104
|
+
(t) => pick([t.bar, t.panel]).dispatch('touchend', te(rndTouches(), [tc(pick(ids), rndX())])),
|
|
1105
|
+
(t) => pick([t.bar, t.panel]).dispatch('touchcancel', te(rndTouches(), pick([[], [tc(pick(ids), 0)]]))),
|
|
1106
|
+
(t) => t.panel.dispatch('wheel', { deltaY: pick([-120, 0, 120]), deltaX: pick([-60, 0, 60]) }),
|
|
1107
|
+
]
|
|
1108
|
+
let checkStream = (seq, tag) => {
|
|
1109
|
+
let pressed = false
|
|
1110
|
+
seq.forEach((v, i) => {
|
|
1111
|
+
let k = v.split(':')[0]
|
|
1112
|
+
if (k === 'press') {
|
|
1113
|
+
assert.strict.deepStrictEqual(pressed, false, `${tag}: pressBar while pressed at ${i}: ${seq.join(' ')}`)
|
|
1114
|
+
pressed = true
|
|
1115
|
+
}
|
|
1116
|
+
else if (k === 'free') {
|
|
1117
|
+
assert.strict.deepStrictEqual(pressed, true, `${tag}: freeBar while not pressed at ${i}: ${seq.join(' ')}`)
|
|
1118
|
+
pressed = false
|
|
1119
|
+
}
|
|
1120
|
+
else if (k === 'drag') {
|
|
1121
|
+
assert.strict.deepStrictEqual(pressed, true, `${tag}: dragBar while not pressed at ${i}: ${seq.join(' ')}`)
|
|
1122
|
+
}
|
|
1123
|
+
})
|
|
1124
|
+
return pressed
|
|
1125
|
+
}
|
|
1126
|
+
let kinds = (seq) => seq.map((v) => v.split(':')[0]).join(',')
|
|
1127
|
+
for (let opt of [{}, { useTouchDragForPanel: false }]) {
|
|
1128
|
+
for (let k = 0; k < 300; k++) {
|
|
1129
|
+
let tag = `opt=${JSON.stringify(opt)} run=${k}`
|
|
1130
|
+
let t = mk(opt)
|
|
1131
|
+
for (let i = 0; i < 25; i++) {
|
|
1132
|
+
pick(acts)(t)
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
//(1)
|
|
1136
|
+
let pressed = checkStream(t.seq, tag)
|
|
1137
|
+
|
|
1138
|
+
//(3), 新手指之identifier為9, 不與序列內者重複, 且touches只含該指
|
|
1139
|
+
if (probeState(t).indexOf('barMouse') < 0) {
|
|
1140
|
+
let n = t.seq.length
|
|
1141
|
+
t.bar.dispatch('touchstart', te([tc(9, 500)]))
|
|
1142
|
+
t.bar.dispatch('touchmove', te([tc(9, 501)]))
|
|
1143
|
+
t.bar.dispatch('touchend', te([], [tc(9, 501)]))
|
|
1144
|
+
let exp = pressed ? 'free,press,drag,free' : 'press,drag,free'
|
|
1145
|
+
assert.strict.deepStrictEqual(kinds(t.seq.slice(n)), exp, `${tag}: touch gesture`)
|
|
1146
|
+
pressed = false
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
//(2)
|
|
1150
|
+
let n = t.seq.length
|
|
1151
|
+
t.bar.dispatch('mousedown', { button: 0, clientX: 500 })
|
|
1152
|
+
t.win.dispatch('mousemove', { clientX: 501, buttons: 1 })
|
|
1153
|
+
t.win.dispatch('mouseup', { button: 0 })
|
|
1154
|
+
let exp = pressed ? 'free,press,drag,free' : 'press,drag,free'
|
|
1155
|
+
assert.strict.deepStrictEqual(kinds(t.seq.slice(n)), exp, `${tag}: mouse gesture`)
|
|
1156
|
+
|
|
1157
|
+
//(1), 含收尾手勢
|
|
1158
|
+
checkStream(t.seq, tag)
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
})
|
|
1162
|
+
|
|
1163
|
+
})
|