wsemi 1.9.4 → 1.9.6

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.
Files changed (489) hide show
  1. package/README.md +1 -1
  2. package/dist/wsemi.umd.js +5 -5
  3. package/dist/wsemi.umd.js.map +1 -1
  4. package/docs/-_class.mjs.html +1 -1
  5. package/docs/-_const.mjs.html +1 -1
  6. package/docs/ab2blob.mjs.html +1 -1
  7. package/docs/ab2u8arr.mjs.html +1 -1
  8. package/docs/addExcelWorksheetFromData.mjs.html +1 -1
  9. package/docs/addMonth.mjs.html +1 -1
  10. package/docs/aes2str.mjs.html +1 -1
  11. package/docs/alive.mjs.html +1 -1
  12. package/docs/arr2dt.mjs.html +1 -1
  13. package/docs/arrAccum.mjs.html +1 -1
  14. package/docs/arrAdd.mjs.html +1 -1
  15. package/docs/arrAt.mjs.html +1 -1
  16. package/docs/arrDiff.mjs.html +1 -1
  17. package/docs/arrFilterByKeywords.mjs.html +1 -1
  18. package/docs/arrFilterByNum.mjs.html +1 -1
  19. package/docs/arrFilterByPnum.mjs.html +1 -1
  20. package/docs/arrFilterByPnumAndToLog.mjs.html +1 -1
  21. package/docs/arrFind.mjs.html +1 -1
  22. package/docs/arrGroupByMaxmin.mjs.html +1 -1
  23. package/docs/arrHas.mjs.html +1 -1
  24. package/docs/arrInsert.mjs.html +1 -1
  25. package/docs/arrLookupByMaxmin.mjs.html +1 -1
  26. package/docs/arrMax.mjs.html +1 -1
  27. package/docs/arrMin.mjs.html +1 -1
  28. package/docs/arrMulti.mjs.html +1 -1
  29. package/docs/arrNorm.mjs.html +1 -1
  30. package/docs/arrProduct.mjs.html +1 -1
  31. package/docs/arrPull.mjs.html +1 -1
  32. package/docs/arrPullAt.mjs.html +1 -1
  33. package/docs/arrReduce.mjs.html +1 -1
  34. package/docs/arrReverse.mjs.html +1 -1
  35. package/docs/arrSep.mjs.html +1 -1
  36. package/docs/arrSort.mjs.html +1 -1
  37. package/docs/asind.mjs.html +1 -1
  38. package/docs/atan2d.mjs.html +1 -1
  39. package/docs/atand.mjs.html +1 -1
  40. package/docs/attstr.mjs.html +1 -1
  41. package/docs/b642obj.mjs.html +1 -1
  42. package/docs/b642str.mjs.html +1 -1
  43. package/docs/b642u16arr.mjs.html +1 -1
  44. package/docs/b642u8arr.mjs.html +1 -1
  45. package/docs/binstr.mjs.html +1 -1
  46. package/docs/blob2ab.mjs.html +1 -1
  47. package/docs/blob2b64.mjs.html +1 -1
  48. package/docs/blob2str.mjs.html +1 -1
  49. package/docs/blob2u8arr.mjs.html +1 -1
  50. package/docs/blobs2b64s.mjs.html +1 -1
  51. package/docs/blobs2u8arrs.mjs.html +1 -1
  52. package/docs/bodyLog.mjs.html +1 -1
  53. package/docs/browserView.mjs.html +1 -1
  54. package/docs/browserViewFromBlob.mjs.html +1 -1
  55. package/docs/browserViewFromText.mjs.html +1 -1
  56. package/docs/browserViewFromU8arr.mjs.html +1 -1
  57. package/docs/bs2u8arr.mjs.html +1 -1
  58. package/docs/bufRead.mjs.html +1 -1
  59. package/docs/bufReadDbl.mjs.html +1 -1
  60. package/docs/bufWrite.mjs.html +1 -1
  61. package/docs/bufWriteDbl.mjs.html +1 -1
  62. package/docs/cache.mjs.html +1 -1
  63. package/docs/cacheBd.mjs.html +1 -1
  64. package/docs/cacheSt.mjs.html +1 -1
  65. package/docs/cbol.mjs.html +1 -1
  66. package/docs/cdbl.mjs.html +1 -1
  67. package/docs/ceil.mjs.html +1 -1
  68. package/docs/cfilesize.mjs.html +1 -1
  69. package/docs/cfinancial.mjs.html +1 -1
  70. package/docs/cint.mjs.html +1 -1
  71. package/docs/clearXSS.mjs.html +1 -1
  72. package/docs/clearXSSDyn.mjs.html +1 -1
  73. package/docs/color.mjs.html +1 -1
  74. package/docs/composeToTree.mjs.html +1 -1
  75. package/docs/convertToTree.mjs.html +1 -1
  76. package/docs/copyStrToClipboard.mjs.html +1 -1
  77. package/docs/cosd.mjs.html +1 -1
  78. package/docs/cotd.mjs.html +1 -1
  79. package/docs/createExcelWorkbook.mjs.html +1 -1
  80. package/docs/cscd.mjs.html +1 -1
  81. package/docs/cstr.mjs.html +1 -1
  82. package/docs/day2cht.mjs.html +1 -1
  83. package/docs/debounce.mjs.html +1 -1
  84. package/docs/delay.mjs.html +1 -1
  85. package/docs/dig.mjs.html +1 -1
  86. package/docs/digExp.mjs.html +1 -1
  87. package/docs/domAlert.mjs.html +1 -1
  88. package/docs/domAppend.mjs.html +1 -1
  89. package/docs/domCancelEvent.mjs.html +1 -1
  90. package/docs/domCompareText.mjs.html +1 -1
  91. package/docs/domConvertToPic.mjs.html +1 -1
  92. package/docs/domConvertToPicDyn.mjs.html +1 -1
  93. package/docs/domDetect.mjs.html +341 -205
  94. package/docs/domDrag.mjs.html +1 -1
  95. package/docs/domDragBarAndScroll.mjs.html +1 -1
  96. package/docs/domDragPointer.mjs.html +1 -1
  97. package/docs/domDragPreview.mjs.html +1 -1
  98. package/docs/domDropFiles.mjs.html +1 -1
  99. package/docs/domElementsFromPoint.mjs.html +1 -1
  100. package/docs/domFadeIn.mjs.html +1 -1
  101. package/docs/domFadeOut.mjs.html +1 -1
  102. package/docs/domFind.mjs.html +1 -1
  103. package/docs/domFinds.mjs.html +1 -1
  104. package/docs/domGetAttr.mjs.html +1 -1
  105. package/docs/domGetBoudRect.mjs.html +1 -1
  106. package/docs/domGetBoudRectRefSelf.mjs.html +1 -1
  107. package/docs/domGetFileAccept.mjs.html +1 -1
  108. package/docs/domGetFiles.mjs.html +1 -1
  109. package/docs/domGetParents.mjs.html +1 -1
  110. package/docs/domGetPointFromEvent.mjs.html +1 -1
  111. package/docs/domGetScrollableParent.mjs.html +1 -1
  112. package/docs/domGetWindowSize.mjs.html +1 -1
  113. package/docs/domIsClientXYIn.mjs.html +1 -1
  114. package/docs/domIsFocused.mjs.html +1 -1
  115. package/docs/domIsStable.mjs.html +1 -1
  116. package/docs/domIsVisible.mjs.html +1 -1
  117. package/docs/domPrepend.mjs.html +1 -1
  118. package/docs/domRemove.mjs.html +1 -1
  119. package/docs/domRenderMarkdownMind.mjs.html +1 -1
  120. package/docs/domRenderMarkdownMindDyn.mjs.html +1 -1
  121. package/docs/domRipple.mjs.html +1 -1
  122. package/docs/domScrollIntoView.mjs.html +1 -1
  123. package/docs/domScrollTo.mjs.html +1 -1
  124. package/docs/domSetAttr.mjs.html +1 -1
  125. package/docs/domShowImages.mjs.html +1 -1
  126. package/docs/domShowImagesDyn.mjs.html +1 -1
  127. package/docs/domShowInputAndGetFiles.mjs.html +1 -1
  128. package/docs/domShowInputAndGetFilesU8Arrs.mjs.html +1 -1
  129. package/docs/domShowInputDatatime.mjs.html +1 -1
  130. package/docs/domTooltip.mjs.html +1 -1
  131. package/docs/domTriggerEvent.mjs.html +1 -1
  132. package/docs/domVirtualCreate.mjs.html +1 -1
  133. package/docs/domVirtualCreateQueue.mjs.html +1 -1
  134. package/docs/downloadExcelFileFromData.mjs.html +1 -1
  135. package/docs/downloadExcelFileFromDataDyn.mjs.html +1 -1
  136. package/docs/downloadFileFromB64.mjs.html +1 -1
  137. package/docs/downloadFileFromBlob.mjs.html +1 -1
  138. package/docs/downloadFileFromText.mjs.html +1 -1
  139. package/docs/downloadFileFromU8Arr.mjs.html +1 -1
  140. package/docs/dtmapping.mjs.html +1 -1
  141. package/docs/dtpick.mjs.html +1 -1
  142. package/docs/estimateTicks.mjs.html +1 -1
  143. package/docs/evEmit.mjs.html +1 -1
  144. package/docs/evEmitDelay.mjs.html +1 -1
  145. package/docs/evem.mjs.html +1 -1
  146. package/docs/examples/ex-blob2b64.html +1 -1
  147. package/docs/examples/ex-clearXSS.html +1 -1
  148. package/docs/examples/ex-clearXSSDyn.html +1 -1
  149. package/docs/examples/ex-colorConverter.html +1 -1
  150. package/docs/examples/ex-domCompareText.html +1 -1
  151. package/docs/examples/ex-domConvertToPic.html +1 -1
  152. package/docs/examples/ex-domDrag.html +1 -1
  153. package/docs/examples/ex-domDropFiles.html +1 -1
  154. package/docs/examples/ex-domIsVisible.html +1 -1
  155. package/docs/examples/ex-domRenderMarkdownMind.html +1 -1
  156. package/docs/examples/ex-domRenderMarkdownMindDyn.html +1 -1
  157. package/docs/examples/ex-domRipple.html +1 -1
  158. package/docs/examples/ex-domScrollIntoView.html +1 -1
  159. package/docs/examples/ex-domScrollTo.html +1 -1
  160. package/docs/examples/ex-domShowImages.html +1 -1
  161. package/docs/examples/ex-domShowImagesDyn.html +1 -1
  162. package/docs/examples/ex-domShowInputAndGetFiles.html +1 -1
  163. package/docs/examples/ex-domTooltip.html +1 -1
  164. package/docs/examples/ex-getFileHash.html +1 -1
  165. package/docs/examples/ex-getUserAgent.html +1 -1
  166. package/docs/examples/ex-getUserAgentDyn.html +1 -1
  167. package/docs/examples/ex-html2str.html +1 -1
  168. package/docs/examples/ex-obj2u8arr-u8arr2obj.html +1 -1
  169. package/docs/examples/ex-openLink.html +1 -1
  170. package/docs/examples/ex-str2aes-aes2str.html +1 -1
  171. package/docs/examples/ex-str2b64-b642str.html +1 -1
  172. package/docs/examples/ex-strFindFuzz.html +1 -1
  173. package/docs/examples/ex-timeTZ2expire-timeTZ2past.html +1 -1
  174. package/docs/examples/ex-xlsx.html +1 -1
  175. package/docs/execCli.mjs.html +1 -1
  176. package/docs/execCliCore.mjs.html +1 -1
  177. package/docs/execCliJsonRpc.mjs.html +1 -1
  178. package/docs/execCliSession.mjs.html +1 -1
  179. package/docs/execJsCode.mjs.html +1 -1
  180. package/docs/execProcess.mjs.html +1 -1
  181. package/docs/execProcessKillPid.mjs.html +1 -1
  182. package/docs/filepathToTree.mjs.html +1 -1
  183. package/docs/files2data.mjs.html +1 -1
  184. package/docs/flattenToConn.mjs.html +1 -1
  185. package/docs/flattenTree.mjs.html +1 -1
  186. package/docs/floor.mjs.html +1 -1
  187. package/docs/fsBuildReadStreamText.mjs.html +1 -1
  188. package/docs/fsBuildWriteStreamText.mjs.html +1 -1
  189. package/docs/fsCleanFolder.mjs.html +1 -1
  190. package/docs/fsCleanFolderCore.mjs.html +1 -1
  191. package/docs/fsCopyFile.mjs.html +1 -1
  192. package/docs/fsCopyFileCore.mjs.html +1 -1
  193. package/docs/fsCopyFolder.mjs.html +1 -1
  194. package/docs/fsCopyFolderCore.mjs.html +1 -1
  195. package/docs/fsCreateFile.mjs.html +1 -1
  196. package/docs/fsCreateFileCore.mjs.html +1 -1
  197. package/docs/fsCreateFolder.mjs.html +1 -1
  198. package/docs/fsCreateFolderCore.mjs.html +1 -1
  199. package/docs/fsDeleteFile.mjs.html +1 -1
  200. package/docs/fsDeleteFileCore.mjs.html +1 -1
  201. package/docs/fsDeleteFileSafe.mjs.html +1 -1
  202. package/docs/fsDeleteFileSafeCore.mjs.html +1 -1
  203. package/docs/fsDeleteFolder.mjs.html +1 -1
  204. package/docs/fsDeleteFolderCore.mjs.html +1 -1
  205. package/docs/fsDeleteFolderSafe.mjs.html +1 -1
  206. package/docs/fsDeleteFolderSafeCore.mjs.html +1 -1
  207. package/docs/fsDownloadFile.mjs.html +1 -1
  208. package/docs/fsDownloadFileCore.mjs.html +1 -1
  209. package/docs/fsEvem.mjs.html +1 -1
  210. package/docs/fsExists.mjs.html +1 -1
  211. package/docs/fsExistsCore.mjs.html +1 -1
  212. package/docs/fsGetFileBasicHash.mjs.html +1 -1
  213. package/docs/fsGetFileBasicHashCore.mjs.html +1 -1
  214. package/docs/fsGetFileHash.mjs.html +1 -1
  215. package/docs/fsGetFileHashCore.mjs.html +1 -1
  216. package/docs/fsGetFileXxHash.mjs.html +1 -1
  217. package/docs/fsGetFileXxHashCore.mjs.html +1 -1
  218. package/docs/fsGetFilesInFolder.mjs.html +1 -1
  219. package/docs/fsGetFilesInFolderCore.mjs.html +1 -1
  220. package/docs/fsGetFilesWithHashInFolder.mjs.html +1 -1
  221. package/docs/fsGetFilesWithHashInFolderCore.mjs.html +1 -1
  222. package/docs/fsGetFolderBasicHash.mjs.html +1 -1
  223. package/docs/fsGetFolderBasicHashCore.mjs.html +1 -1
  224. package/docs/fsGetFoldersInFolder.mjs.html +1 -1
  225. package/docs/fsGetFoldersInFolderCore.mjs.html +1 -1
  226. package/docs/fsIsFile.mjs.html +1 -1
  227. package/docs/fsIsFileCore.mjs.html +1 -1
  228. package/docs/fsIsFolder.mjs.html +1 -1
  229. package/docs/fsIsFolderCore.mjs.html +1 -1
  230. package/docs/fsMergeFiles.mjs.html +1 -1
  231. package/docs/fsMergeFilesCore.mjs.html +1 -1
  232. package/docs/fsReadJson.mjs.html +1 -1
  233. package/docs/fsReadText.mjs.html +1 -1
  234. package/docs/fsReadTextCore.mjs.html +1 -1
  235. package/docs/fsRenameFile.mjs.html +1 -1
  236. package/docs/fsRenameFileCore.mjs.html +1 -1
  237. package/docs/fsRenameFolder.mjs.html +1 -1
  238. package/docs/fsRenameFolderCore.mjs.html +1 -1
  239. package/docs/fsSrlog.mjs.html +1 -1
  240. package/docs/fsSyncFolder.mjs.html +1 -1
  241. package/docs/fsSyncFolderCore.mjs.html +1 -1
  242. package/docs/fsTask.mjs.html +1 -1
  243. package/docs/fsTaskCp.mjs.html +1 -1
  244. package/docs/fsTreeFolder.mjs.html +1 -1
  245. package/docs/fsTreeFolderCore.mjs.html +1 -1
  246. package/docs/fsTreeFolderWithHash.mjs.html +1 -1
  247. package/docs/fsTreeFolderWithHashCore.mjs.html +1 -1
  248. package/docs/fsWatchFile.mjs.html +1 -1
  249. package/docs/fsWatchFolder.mjs.html +1 -1
  250. package/docs/fsWriteJson.mjs.html +1 -1
  251. package/docs/fsWriteText.mjs.html +1 -1
  252. package/docs/fsWriteTextCore.mjs.html +1 -1
  253. package/docs/genGradientColor.mjs.html +1 -1
  254. package/docs/genID.mjs.html +1 -1
  255. package/docs/genIDSeq.mjs.html +1 -1
  256. package/docs/genPm.mjs.html +1 -1
  257. package/docs/getBufferSize.mjs.html +1 -1
  258. package/docs/getClearPathName.mjs.html +1 -1
  259. package/docs/getCsvStrFromData.mjs.html +1 -1
  260. package/docs/getDataFromExcelFileU8Arr.mjs.html +1 -1
  261. package/docs/getDataFromExcelFileU8ArrDyn.mjs.html +1 -1
  262. package/docs/getEnv.mjs.html +1 -1
  263. package/docs/getErrorMessage.mjs.html +1 -1
  264. package/docs/getExcelU8ArrFromData.mjs.html +1 -1
  265. package/docs/getExcelU8ArrFromWorkbook.mjs.html +1 -1
  266. package/docs/getExcelWorkbookFromData.mjs.html +1 -1
  267. package/docs/getExcelWorkbookFromWorksheet.mjs.html +1 -1
  268. package/docs/getExcelWorksheetFromData.mjs.html +1 -1
  269. package/docs/getFileAccept.mjs.html +1 -1
  270. package/docs/getFileBasicHash.mjs.html +1 -1
  271. package/docs/getFileHash.mjs.html +1 -1
  272. package/docs/getFileName.mjs.html +1 -1
  273. package/docs/getFileNameExt.mjs.html +1 -1
  274. package/docs/getFileTrueName.mjs.html +1 -1
  275. package/docs/getFileType.mjs.html +1 -1
  276. package/docs/getFileXxHash.mjs.html +1 -1
  277. package/docs/getGlobal.mjs.html +1 -1
  278. package/docs/getObjSize.mjs.html +1 -1
  279. package/docs/getPathParent.mjs.html +1 -1
  280. package/docs/getTimeBetween.mjs.html +1 -1
  281. package/docs/getTimeFromUnit.mjs.html +1 -1
  282. package/docs/getTimeHp.mjs.html +1 -1
  283. package/docs/getTimeObject.mjs.html +1 -1
  284. package/docs/getTimeRange.mjs.html +1 -1
  285. package/docs/getUserAgent.mjs.html +1 -1
  286. package/docs/getUserAgentDyn.mjs.html +1 -1
  287. package/docs/getltdtkeys.mjs.html +1 -1
  288. package/docs/global.html +1 -1
  289. package/docs/haskey.mjs.html +1 -1
  290. package/docs/html2str.mjs.html +1 -1
  291. package/docs/htmlDecode.mjs.html +1 -1
  292. package/docs/htmlEncode.mjs.html +1 -1
  293. package/docs/importResources.mjs.html +1 -1
  294. package/docs/index.html +1 -1
  295. package/docs/interp1.mjs.html +1 -1
  296. package/docs/isDate.mjs.html +1 -1
  297. package/docs/isDev.mjs.html +1 -1
  298. package/docs/isEle.mjs.html +1 -1
  299. package/docs/isEleExistByID.mjs.html +1 -1
  300. package/docs/isEmail.mjs.html +1 -1
  301. package/docs/isErr.mjs.html +1 -1
  302. package/docs/isIE.mjs.html +1 -1
  303. package/docs/isNarrow.mjs.html +1 -1
  304. package/docs/isStrHasCapital.mjs.html +1 -1
  305. package/docs/isStrHasLetter.mjs.html +1 -1
  306. package/docs/isStrHasLowerCase.mjs.html +1 -1
  307. package/docs/isStrHasNumber.mjs.html +1 -1
  308. package/docs/isStrHasSpace.mjs.html +1 -1
  309. package/docs/isStrHasSymbol.mjs.html +1 -1
  310. package/docs/isUserIdentify.mjs.html +1 -1
  311. package/docs/isUserName.mjs.html +1 -1
  312. package/docs/isUserPw.mjs.html +1 -1
  313. package/docs/isWindow.mjs.html +1 -1
  314. package/docs/isab.mjs.html +1 -1
  315. package/docs/isarr.mjs.html +1 -1
  316. package/docs/isarr0.mjs.html +1 -1
  317. package/docs/isarr1.mjs.html +1 -1
  318. package/docs/isbigint.mjs.html +1 -1
  319. package/docs/isblob.mjs.html +1 -1
  320. package/docs/isbol.mjs.html +1 -1
  321. package/docs/isday.mjs.html +1 -1
  322. package/docs/isearr.mjs.html +1 -1
  323. package/docs/iseobj.mjs.html +1 -1
  324. package/docs/iser.mjs.html +1 -1
  325. package/docs/isernot.mjs.html +1 -1
  326. package/docs/isestr.mjs.html +1 -1
  327. package/docs/isfile.mjs.html +1 -1
  328. package/docs/isfun.mjs.html +1 -1
  329. package/docs/isint.mjs.html +1 -1
  330. package/docs/ismonth.mjs.html +1 -1
  331. package/docs/isn0int.mjs.html +1 -1
  332. package/docs/isn0num.mjs.html +1 -1
  333. package/docs/isnan.mjs.html +1 -1
  334. package/docs/isnbr.mjs.html +1 -1
  335. package/docs/isnint.mjs.html +1 -1
  336. package/docs/isnnum.mjs.html +1 -1
  337. package/docs/isnull.mjs.html +1 -1
  338. package/docs/isnum.mjs.html +1 -1
  339. package/docs/isobj.mjs.html +1 -1
  340. package/docs/isobj0.mjs.html +1 -1
  341. package/docs/isp0int.mjs.html +1 -1
  342. package/docs/isp0num.mjs.html +1 -1
  343. package/docs/ispint.mjs.html +1 -1
  344. package/docs/ispm.mjs.html +1 -1
  345. package/docs/ispnum.mjs.html +1 -1
  346. package/docs/isstr.mjs.html +1 -1
  347. package/docs/isstr0.mjs.html +1 -1
  348. package/docs/istime.mjs.html +1 -1
  349. package/docs/istimeTZ.mjs.html +1 -1
  350. package/docs/istimems.mjs.html +1 -1
  351. package/docs/istimemsTZ.mjs.html +1 -1
  352. package/docs/isu16arr.mjs.html +1 -1
  353. package/docs/isu8arr.mjs.html +1 -1
  354. package/docs/isundefined.mjs.html +1 -1
  355. package/docs/j2o.mjs.html +1 -1
  356. package/docs/judge.mjs.html +1 -1
  357. package/docs/keysmat2ltdt.mjs.html +1 -1
  358. package/docs/ltdt2vrtsable.mjs.html +1 -1
  359. package/docs/ltdtDiff.mjs.html +1 -1
  360. package/docs/ltdtDiffByKey.mjs.html +1 -1
  361. package/docs/ltdtkeys2mat.mjs.html +1 -1
  362. package/docs/ltdtkeysheads2mat.mjs.html +1 -1
  363. package/docs/ltdtmapping.mjs.html +1 -1
  364. package/docs/ltdtmerge.mjs.html +1 -1
  365. package/docs/ltdtpick.mjs.html +1 -1
  366. package/docs/ltdtpull.mjs.html +1 -1
  367. package/docs/mat2ltdt.mjs.html +1 -1
  368. package/docs/matConcat.mjs.html +1 -1
  369. package/docs/matDiff.mjs.html +1 -1
  370. package/docs/matat.mjs.html +1 -1
  371. package/docs/now2str.mjs.html +1 -1
  372. package/docs/now2strp.mjs.html +1 -1
  373. package/docs/nowDay2str.mjs.html +1 -1
  374. package/docs/nowms2str.mjs.html +1 -1
  375. package/docs/nowms2strp.mjs.html +1 -1
  376. package/docs/o2j.mjs.html +1 -1
  377. package/docs/obj2b64.mjs.html +1 -1
  378. package/docs/obj2pb64.mjs.html +1 -1
  379. package/docs/obj2str.mjs.html +1 -1
  380. package/docs/obj2stru8arr.mjs.html +1 -1
  381. package/docs/obj2u8arr.mjs.html +1 -1
  382. package/docs/objSortBy.mjs.html +1 -1
  383. package/docs/objSortByKeys.mjs.html +1 -1
  384. package/docs/oo.mjs.html +1 -1
  385. package/docs/openLink.mjs.html +1 -1
  386. package/docs/pb642obj.mjs.html +1 -1
  387. package/docs/pmChain.mjs.html +1 -1
  388. package/docs/pmConvertResolve.mjs.html +1 -1
  389. package/docs/pmDebounce.mjs.html +1 -1
  390. package/docs/pmHook.mjs.html +1 -1
  391. package/docs/pmHookReject.mjs.html +1 -1
  392. package/docs/pmIni.mjs.html +1 -1
  393. package/docs/pmInvResolve.mjs.html +1 -1
  394. package/docs/pmKeyMutex.mjs.html +1 -1
  395. package/docs/pmMap.mjs.html +1 -1
  396. package/docs/pmQueue.mjs.html +1 -1
  397. package/docs/pmSeries.mjs.html +1 -1
  398. package/docs/pmThrottle.mjs.html +1 -1
  399. package/docs/pmTimeout.mjs.html +1 -1
  400. package/docs/preciseNum.mjs.html +1 -1
  401. package/docs/pseudoRandom.mjs.html +1 -1
  402. package/docs/pseudoRandomIntRange.mjs.html +1 -1
  403. package/docs/pseudoRandomIntsNdpRange.mjs.html +1 -1
  404. package/docs/pseudoRandomIntsRange.mjs.html +1 -1
  405. package/docs/pseudoRandomRange.mjs.html +1 -1
  406. package/docs/pseudoRandomsRange.mjs.html +1 -1
  407. package/docs/queue.mjs.html +1 -1
  408. package/docs/random.mjs.html +1 -1
  409. package/docs/randomIntRange.mjs.html +1 -1
  410. package/docs/randomIntsNdpRange.mjs.html +1 -1
  411. package/docs/randomIntsRange.mjs.html +1 -1
  412. package/docs/randomRange.mjs.html +1 -1
  413. package/docs/randomsRange.mjs.html +1 -1
  414. package/docs/rang.mjs.html +1 -1
  415. package/docs/rep.mjs.html +1 -1
  416. package/docs/repObj.mjs.html +1 -1
  417. package/docs/replace.mjs.html +1 -1
  418. package/docs/replaceObj.mjs.html +1 -1
  419. package/docs/replacePlus.mjs.html +1 -1
  420. package/docs/round.mjs.html +1 -1
  421. package/docs/secd.mjs.html +1 -1
  422. package/docs/sep.mjs.html +1 -1
  423. package/docs/sepInt.mjs.html +1 -1
  424. package/docs/setIntervalHp.mjs.html +1 -1
  425. package/docs/setTimeoutHp.mjs.html +1 -1
  426. package/docs/sind.mjs.html +1 -1
  427. package/docs/split.mjs.html +1 -1
  428. package/docs/str2aes.mjs.html +1 -1
  429. package/docs/str2b64.mjs.html +1 -1
  430. package/docs/str2hint.mjs.html +1 -1
  431. package/docs/str2md5.mjs.html +1 -1
  432. package/docs/str2obj.mjs.html +1 -1
  433. package/docs/str2sha.mjs.html +1 -1
  434. package/docs/str2sha512.mjs.html +1 -1
  435. package/docs/str2u8arr.mjs.html +1 -1
  436. package/docs/strCompare.mjs.html +1 -1
  437. package/docs/strCompareForVersion.mjs.html +1 -1
  438. package/docs/strDiff.mjs.html +1 -1
  439. package/docs/strDiffToHtml.mjs.html +1 -1
  440. package/docs/strFindFuzz.mjs.html +1 -1
  441. package/docs/strFindSimilar.mjs.html +1 -1
  442. package/docs/strTruncate.mjs.html +1 -1
  443. package/docs/strdelleft.mjs.html +1 -1
  444. package/docs/strdelright.mjs.html +1 -1
  445. package/docs/strleft.mjs.html +1 -1
  446. package/docs/strmid.mjs.html +1 -1
  447. package/docs/strright.mjs.html +1 -1
  448. package/docs/stru8arr2obj.mjs.html +1 -1
  449. package/docs/tand.mjs.html +1 -1
  450. package/docs/time2day.mjs.html +1 -1
  451. package/docs/time2hour.mjs.html +1 -1
  452. package/docs/time2min.mjs.html +1 -1
  453. package/docs/timeTZ2day.mjs.html +1 -1
  454. package/docs/timeTZ2expire.mjs.html +1 -1
  455. package/docs/timeTZ2hour.mjs.html +1 -1
  456. package/docs/timeTZ2min.mjs.html +1 -1
  457. package/docs/timeTZ2past.mjs.html +1 -1
  458. package/docs/timems2day.mjs.html +1 -1
  459. package/docs/timems2hour.mjs.html +1 -1
  460. package/docs/timems2min.mjs.html +1 -1
  461. package/docs/timems2time.mjs.html +1 -1
  462. package/docs/timemsTZ2day.mjs.html +1 -1
  463. package/docs/timemsTZ2expire.mjs.html +1 -1
  464. package/docs/timemsTZ2hour.mjs.html +1 -1
  465. package/docs/timemsTZ2min.mjs.html +1 -1
  466. package/docs/timemsTZ2past.mjs.html +1 -1
  467. package/docs/timemsTZ2time.mjs.html +1 -1
  468. package/docs/treeObj.mjs.html +1 -1
  469. package/docs/treeToFilepath.mjs.html +1 -1
  470. package/docs/trim.mjs.html +1 -1
  471. package/docs/u16arr2b64.mjs.html +1 -1
  472. package/docs/u16arr2u8arr.mjs.html +1 -1
  473. package/docs/u8arr2ab.mjs.html +1 -1
  474. package/docs/u8arr2b64.mjs.html +1 -1
  475. package/docs/u8arr2blob.mjs.html +1 -1
  476. package/docs/u8arr2bs.mjs.html +1 -1
  477. package/docs/u8arr2obj.mjs.html +1 -1
  478. package/docs/u8arr2str.mjs.html +1 -1
  479. package/docs/u8arr2u16arr.mjs.html +1 -1
  480. package/docs/urlParse.mjs.html +1 -1
  481. package/docs/verifyValue.mjs.html +1 -1
  482. package/docs/waitFun.mjs.html +1 -1
  483. package/docs/wsemi.html +127 -7
  484. package/package.json +1 -1
  485. package/src/domDetect.mjs +340 -204
  486. package/test/domDetect.test.mjs +1248 -11
  487. package/toolfp/dist/wsemip.umd.js +1 -1
  488. package/toolparfor/dist/wpf.umd.js +1 -1
  489. package/toolparfor/dist/wpf_pmSeries.umd.js +1 -1
@@ -3,9 +3,9 @@ import domDetect from '../src/domDetect.mjs'
3
3
 
4
4
 
5
5
  //本函數於瀏覽器執行, 以ResizeObserver、MutationObserver、window與document偵測元素尺寸, nodejs無此等物件, 故以假環境覆蓋全域
6
- // 假ResizeObserver與假MutationObserver只記錄觀察對象, 由測試呼叫fireRO、fireMO觸發回呼, 可精確控制瀏覽器於何時回報
7
- // 假元素之尺寸、是否在頁面中與display由測試直接設定
8
- // 瀏覽器本身之語義(只觀察content-box時漏掉padding變化、行內元素不回報、開始觀察必回報一次、捲軸佔寬等)非本檔所能驗證, 已另以真瀏覽器實測
6
+ // 假ResizeObserver與假MutationObserver只記錄觀察對象, 由測試呼叫fireRO、fireMO或frame觸發回呼, 可精確控制瀏覽器於何時回報
7
+ // 假元素之尺寸、是否在頁面中、display與父元素由測試直接設定; 隱藏以display:none且尺寸0模擬(延後發出前之確認只看是否被繪製, 不讀外框)
8
+ // 瀏覽器本身之語義(只觀察content-box時漏掉padding變化、行內元素不回報、開始觀察必回報一次、同盒再observe不重建觀察、捲軸佔寬、確認不強制重排等)非本檔所能驗證, 已另以真瀏覽器實測
9
9
 
10
10
 
11
11
  let sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
@@ -16,6 +16,20 @@ class Ele {
16
16
  this.nodeType = 1
17
17
  this.display = o.display || 'block'
18
18
  this.isConnected = o.connected !== false
19
+ this.parentElement = o.parent || null
20
+ if (o.cv) {
21
+ //checkVisibility, 同瀏覽器: 不在頁面中或自身、祖先為display:none者為false
22
+ this.checkVisibility = () => {
23
+ let e = this
24
+ while (e) {
25
+ if (!e.isConnected || e.display === 'none') {
26
+ return false
27
+ }
28
+ e = e.parentElement
29
+ }
30
+ return true
31
+ }
32
+ }
19
33
  this.size(w, h, o.cw, o.ch)
20
34
  }
21
35
 
@@ -28,6 +42,19 @@ class Ele {
28
42
  }
29
43
 
30
44
 
45
+ //hide, show, 以display隱藏與顯示(同v-show), 隱藏時尺寸為0
46
+ function hide(el) {
47
+ el.display = 'none'
48
+ el.size(0, 0)
49
+ }
50
+
51
+
52
+ function show(el, w, h) {
53
+ el.display = 'block'
54
+ el.size(w, h)
55
+ }
56
+
57
+
31
58
  let envCur = null
32
59
 
33
60
 
@@ -44,19 +71,28 @@ function mkEnv(o = {}) {
44
71
  constructor(cb) {
45
72
  this.cb = cb
46
73
  this.targets = new Map()
74
+ this.last = new Map() //各觀察對象上次回報之尺寸, 新建之觀察為undefined(與瀏覽器相同, 開始觀察必回報一次)
47
75
  env.ros.push(this)
48
76
  }
49
77
 
50
78
  observe(t, opt) {
51
- this.targets.set(t, (opt && opt.box) ? opt.box : 'content-box')
79
+ //同瀏覽器(Blink、Gecko)之實作: 已以同一盒觀察者再observe直接返回, 不重建觀察(上次回報之尺寸不歸零), 與規範所寫之先unobserve不同
80
+ let box = (opt && opt.box) ? opt.box : 'content-box'
81
+ if (this.targets.get(t) === box) {
82
+ return
83
+ }
84
+ this.targets.set(t, box)
85
+ this.last.set(t, undefined)
52
86
  }
53
87
 
54
88
  unobserve(t) {
55
89
  this.targets.delete(t)
90
+ this.last.delete(t)
56
91
  }
57
92
 
58
93
  disconnect() {
59
94
  this.targets.clear()
95
+ this.last.clear()
60
96
  }
61
97
  }
62
98
 
@@ -90,7 +126,8 @@ function mkEnv(o = {}) {
90
126
  env.listeners = env.listeners.filter((l) => !(l.type === type && l.fn === fn))
91
127
  },
92
128
  getComputedStyle: (e) => {
93
- return { display: e.display }
129
+ //未插入DOM之元素取不到計算後樣式, display為空字串, 與瀏覽器一致
130
+ return { display: e.isConnected ? e.display : '' }
94
131
  },
95
132
  }
96
133
  env.document = { documentElement: { name: 'html' } }
@@ -107,6 +144,24 @@ function mkEnv(o = {}) {
107
144
  }
108
145
  }
109
146
 
147
+ //frame, 逐幀模型: 同瀏覽器之ResizeObserver, 各觀察對象所觀察之盒尺寸與上次回報不同(新建之觀察必不同)時才回報
148
+ // fireRO為測試直接指定回報, 不理會上次回報之尺寸; 需要「尺寸未變即不回報」之語義者用frame
149
+ env.frame = () => {
150
+ for (let ro of env.ros) {
151
+ let entries = []
152
+ for (let [t, box] of ro.targets) {
153
+ let cur = box === 'border-box' ? `${t.offsetWidth}x${t.offsetHeight}` : `${t.clientWidth}x${t.clientHeight}`
154
+ if (ro.last.get(t) !== cur) {
155
+ ro.last.set(t, cur)
156
+ entries.push({ target: t })
157
+ }
158
+ }
159
+ if (entries.length > 0) {
160
+ ro.cb(entries, ro)
161
+ }
162
+ }
163
+ }
164
+
110
165
  //fireMO, DOM變動
111
166
  env.fireMO = () => {
112
167
  for (let m of env.mos) {
@@ -183,8 +238,9 @@ describe(`domDetect`, function() {
183
238
  let r = env.dd(() => el)
184
239
  env.fireRO(el)
185
240
  await sleep(10)
186
- let snew = { offsetWidth: 300, offsetHeight: 40, clientWidth: 290, clientHeight: 30, windowWidth: 1200, windowHeight: 800 }
187
- let sold = { offsetWidth: 0, offsetHeight: 0, clientWidth: 0, clientHeight: 0, windowWidth: 0, windowHeight: 0 }
241
+ //width、height為比較用尺寸, 未給getSize時同offsetWidth、offsetHeight
242
+ let snew = { offsetWidth: 300, offsetHeight: 40, clientWidth: 290, clientHeight: 30, windowWidth: 1200, windowHeight: 800, width: 300, height: 40 }
243
+ let sold = { offsetWidth: 0, offsetHeight: 0, clientWidth: 0, clientHeight: 0, windowWidth: 0, windowHeight: 0, width: 0, height: 0 }
188
244
  let smode = { width: 'larger', height: 'larger' }
189
245
  assert.strict.deepStrictEqual(r.resize, [{ sold, snew, smode, ele: el }])
190
246
  assert.strict.deepStrictEqual(r.rww, [{ sold, snew, smode, ele: el, from: 'dom' }])
@@ -299,6 +355,21 @@ describe(`domDetect`, function() {
299
355
  assert.strict.deepStrictEqual(r.resize.map((m) => [m.snew.offsetHeight, m.smode.width, m.smode.height]), [[40, 'larger', 'larger'], [60, '', 'larger']])
300
356
  })
301
357
 
358
+ it(`should give no direction for an axis within the tolerance when the other axis triggers`, async function() {
359
+ //寬先+1px(未超過容許誤差, 不發出), 之後高+20px觸發發出: 寬之方向須為空, 否則依方向判斷變寬或變窄之使用端會誤動作
360
+ let env = mkEnv()
361
+ let el = new Ele(300, 40)
362
+ let r = env.dd(() => el)
363
+ env.fireRO(el)
364
+ el.size(301, 40)
365
+ env.fireRO(el)
366
+ el.size(301, 60)
367
+ env.fireRO(el)
368
+ await sleep(10)
369
+ assert.strict.deepStrictEqual(r.resize.map((m) => [m.snew.offsetWidth, m.snew.offsetHeight, m.smode.width, m.smode.height]), [[300, 40, 'larger', 'larger'], [301, 60, '', 'larger']])
370
+ assert.strict.deepStrictEqual([r.resize[1].sold.offsetWidth, r.resize[1].sold.offsetHeight], [300, 40])
371
+ })
372
+
302
373
  //--- 發出時點與清除 ---
303
374
 
304
375
  it(`should emit asynchronously by default and synchronously with sync`, async function() {
@@ -374,11 +445,12 @@ describe(`domDetect`, function() {
374
445
  it(`should stop window events after clear`, function() {
375
446
  let env = mkEnv()
376
447
  let el = new Ele(300, 40)
377
- let r = env.dd(() => el)
448
+ let r = env.dd(() => el, { sync: true })
449
+ env.fireRO(el)
378
450
  env.resizeWindow(1000, 800)
379
451
  r.d.clear()
380
452
  env.resizeWindow(900, 800)
381
- assert.strict.deepStrictEqual(r.rww.map((m) => m.snew.windowWidth), [1000])
453
+ assert.strict.deepStrictEqual(r.rww.filter((m) => m.from === 'window').map((m) => m.snew.windowWidth), [1000])
382
454
  })
383
455
 
384
456
  //--- 元素之取得與替換 ---
@@ -408,6 +480,7 @@ describe(`domDetect`, function() {
408
480
  let cur = a
409
481
  let r = env.dd(() => cur)
410
482
  env.fireRO(a)
483
+ await sleep(10) //舊節點之尺寸於替換前發出; 延後發出前即被替換者不發出, 見下方延後發出之測試
411
484
  let b = new Ele(320, 40)
412
485
  cur = b
413
486
  a.isConnected = false
@@ -428,6 +501,7 @@ describe(`domDetect`, function() {
428
501
  let r = env.dd(() => cur)
429
502
  env.fireRO(a)
430
503
  assert.strict.deepStrictEqual(env.moActive(), 0)
504
+ await sleep(10) //舊節點之尺寸於隱藏前發出; 延後發出前即隱藏者不發出, 見下方延後發出之測試
431
505
  a.size(0, 0)
432
506
  env.fireRO(a)
433
507
  assert.strict.deepStrictEqual(env.moActive(), 1)
@@ -530,6 +604,160 @@ describe(`domDetect`, function() {
530
604
  assert.strict.deepStrictEqual([n, nStopped], [2, 2])
531
605
  })
532
606
 
607
+ it(`should measure an inline element that was taken before being connected`, async function() {
608
+ //Vue指令之bind時序: 元素於插入DOM前即建立偵測器, 此時取不到display而判為非行內; 插入後須於DOM變動時重判為行內並改定期量測
609
+ let env = mkEnv()
610
+ let el = new Ele(27, 18, { display: 'inline', connected: false })
611
+ let r = env.dd(() => el, { timeInterval: 5 })
612
+ await sleep(30)
613
+ el.isConnected = true
614
+ env.fireMO()
615
+ await sleep(30)
616
+ el.size(137, 18)
617
+ await sleep(30)
618
+ assert.strict.deepStrictEqual(ws(r), [27, 137])
619
+ })
620
+
621
+ it(`should resume measuring an inline element after it is removed and inserted again`, async function() {
622
+ //如keep-alive: 行內元素移出DOM時定期量測量到尺寸0並判為非行內而停止, 插回後須於DOM變動時重判為行內並恢復定期量測
623
+ let env = mkEnv()
624
+ let el = new Ele(27, 18, { display: 'inline' })
625
+ let r = env.dd(() => el, { timeInterval: 5 })
626
+ await sleep(30)
627
+ el.isConnected = false
628
+ el.size(0, 0)
629
+ await sleep(30)
630
+ el.isConnected = true
631
+ el.size(27, 18)
632
+ env.fireMO()
633
+ await sleep(30)
634
+ el.size(137, 18)
635
+ await sleep(30)
636
+ assert.strict.deepStrictEqual(ws(r), [27, 27, 137])
637
+ })
638
+
639
+ it(`should keep measuring an inline element while it hides itself, and pick up the size once shown`, async function() {
640
+ //如v-show: 行內元素自身設display:none時取得之display為none而非inline, 若因此停止定期量測, 顯示後行內元素ResizeObserver又不回報即永不量測
641
+ let env = mkEnv()
642
+ let el = new Ele(27, 18, { display: 'inline' })
643
+ let r = env.dd(() => el, { timeInterval: 5 })
644
+ await sleep(30)
645
+ el.display = 'none'
646
+ el.size(0, 0)
647
+ await sleep(30)
648
+ el.display = 'inline'
649
+ el.size(27, 18)
650
+ await sleep(30)
651
+ el.size(137, 18)
652
+ await sleep(30)
653
+ assert.strict.deepStrictEqual(ws(r), [27, 27, 137])
654
+ })
655
+
656
+ it(`should resume an inline element that was hidden, removed and inserted again while hidden`, async function() {
657
+ //隱藏期間移出(不在頁面中而停止量測)再插回時仍為隱藏, 須依曾判定為行內而恢復定期量測
658
+ let env = mkEnv()
659
+ let el = new Ele(27, 18, { display: 'inline' })
660
+ let r = env.dd(() => el, { timeInterval: 5 })
661
+ await sleep(30)
662
+ el.display = 'none'
663
+ el.size(0, 0)
664
+ await sleep(30)
665
+ el.isConnected = false
666
+ await sleep(30)
667
+ el.isConnected = true
668
+ env.fireMO()
669
+ await sleep(30)
670
+ el.display = 'inline'
671
+ el.size(27, 18)
672
+ await sleep(30)
673
+ el.size(137, 18)
674
+ await sleep(30)
675
+ assert.strict.deepStrictEqual(ws(r), [27, 27, 137])
676
+ })
677
+
678
+ it(`should not poll a hidden element that was never measured as inline`, async function() {
679
+ //區塊元素自身隱藏時不定期量測(如v-show隱藏之元件), 顯示時由ResizeObserver回報
680
+ let env = mkEnv()
681
+ let el = new Ele(300, 40)
682
+ let r = env.dd(() => el, { timeInterval: 5 })
683
+ env.fireRO(el)
684
+ el.display = 'none'
685
+ el.size(0, 0)
686
+ env.fireRO(el)
687
+ el.display = 'block'
688
+ el.size(300, 40)
689
+ await sleep(30)
690
+ let n = r.resize.length
691
+ env.fireRO(el)
692
+ await sleep(10)
693
+ assert.strict.deepStrictEqual([n, ws(r)], [1, [300, 300]])
694
+ })
695
+
696
+ it(`should not carry the inline state over to a replacement element`, async function() {
697
+ //是否曾為行內只屬於當時之元素; 換成隱藏之新節點時不得沿用而定期量測
698
+ let env = mkEnv()
699
+ let a = new Ele(27, 18, { display: 'inline' })
700
+ let cur = a
701
+ let r = env.dd(() => cur, { timeInterval: 5 })
702
+ await sleep(30)
703
+ let b = new Ele(0, 0, { display: 'none' })
704
+ cur = b
705
+ await sleep(30)
706
+ b.display = 'block'
707
+ b.size(300, 40)
708
+ await sleep(30)
709
+ let n = r.resize.length
710
+ env.fireRO(b)
711
+ await sleep(10)
712
+ assert.strict.deepStrictEqual([n, ws(r)], [1, [27, 300]])
713
+ })
714
+
715
+ it(`should give no direction on window events after a change within the tolerance`, async function() {
716
+ //元素+1px未超過容許誤差而不發出, 其後之視窗事件不得帶出該方向
717
+ let env = mkEnv()
718
+ let el = new Ele(300, 40)
719
+ let r = env.dd(() => el)
720
+ env.fireRO(el)
721
+ await sleep(10)
722
+ el.size(301, 40)
723
+ env.fireRO(el)
724
+ await sleep(10)
725
+ env.resizeWindow(1000, 800)
726
+ let w = r.rww.filter((m) => m.from === 'window')
727
+ assert.strict.deepStrictEqual(w.map((m) => [m.snew.offsetWidth, m.smode]), [[301, { width: '', height: '' }]])
728
+ })
729
+
730
+ it(`should give no direction on window events after a change of the border box only`, async function() {
731
+ //只改padding時只有border-box之觀察器回報, 發出後無第二次回報可把方向歸零; 其後之視窗事件不得帶出該方向
732
+ let env = mkEnv()
733
+ let el = new Ele(300, 40)
734
+ let r = env.dd(() => el)
735
+ env.fireRO(el)
736
+ await sleep(10)
737
+ el.size(340, 40, 300, 40)
738
+ env.fireRO(el, 'border-box')
739
+ await sleep(10)
740
+ env.resizeWindow(1000, 800)
741
+ let w = r.rww.filter((m) => m.from === 'window')
742
+ assert.strict.deepStrictEqual(ws(r), [300, 340])
743
+ assert.strict.deepStrictEqual(w.map((m) => m.smode), [{ width: '', height: '' }])
744
+ })
745
+
746
+ it(`should carry the comparison baseline as sold on window events`, async function() {
747
+ //sold為比較基準(上次發出事件時之尺寸), 視窗事件與dom事件同義; 未超過容許誤差之最新量測只出現於snew
748
+ let env = mkEnv()
749
+ let el = new Ele(300, 40)
750
+ let r = env.dd(() => el)
751
+ env.fireRO(el)
752
+ await sleep(10)
753
+ el.size(301, 40)
754
+ env.fireRO(el)
755
+ await sleep(10)
756
+ env.resizeWindow(1000, 800)
757
+ let w = r.rww.filter((m) => m.from === 'window')
758
+ assert.strict.deepStrictEqual(w.map((m) => [m.sold.offsetWidth, m.snew.offsetWidth]), [[300, 301]])
759
+ })
760
+
533
761
  it(`should carry the current window size in resizeWithWindow from the window`, async function() {
534
762
  //F3、G4 元素尺寸未變時, 視窗事件之視窗尺寸仍須為當下之值
535
763
  let env = mkEnv()
@@ -618,9 +846,9 @@ describe(`domDetect`, function() {
618
846
  }
619
847
  return el
620
848
  }, { mode: 'polling', timeInterval: 5 })
621
- await sleep(25)
849
+ await sleep(50)
622
850
  ok = true
623
- await sleep(25)
851
+ await sleep(50) //輪詢與延後發出之計時器於負載下可能延誤, 等待放寬
624
852
  assert.strict.deepStrictEqual(ws(r), [300])
625
853
  })
626
854
 
@@ -639,6 +867,929 @@ describe(`domDetect`, function() {
639
867
  }
640
868
  })
641
869
 
870
+ //--- 延後發出之再確認與略過後之重新比較 ---
871
+
872
+ it(`should not emit when the element is hidden between the measurement and the deferred emit`, async function() {
873
+ //缺陷一: 量測後、延後發出前元素已隱藏, 若照發則監聽器讀到尺寸0
874
+ let env = mkEnv()
875
+ let el = new Ele(300, 36)
876
+ let live = []
877
+ let r = env.dd(() => el)
878
+ r.d.on('resize', () => live.push(el.offsetHeight))
879
+ env.frame()
880
+ await sleep(5)
881
+ el.size(300, 40)
882
+ env.frame()
883
+ hide(el)
884
+ await sleep(5)
885
+ assert.strict.deepStrictEqual([r.resize.map((m) => m.snew.offsetHeight), live], [[36], [36]])
886
+ })
887
+
888
+ it(`should not emit when an ancestor hides the element between the measurement and the deferred emit, with or without checkVisibility`, async function() {
889
+ //確認元素是否被繪製: 有checkVisibility時以其判定, 否則逐層檢查display
890
+ for (let cv of [false, true]) {
891
+ let env = mkEnv()
892
+ let parent = new Ele(400, 100, { cv })
893
+ let el = new Ele(300, 36, { cv, parent })
894
+ let r = env.dd(() => el)
895
+ env.frame()
896
+ await sleep(5)
897
+ el.size(300, 40)
898
+ env.frame()
899
+ parent.display = 'none'
900
+ el.size(0, 0)
901
+ await sleep(5)
902
+ assert.strict.deepStrictEqual(r.resize.map((m) => m.snew.offsetHeight), [36], `cv=${cv}`)
903
+ for (let d of env.dds) {
904
+ d.clear()
905
+ }
906
+ }
907
+ })
908
+
909
+ it(`should use checkVisibility when available, which also covers content-visibility`, async function() {
910
+ //checkVisibility另涵蓋content-visibility:hidden等display以外之不被繪製, 有則以其判定; 以其回傳false而display非none模擬
911
+ let env = mkEnv()
912
+ let el = new Ele(300, 36, { cv: true })
913
+ let r = env.dd(() => el)
914
+ env.frame()
915
+ await sleep(5)
916
+ el.size(300, 40)
917
+ env.frame()
918
+ el.checkVisibility = () => false
919
+ await sleep(5)
920
+ assert.strict.deepStrictEqual(r.resize.map((m) => m.snew.offsetHeight), [36])
921
+ })
922
+
923
+ it(`should still emit when the element is rendered but collapsed to zero between the measurement and the deferred emit`, async function() {
924
+ //刻意(同1.9.5): 發出前之確認只計算樣式不讀外框(讀外框則多偵測器時每次確認皆強制重排), 仍被繪製而尺寸縮為0者照發量測時之快照
925
+ let env = mkEnv()
926
+ let el = new Ele(300, 36)
927
+ let r = env.dd(() => el)
928
+ env.frame()
929
+ await sleep(5)
930
+ el.size(300, 40)
931
+ env.frame()
932
+ el.size(300, 0)
933
+ await sleep(5)
934
+ assert.strict.deepStrictEqual(r.resize.map((m) => m.snew.offsetHeight), [36, 40])
935
+ })
936
+
937
+ it(`should emit the change once shown again at the same size before the next frame`, async function() {
938
+ //略過後元素於下一幀前恢復顯示且尺寸不變時, 瀏覽器不再回報(上次回報之尺寸未變), 須重新觀察使下一幀回報, 否則此次變化永不發出
939
+ let env = mkEnv()
940
+ let el = new Ele(300, 36)
941
+ let r = env.dd(() => el)
942
+ env.frame()
943
+ await sleep(5)
944
+ el.size(300, 40)
945
+ env.frame()
946
+ hide(el)
947
+ await sleep(5)
948
+ show(el, 300, 40)
949
+ env.frame()
950
+ await sleep(5)
951
+ assert.strict.deepStrictEqual(r.resize.map((m) => [m.snew.offsetHeight, m.sold.offsetHeight, m.smode.height]), [[36, 0, 'larger'], [40, 0, 'larger']])
952
+ })
953
+
954
+ it(`should wait for DOM changes right after skipping an element hidden by display`, async function() {
955
+ //略過時最新量測歸0而依[尺寸為0時等待DOM變動]登記等待: 元素仍在頁面中(display:none)者亦然, 不依賴重新觀察之首次回報(舊版Safari對0尺寸之新觀察不回報), 否則隱藏期間被替換之節點無從跟隨
956
+ let env = mkEnv()
957
+ let el = new Ele(300, 36)
958
+ let r = env.dd(() => el)
959
+ env.frame()
960
+ await sleep(5)
961
+ let waitingVisible = env.moActive()
962
+ el.size(300, 40)
963
+ env.frame()
964
+ hide(el)
965
+ await sleep(5)
966
+ assert.strict.deepStrictEqual([ws(r), waitingVisible, env.moActive()], [[300], 0, 1])
967
+ })
968
+
969
+ it(`should emit a different size once shown again after a skipped emit`, async function() {
970
+ let env = mkEnv()
971
+ let el = new Ele(300, 36)
972
+ let r = env.dd(() => el)
973
+ env.frame()
974
+ await sleep(5)
975
+ el.size(300, 40)
976
+ env.frame()
977
+ hide(el)
978
+ await sleep(5)
979
+ show(el, 300, 50)
980
+ env.frame()
981
+ await sleep(5)
982
+ assert.strict.deepStrictEqual(r.resize.map((m) => m.snew.offsetHeight), [36, 50])
983
+ })
984
+
985
+ it(`should not emit when the element is removed before the deferred emit, and wait for it to come back`, async function() {
986
+ let env = mkEnv()
987
+ let el = new Ele(300, 36)
988
+ let r = env.dd(() => el)
989
+ env.frame()
990
+ await sleep(5)
991
+ el.size(300, 40)
992
+ env.frame()
993
+ el.isConnected = false
994
+ el.size(0, 0)
995
+ await sleep(5)
996
+ let n = r.resize.length
997
+ let waitingAfterSkip = env.moActive() //略過時最新量測歸0, 即登記等待DOM變動, 不依賴重新觀察之首次回報(舊版Safari對0尺寸之新觀察不回報)
998
+ env.frame()
999
+ let waitingWhileRemoved = env.moActive()
1000
+ el.isConnected = true
1001
+ el.size(300, 40)
1002
+ env.fireMO()
1003
+ env.frame()
1004
+ await sleep(5)
1005
+ assert.strict.deepStrictEqual([n, waitingAfterSkip, waitingWhileRemoved, r.resize.map((m) => m.snew.offsetHeight), env.moActive()], [1, 1, 1, [36, 40], 0])
1006
+ })
1007
+
1008
+ it(`should re-observe the current element when the skipped emit belonged to a replaced node`, async function() {
1009
+ //舊節點之延後發出因已移出而略過, 新節點已於同一幀比較且同尺寸(未判變化)時, 須重新觀察新節點, 否則使用端永遠停在舊尺寸
1010
+ let env = mkEnv()
1011
+ let a = new Ele(300, 36)
1012
+ let cur = a
1013
+ let r = env.dd(() => cur)
1014
+ env.frame()
1015
+ await sleep(5)
1016
+ a.size(300, 40)
1017
+ env.frame()
1018
+ let b = new Ele(300, 40)
1019
+ cur = b
1020
+ a.isConnected = false
1021
+ a.size(0, 0)
1022
+ env.frame() //舊節點回報尺寸0時改觀察新節點, 新節點與基準同尺寸而不判變化
1023
+ env.frame() //新節點於兩個觀察器皆已回報, 之後尺寸未變即不再回報
1024
+ await sleep(5)
1025
+ let n = r.resize.length
1026
+ env.frame()
1027
+ await sleep(5)
1028
+ let last = r.resize[r.resize.length - 1]
1029
+ assert.strict.deepStrictEqual([n, r.resize.map((m) => m.snew.offsetHeight), last.ele === b], [1, [36, 40], true])
1030
+ })
1031
+
1032
+ it(`should keep a newer baseline when an older deferred emit is skipped`, async function() {
1033
+ //較舊之延後發出略過時, 若其後已判定較新之變化, 不得把基準歸0, 否則之後容許誤差內之變化被誤判而多發
1034
+ let env = mkEnv()
1035
+ let a = new Ele(300, 36)
1036
+ let cur = a
1037
+ let r = env.dd(() => cur)
1038
+ env.frame()
1039
+ await sleep(5)
1040
+ a.size(300, 40)
1041
+ env.frame()
1042
+ let b = new Ele(300, 60)
1043
+ cur = b
1044
+ a.isConnected = false
1045
+ a.size(0, 0)
1046
+ env.frame()
1047
+ await sleep(5)
1048
+ b.size(301, 60)
1049
+ env.frame()
1050
+ await sleep(5)
1051
+ assert.strict.deepStrictEqual(r.resize.map((m) => [m.snew.offsetWidth, m.snew.offsetHeight]), [[300, 36], [300, 60]])
1052
+ })
1053
+
1054
+ it(`should not emit window events after a skipped emit until the element is measured again`, async function() {
1055
+ //略過時元素已隱藏, 最新量測亦歸0, 其後之視窗事件不得帶出隱藏前之尺寸
1056
+ let env = mkEnv()
1057
+ let el = new Ele(300, 36)
1058
+ let r = env.dd(() => el)
1059
+ env.frame()
1060
+ await sleep(5)
1061
+ el.size(300, 40)
1062
+ env.frame()
1063
+ hide(el)
1064
+ await sleep(5)
1065
+ env.resizeWindow(1000, 800)
1066
+ show(el, 300, 40)
1067
+ env.frame()
1068
+ await sleep(5)
1069
+ env.resizeWindow(900, 800)
1070
+ let w = r.rww.filter((m) => m.from === 'window')
1071
+ assert.strict.deepStrictEqual(w.map((m) => [m.snew.windowWidth, m.snew.offsetHeight]), [[900, 40]])
1072
+ })
1073
+
1074
+ it(`should measure again on the next sample after a skipped emit in polling mode and for an inline element`, async function() {
1075
+ for (let [opt, display] of [[{ mode: 'polling', timeInterval: 5 }, 'block'], [{ timeInterval: 5 }, 'inline']]) {
1076
+ let env = mkEnv()
1077
+ let el = new Ele(27, 18, { display })
1078
+ let r = env.dd(() => el, opt)
1079
+ await sleep(30)
1080
+ el.size(137, 18)
1081
+ r.d.refresh()
1082
+ el.display = 'none'
1083
+ el.size(0, 0)
1084
+ await sleep(3)
1085
+ el.display = display
1086
+ el.size(137, 18)
1087
+ await sleep(30)
1088
+ assert.strict.deepStrictEqual([ws(r), r.resize[r.resize.length - 1].sold.offsetWidth], [[27, 137], 0], display)
1089
+ for (let d of env.dds) {
1090
+ d.clear()
1091
+ }
1092
+ }
1093
+ })
1094
+
1095
+ //--- 多偵測器之延後發出 ---
1096
+
1097
+ it(`should run each deferred emit in its own task, so a later listener sees the microtask effects of an earlier one`, async function() {
1098
+ //延後發出各自一個task(同1.9.5與瀏覽器對各回呼之慣例): 前一個監聽器之microtask(如Vue之DOM更新)於下一個監聽器前已套用, 且監聽器不集中於同一個task而形成長任務
1099
+ let env = mkEnv()
1100
+ let e1 = new Ele(300, 40)
1101
+ let e2 = new Ele(200, 40)
1102
+ let order = []
1103
+ let r1 = env.dd(() => e1)
1104
+ let r2 = env.dd(() => e2)
1105
+ r1.d.on('resize', () => {
1106
+ order.push('l1')
1107
+ Promise.resolve().then(() => order.push('m1'))
1108
+ })
1109
+ r2.d.on('resize', () => order.push('l2'))
1110
+ env.frame()
1111
+ await sleep(5)
1112
+ assert.strict.deepStrictEqual(order, ['l1', 'm1', 'l2'])
1113
+ })
1114
+
1115
+ it(`should see an element hidden synchronously by an earlier listener in the same batch`, async function() {
1116
+ //各自於發出前確認: 同一批前一個監聽器同步隱藏後一個元素時, 後者不發出
1117
+ let env = mkEnv()
1118
+ let e1 = new Ele(300, 40)
1119
+ let e2 = new Ele(200, 40)
1120
+ let r1 = env.dd(() => e1)
1121
+ let r2 = env.dd(() => e2)
1122
+ env.frame()
1123
+ await sleep(5)
1124
+ let armed = true
1125
+ r1.d.on('resize', () => {
1126
+ if (armed) {
1127
+ armed = false
1128
+ hide(e2)
1129
+ }
1130
+ })
1131
+ e1.size(310, 40)
1132
+ e2.size(210, 40)
1133
+ env.frame()
1134
+ await sleep(5)
1135
+ assert.strict.deepStrictEqual([ws(r1), ws(r2)], [[300, 310], [200]])
1136
+ })
1137
+
1138
+ it(`should skip a detector cleared by an earlier listener in the same batch`, async function() {
1139
+ let env = mkEnv()
1140
+ let e1 = new Ele(300, 40)
1141
+ let e2 = new Ele(200, 40)
1142
+ let r1 = env.dd(() => e1)
1143
+ let r2 = env.dd(() => e2)
1144
+ r1.d.on('resize', () => r2.d.clear())
1145
+ env.frame()
1146
+ await sleep(5)
1147
+ assert.strict.deepStrictEqual([ws(r1), ws(r2), r2.rww.length], [[300], [], 0])
1148
+ })
1149
+
1150
+ it(`should keep emitting to other detectors when a listener throws, and still surface the error`, async function() {
1151
+ //監聽器拋錯不得中斷同一批其他偵測器之發出, 錯誤自該次發出之task拋出而仍可察覺
1152
+ let env = mkEnv()
1153
+ let caught = []
1154
+ let st = globalThis.setTimeout
1155
+ globalThis.setTimeout = (fn, ms) => st(() => {
1156
+ try {
1157
+ fn()
1158
+ }
1159
+ catch (err) {
1160
+ caught.push(err.message)
1161
+ }
1162
+ }, ms)
1163
+ try {
1164
+ let e1 = new Ele(300, 40)
1165
+ let e2 = new Ele(200, 40)
1166
+ let r1 = env.dd(() => e1)
1167
+ let r2 = env.dd(() => e2)
1168
+ r1.d.on('resize', () => {
1169
+ throw new Error('consumer boom')
1170
+ })
1171
+ env.frame()
1172
+ await sleep(10)
1173
+ await sleep(10) //事件迴圈忙碌時計時器可能與上一段等待同批到期, 再等一輪
1174
+ assert.strict.deepStrictEqual([ws(r1), r1.rww.length, ws(r2), r2.rww.length, caught], [[300], 0, [200], 1, ['consumer boom']])
1175
+ }
1176
+ finally {
1177
+ globalThis.setTimeout = st
1178
+ }
1179
+ })
1180
+
1181
+ //--- 視窗通道之零尺寸規則 ---
1182
+
1183
+ it(`should not emit window events while the element is hidden, zero in one dimension, or not measured yet`, async function() {
1184
+ //缺陷二: 視窗事件與dom事件同規則, 最新量測任一維為0不發出
1185
+ let env = mkEnv()
1186
+ let el = new Ele(300, 40)
1187
+ let r = env.dd(() => el)
1188
+ env.resizeWindow(1100, 800) //尚未量得
1189
+ env.frame()
1190
+ await sleep(5)
1191
+ env.resizeWindow(1000, 800) //可見
1192
+ el.size(0, 0)
1193
+ env.frame()
1194
+ await sleep(5)
1195
+ env.resizeWindow(900, 800) //隱藏
1196
+ el.size(300, 0)
1197
+ env.frame()
1198
+ await sleep(5)
1199
+ env.resizeWindow(800, 800) //寬大於0高為0
1200
+ el.size(300, 40)
1201
+ env.frame()
1202
+ await sleep(5)
1203
+ env.resizeWindow(700, 800) //恢復顯示
1204
+ let w = r.rww.filter((m) => m.from === 'window')
1205
+ assert.strict.deepStrictEqual(w.map((m) => m.snew.windowWidth), [1000, 700])
1206
+ assert.strict.deepStrictEqual(ws(r), [300, 300])
1207
+ })
1208
+
1209
+ it(`should not emit window events once the element is no longer rendered, before it is measured again`, async function() {
1210
+ //視窗事件於同一幀之ResizeObserver回報前派發, 視窗改變本身使元素隱藏時(如媒體查詢)最新量測仍為隱藏前之尺寸, 須另確認元素當下被繪製
1211
+ for (let cv of [false, true]) {
1212
+ let env = mkEnv()
1213
+ let el = new Ele(300, 40, { cv })
1214
+ let r = env.dd(() => el)
1215
+ env.frame()
1216
+ await sleep(5)
1217
+ hide(el)
1218
+ env.resizeWindow(900, 800)
1219
+ show(el, 300, 40)
1220
+ env.resizeWindow(1000, 800)
1221
+ let w = r.rww.filter((m) => m.from === 'window')
1222
+ assert.strict.deepStrictEqual(w.map((m) => m.snew.windowWidth), [1000], `cv=${cv}`)
1223
+ for (let d of env.dds) {
1224
+ d.clear()
1225
+ }
1226
+ }
1227
+ })
1228
+
1229
+ it(`should not emit window events once f finds no element, even if the old element stays rendered`, async function() {
1230
+ //f改回傳null而舊元素仍在頁面中且可見: 偵測對象已不存在, 最新量測歸0, 視窗事件不得帶出舊元素之尺寸
1231
+ for (let opt of [{}, { mode: 'polling', timeInterval: 5 }]) {
1232
+ let env = mkEnv()
1233
+ let el = new Ele(300, 40)
1234
+ let cur = el
1235
+ let r = env.dd(() => cur, opt)
1236
+ env.frame()
1237
+ await sleep(30)
1238
+ cur = null
1239
+ if (opt.mode !== 'polling') {
1240
+ r.d.refresh() //ResizeObserver模式由refresh重新取得元素(舊元素未變, 瀏覽器不回報); 輪詢模式由定期取樣重新取得
1241
+ }
1242
+ await sleep(30)
1243
+ env.resizeWindow(900, 800)
1244
+ assert.strict.deepStrictEqual([ws(r), r.rww.filter((m) => m.from === 'window').length], [[300], 0], JSON.stringify(opt))
1245
+ for (let d of env.dds) {
1246
+ d.clear()
1247
+ }
1248
+ }
1249
+ })
1250
+
1251
+ it(`should not emit window events while f finds no element, in both modes`, async function() {
1252
+ //取不到元素時最新量測歸0, 視窗事件不發出; 比較基準不動, 同尺寸之元素再出現時不發出dom事件(同1.9.5)
1253
+ for (let opt of [{}, { mode: 'polling', timeInterval: 5 }]) {
1254
+ let env = mkEnv()
1255
+ let el = new Ele(300, 40)
1256
+ let cur = el
1257
+ let r = env.dd(() => cur, opt)
1258
+ env.frame()
1259
+ await sleep(30)
1260
+ env.resizeWindow(1000, 800)
1261
+ cur = null
1262
+ el.isConnected = false
1263
+ el.size(0, 0)
1264
+ env.frame()
1265
+ await sleep(30)
1266
+ env.resizeWindow(900, 800)
1267
+ let b = new Ele(300, 40)
1268
+ cur = b
1269
+ env.fireMO()
1270
+ env.frame()
1271
+ await sleep(30)
1272
+ env.resizeWindow(800, 800)
1273
+ let w = r.rww.filter((m) => m.from === 'window')
1274
+ assert.strict.deepStrictEqual([w.map((m) => m.snew.windowWidth), ws(r)], [[1000, 800], [300]], JSON.stringify(opt))
1275
+ for (let d of env.dds) {
1276
+ d.clear()
1277
+ }
1278
+ }
1279
+ })
1280
+
1281
+ //--- getSize、getBase、throttle、refresh ---
1282
+
1283
+ it(`should compare the size given by getSize and carry it as width and height`, async function() {
1284
+ //box-sizing為border-box且只改padding時外框不變、內容區變小, 給getSize(內容區)者須發出
1285
+ let env = mkEnv()
1286
+ let el = new Ele(300, 40)
1287
+ let r = env.dd(() => el, { getSize: (e) => ({ width: e.clientWidth, height: e.clientHeight }) })
1288
+ env.frame()
1289
+ await sleep(5)
1290
+ el.size(300, 40, 260, 40)
1291
+ env.frame()
1292
+ await sleep(5)
1293
+ assert.strict.deepStrictEqual(r.resize.map((m) => [m.snew.offsetWidth, m.snew.width, m.smode.width]), [[300, 300, 'larger'], [300, 260, 'smaller']])
1294
+ })
1295
+
1296
+ it(`should not emit while getSize throws or gives no number, and emit once it gives a size`, async function() {
1297
+ let env = mkEnv()
1298
+ let el = new Ele(300, 40)
1299
+ let mode = 'throw'
1300
+ let r = env.dd(() => el, {
1301
+ getSize: () => {
1302
+ if (mode === 'throw') {
1303
+ throw new Error('size boom')
1304
+ }
1305
+ if (mode === 'nan') {
1306
+ return { width: 'abc', height: 40 }
1307
+ }
1308
+ return { width: 280, height: 40 }
1309
+ },
1310
+ })
1311
+ env.frame()
1312
+ await sleep(5)
1313
+ mode = 'nan'
1314
+ el.size(310, 40)
1315
+ env.frame()
1316
+ await sleep(5)
1317
+ mode = 'ok'
1318
+ el.size(320, 40)
1319
+ env.frame()
1320
+ await sleep(5)
1321
+ assert.strict.deepStrictEqual(r.resize.map((m) => [m.snew.offsetWidth, m.snew.width]), [[320, 280]])
1322
+ })
1323
+
1324
+ it(`should compare against getBase, skip axes that are not numbers, and not emit when already in line at mount`, async function() {
1325
+ //比較基準為使用端目前套用之尺寸, 掛載時一致不發出; 某軸非數字(如固定寬度)該軸不比
1326
+ for (let opt of [{}, { sync: true }, { mode: 'polling', timeInterval: 5 }]) {
1327
+ let env = mkEnv()
1328
+ let el = new Ele(300, 40)
1329
+ let chart = { w: 300, h: 40, fixW: false }
1330
+ let r = env.dd(() => el, { ...opt, getBase: () => ({ width: chart.fixW ? 'fixed' : chart.w, height: chart.h }) })
1331
+ r.d.on('resize', (m) => {
1332
+ if (!chart.fixW) {
1333
+ chart.w = m.snew.width
1334
+ }
1335
+ chart.h = m.snew.height
1336
+ })
1337
+ let step = async () => {
1338
+ env.frame()
1339
+ await sleep(30)
1340
+ }
1341
+ await step()
1342
+ el.size(350, 40)
1343
+ await step()
1344
+ chart.fixW = true
1345
+ el.size(400, 40)
1346
+ await step()
1347
+ el.size(400, 60)
1348
+ await step()
1349
+ assert.strict.deepStrictEqual(r.resize.map((m) => [m.snew.width, m.snew.height]), [[350, 40], [400, 60]], JSON.stringify(opt))
1350
+ for (let d of env.dds) {
1351
+ d.clear()
1352
+ }
1353
+ }
1354
+ })
1355
+
1356
+ it(`should not emit a deferred change that the consumer has already applied`, async function() {
1357
+ //延後發出前再比一次getBase, 使用端已自行同步時不發出
1358
+ let env = mkEnv()
1359
+ let el = new Ele(300, 40)
1360
+ let chart = { w: 300, h: 40 }
1361
+ let r = env.dd(() => el, { getBase: () => ({ width: chart.w, height: chart.h }) })
1362
+ env.frame()
1363
+ await sleep(5)
1364
+ el.size(350, 40)
1365
+ env.frame()
1366
+ chart.w = 350
1367
+ await sleep(5)
1368
+ assert.strict.deepStrictEqual(r.resize.length, 0)
1369
+ })
1370
+
1371
+ it(`should not compare an axis when getBase throws`, async function() {
1372
+ let env = mkEnv()
1373
+ let el = new Ele(300, 40)
1374
+ let r = env.dd(() => el, {
1375
+ getBase: () => {
1376
+ throw new Error('base boom')
1377
+ },
1378
+ })
1379
+ env.frame()
1380
+ await sleep(5)
1381
+ el.size(350, 60)
1382
+ env.frame()
1383
+ await sleep(5)
1384
+ assert.strict.deepStrictEqual(r.resize.length, 0)
1385
+ })
1386
+
1387
+ it(`should give directions against the consumer size at the deferred emit`, async function() {
1388
+ //有給getBase者延後發出前再比一次, 方向依發出當下與使用端尺寸之比較重給: 空檔內使用端已同步之軸不給方向; 同批多次變化時相對前一次發出後之使用端尺寸
1389
+ let env = mkEnv()
1390
+ let el = new Ele(300, 40)
1391
+ let base = { w: 300, h: 40 }
1392
+ let r = env.dd(() => el, { getBase: () => ({ width: base.w, height: base.h }) })
1393
+ r.d.on('resize', (m) => {
1394
+ base.w = m.snew.offsetWidth
1395
+ base.h = m.snew.offsetHeight
1396
+ })
1397
+ env.fireRO(el)
1398
+ el.size(400, 60)
1399
+ env.fireRO(el)
1400
+ base.w = 400
1401
+ await sleep(10)
1402
+ el.size(500, 60)
1403
+ env.fireRO(el)
1404
+ el.size(450, 60)
1405
+ env.fireRO(el)
1406
+ await sleep(10)
1407
+ assert.strict.deepStrictEqual(r.resize.map((m) => [m.snew.offsetWidth, m.smode.width, m.smode.height]), [[400, '', 'larger'], [500, 'larger', ''], [450, 'smaller', '']])
1408
+ })
1409
+
1410
+ it(`should skip an older deferred change that the consumer already applied while a newer one is pending`, async function() {
1411
+ //發出前之再比較以使用端當下之尺寸為準(不以尚未處理之較新量測), 使用端已同步之較舊項不發出
1412
+ let env = mkEnv()
1413
+ let el = new Ele(300, 40)
1414
+ let base = { w: 300, h: 40 }
1415
+ let r = env.dd(() => el, { getBase: () => ({ width: base.w, height: base.h }) })
1416
+ r.d.on('resize', (m) => {
1417
+ base.w = m.snew.offsetWidth
1418
+ base.h = m.snew.offsetHeight
1419
+ })
1420
+ env.frame()
1421
+ await sleep(5)
1422
+ el.size(350, 40)
1423
+ env.frame()
1424
+ el.size(380, 40)
1425
+ env.frame()
1426
+ base.w = 350
1427
+ await sleep(5)
1428
+ assert.strict.deepStrictEqual([ws(r), base.w], [[380], 380])
1429
+ })
1430
+
1431
+ it(`should compare against a pending deferred emit with getBase, so the consumer does not end at a stale size`, async function() {
1432
+ //有尚未發出之變化時使用端尺寸將被其更新; 其後量得與使用端當下相近之尺寸若仍與使用端當下比較, 會判為未變化, 前一個發出套用後使用端即停在過期尺寸
1433
+ let env = mkEnv()
1434
+ let el = new Ele(257, 43)
1435
+ let base = { w: 257, h: 43 }
1436
+ let r = env.dd(() => el, { tolerancePixel: 2, getBase: () => ({ width: base.w, height: base.h }) })
1437
+ r.d.on('resize', (m) => {
1438
+ base.w = m.snew.offsetWidth
1439
+ base.h = m.snew.offsetHeight
1440
+ })
1441
+ env.frame()
1442
+ await sleep(5)
1443
+ el.size(245, 40)
1444
+ env.frame()
1445
+ el.size(256, 41)
1446
+ env.frame()
1447
+ await sleep(5)
1448
+ assert.strict.deepStrictEqual([ws(r), [base.w, base.h]], [[245, 256], [256, 41]])
1449
+ })
1450
+
1451
+ it(`should throttle with the first change at once and the last change at the end of the window`, async function() {
1452
+ for (let opt of [{}, { mode: 'polling', timeInterval: 5 }]) {
1453
+ let env = mkEnv()
1454
+ let el = new Ele(300, 40)
1455
+ let r = env.dd(() => el, { ...opt, throttle: 60 })
1456
+ env.frame()
1457
+ await sleep(10)
1458
+ for (let w of [310, 320, 330]) {
1459
+ el.size(w, 40)
1460
+ env.frame()
1461
+ await sleep(10)
1462
+ }
1463
+ let during = ws(r)
1464
+ await sleep(100)
1465
+ await sleep(10) //節流之後續發出於節流計時器觸發後再延後1ms發出, 再等一輪
1466
+ assert.strict.deepStrictEqual([during, ws(r)], [[300], [300, 330]], JSON.stringify(opt))
1467
+ for (let d of env.dds) {
1468
+ d.clear()
1469
+ }
1470
+ }
1471
+ })
1472
+
1473
+ it(`should cancel the pending throttled emit on clear`, async function() {
1474
+ let env = mkEnv()
1475
+ let el = new Ele(300, 40)
1476
+ let r = env.dd(() => el, { throttle: 60 })
1477
+ env.frame()
1478
+ await sleep(10)
1479
+ el.size(310, 40)
1480
+ env.frame()
1481
+ await sleep(10)
1482
+ r.d.clear()
1483
+ await sleep(100)
1484
+ await sleep(10) //節流之後續發出於節流計時器觸發後再延後1ms發出, 再等一輪
1485
+ assert.strict.deepStrictEqual(ws(r), [300])
1486
+ })
1487
+
1488
+ it(`should not schedule the pending throttled emit after clear`, async function() {
1489
+ //clear時取消節流待發出者: 已排定之事件本就因clear而不發出, 但不取消時節流計時器殘留至窗口結束後仍排定延後發出
1490
+ let env = mkEnv()
1491
+ let el = new Ele(300, 40)
1492
+ let r = env.dd(() => el, { throttle: 60 })
1493
+ env.frame()
1494
+ await sleep(10)
1495
+ el.size(310, 40)
1496
+ env.frame()
1497
+ await sleep(10)
1498
+ let delays = []
1499
+ let st = globalThis.setTimeout
1500
+ globalThis.setTimeout = (fn, ms) => {
1501
+ delays.push(ms)
1502
+ return st(fn, ms)
1503
+ }
1504
+ try {
1505
+ r.d.clear()
1506
+ await sleep(100)
1507
+ await sleep(10)
1508
+ }
1509
+ finally {
1510
+ globalThis.setTimeout = st
1511
+ }
1512
+ assert.strict.deepStrictEqual([ws(r), delays.filter((ms) => ms === 1)], [[300], []])
1513
+ })
1514
+
1515
+ it(`should clear the timers of scheduled deferred emits on clear`, async function() {
1516
+ //clear時取消已排定之延後發出計時器, 不殘留至觸發
1517
+ let env = mkEnv()
1518
+ let ids = []
1519
+ let cleared = []
1520
+ let st = globalThis.setTimeout
1521
+ let ct = globalThis.clearTimeout
1522
+ globalThis.setTimeout = (fn, ms) => {
1523
+ let id = st(fn, ms)
1524
+ if (ms === 1) {
1525
+ ids.push(id)
1526
+ }
1527
+ return id
1528
+ }
1529
+ globalThis.clearTimeout = (id) => {
1530
+ cleared.push(id)
1531
+ return ct(id)
1532
+ }
1533
+ try {
1534
+ let el = new Ele(300, 40)
1535
+ let r = env.dd(() => el)
1536
+ env.frame()
1537
+ r.d.clear()
1538
+ await sleep(10)
1539
+ assert.strict.deepStrictEqual([ids.length > 0, ids.every((id) => cleared.includes(id)), r.resize.length], [true, true, 0])
1540
+ }
1541
+ finally {
1542
+ globalThis.setTimeout = st
1543
+ globalThis.clearTimeout = ct
1544
+ }
1545
+ })
1546
+
1547
+ it(`should defer the first throttled emit unless sync`, async function() {
1548
+ //節流之首次發出與未節流相同: 非sync時延後發出(發出前確認元素仍被繪製), sync時於回呼內同步發出
1549
+ for (let sync of [false, true]) {
1550
+ let env = mkEnv()
1551
+ let el = new Ele(300, 40)
1552
+ let r = env.dd(() => el, { throttle: 40, sync })
1553
+ env.frame()
1554
+ let nNow = r.resize.length
1555
+ await sleep(10)
1556
+ assert.strict.deepStrictEqual([nNow, ws(r)], [sync ? 1 : 0, [300]], `sync=${sync}`)
1557
+ for (let d of env.dds) {
1558
+ d.clear()
1559
+ }
1560
+ }
1561
+ })
1562
+
1563
+ it(`should emit the first change synchronously and the rest deferred with throttle and sync`, async function() {
1564
+ let env = mkEnv()
1565
+ let el = new Ele(300, 40)
1566
+ let live = []
1567
+ let r = env.dd(() => el, { throttle: 60, sync: true })
1568
+ r.d.on('resize', () => live.push(el.offsetWidth))
1569
+ env.frame()
1570
+ let nSync = r.resize.length
1571
+ el.size(310, 40)
1572
+ env.frame()
1573
+ let nWindow = r.resize.length
1574
+ await sleep(100)
1575
+ await sleep(10) //節流之後續發出於節流計時器觸發後再延後1ms發出, 再等一輪
1576
+ assert.strict.deepStrictEqual([nSync, nWindow, ws(r), live], [1, 1, [300, 310], [300, 310]])
1577
+ })
1578
+
1579
+ it(`should not emit an older deferred measurement after a newer one was emitted synchronously`, async function() {
1580
+ //sync時refresh為延後發出, 其後同一幀內之回報以sync同步發出較新之量測, 較舊之refresh不得再發出, 否則以事件套用尺寸之使用端最後停在過期尺寸
1581
+ let env = mkEnv()
1582
+ let el = new Ele(300, 40)
1583
+ let base = { w: 300, h: 40 }
1584
+ let r = env.dd(() => el, { sync: true, getBase: () => ({ width: base.w, height: base.h }) })
1585
+ r.d.on('resize', (m) => {
1586
+ base.w = m.snew.offsetWidth
1587
+ base.h = m.snew.offsetHeight
1588
+ })
1589
+ env.fireRO(el)
1590
+ el.size(350, 40)
1591
+ r.d.refresh()
1592
+ el.size(400, 40)
1593
+ env.fireRO(el)
1594
+ await sleep(10)
1595
+ assert.strict.deepStrictEqual([ws(r), base.w], [[400], 400])
1596
+ })
1597
+
1598
+ it(`should not emit an older throttled trailing measurement after a newer one was emitted synchronously`, async function() {
1599
+ //sync且節流時後續發出為延後發出; 其1ms空檔若被延誤至節流窗口結束後, 期間之回報為首次而同步發出較新之量測, 較舊之後續發出不得再發出
1600
+ let env = mkEnv()
1601
+ let el = new Ele(300, 40)
1602
+ let held = []
1603
+ let st = globalThis.setTimeout
1604
+ let r = env.dd(() => el, { sync: true, throttle: 20 })
1605
+ env.fireRO(el) //首次, 同步發出300
1606
+ el.size(310, 40)
1607
+ env.fireRO(el) //窗口內, 待後續發出
1608
+ globalThis.setTimeout = (fn, ms) => {
1609
+ if (ms === 1) {
1610
+ held.push(fn) //攔下後續發出之延後, 模擬其被延誤
1611
+ return 0
1612
+ }
1613
+ return st(fn, ms)
1614
+ }
1615
+ try {
1616
+ await sleep(40)
1617
+ }
1618
+ finally {
1619
+ globalThis.setTimeout = st
1620
+ }
1621
+ let t0 = Date.now()
1622
+ while (Date.now() - t0 < 25) {
1623
+ //忙等超過節流時間, 使下一次回報為首次(節流以Date.now判定)
1624
+ }
1625
+ el.size(320, 40)
1626
+ env.fireRO(el) //首次, 同步發出320
1627
+ for (let fn of held) {
1628
+ fn() //被延誤之後續發出(310)此時才執行
1629
+ }
1630
+ await sleep(10)
1631
+ assert.strict.deepStrictEqual([held.length, ws(r)], [1, [300, 320]])
1632
+ })
1633
+
1634
+ it(`should not emit a throttled trailing change that the consumer has already applied`, async function() {
1635
+ //有給getBase者節流之後續發出亦於發出前再比一次, 窗口內使用端已自行同步時不發出
1636
+ let env = mkEnv()
1637
+ let el = new Ele(300, 40)
1638
+ let base = { w: 300, h: 40 }
1639
+ let r = env.dd(() => el, { throttle: 40, getBase: () => ({ width: base.w, height: base.h }) })
1640
+ r.d.on('resize', (m) => {
1641
+ base.w = m.snew.offsetWidth
1642
+ base.h = m.snew.offsetHeight
1643
+ })
1644
+ env.frame()
1645
+ await sleep(5)
1646
+ el.size(350, 40)
1647
+ env.frame()
1648
+ await sleep(5)
1649
+ el.size(380, 40)
1650
+ env.frame()
1651
+ base.w = 380
1652
+ await sleep(80)
1653
+ await sleep(10) //節流之後續發出於節流計時器觸發後再延後1ms發出, 再等一輪
1654
+ assert.strict.deepStrictEqual(ws(r), [350])
1655
+ })
1656
+
1657
+ it(`should merge a refresh within the throttle window into the trailing emit`, async function() {
1658
+ //節流窗口內之refresh併入節流, 於窗口結束時以最後一次之量測發出
1659
+ let env = mkEnv()
1660
+ let el = new Ele(300, 40)
1661
+ let chart = { w: 300, h: 40, fixW: false }
1662
+ let r = env.dd(() => el, { throttle: 60, getBase: () => ({ width: chart.fixW ? null : chart.w, height: chart.h }) })
1663
+ r.d.on('resize', (m) => {
1664
+ chart.w = m.snew.offsetWidth
1665
+ chart.h = m.snew.offsetHeight
1666
+ })
1667
+ el.size(310, 40)
1668
+ env.frame()
1669
+ await sleep(10)
1670
+ chart.fixW = true
1671
+ el.size(360, 40)
1672
+ env.frame()
1673
+ chart.fixW = false
1674
+ r.d.refresh()
1675
+ await sleep(10)
1676
+ let nInWindow = r.resize.length
1677
+ await sleep(80)
1678
+ await sleep(10) //節流之後續發出於節流計時器觸發後再延後1ms發出, 再等一輪
1679
+ assert.strict.deepStrictEqual([nInWindow, ws(r)], [1, [310, 360]])
1680
+ })
1681
+
1682
+ it(`should restore the sync state after a throwing listener with throttle and sync`, async function() {
1683
+ //sync時節流之首次於回呼內同步發出, 監聽器拋錯外拋後同步旗標須復原, 其後之refresh仍為延後發出
1684
+ let env = mkEnv()
1685
+ let el = new Ele(300, 40)
1686
+ let r = env.dd(() => el, { throttle: 20, sync: true })
1687
+ let boom = true
1688
+ r.d.on('resize', () => {
1689
+ if (boom) {
1690
+ boom = false
1691
+ throw new Error('consumer boom')
1692
+ }
1693
+ })
1694
+ assert.throws(() => env.fireRO(el), /consumer boom/)
1695
+ await sleep(40)
1696
+ el.size(330, 40)
1697
+ r.d.refresh()
1698
+ let nNow = r.resize.length
1699
+ await sleep(10)
1700
+ assert.strict.deepStrictEqual([nNow, ws(r)], [1, [300, 330]])
1701
+ })
1702
+
1703
+ it(`should not emit the later deferred change of a detector cleared by the listener of the earlier one`, async function() {
1704
+ let env = mkEnv()
1705
+ let el = new Ele(300, 40)
1706
+ let r = env.dd(() => el)
1707
+ r.d.on('resize', () => r.d.clear())
1708
+ el.size(300, 40)
1709
+ r.d.refresh()
1710
+ el.size(340, 40)
1711
+ r.d.refresh()
1712
+ await sleep(10)
1713
+ assert.strict.deepStrictEqual([ws(r), r.rww.length], [[300], 0])
1714
+ })
1715
+
1716
+ it(`should skip a throttled emit of a hidden element and emit once it is shown again`, async function() {
1717
+ let env = mkEnv()
1718
+ let el = new Ele(300, 40)
1719
+ let r = env.dd(() => el, { throttle: 60 })
1720
+ env.frame()
1721
+ await sleep(10)
1722
+ el.size(310, 40)
1723
+ env.frame()
1724
+ hide(el)
1725
+ await sleep(100)
1726
+ await sleep(10) //節流之後續發出於節流計時器觸發後再延後1ms發出, 再等一輪
1727
+ let n = r.resize.length
1728
+ show(el, 310, 40)
1729
+ env.frame()
1730
+ await sleep(100)
1731
+ await sleep(10) //節流之後續發出於節流計時器觸發後再延後1ms發出, 再等一輪
1732
+ assert.strict.deepStrictEqual([n, ws(r)], [1, [300, 310]])
1733
+ })
1734
+
1735
+ it(`should compare again on refresh in both modes`, async function() {
1736
+ //比較基準因尺寸以外之原因改變(例如取消固定寬度)時, refresh立即重新比較並延後發出
1737
+ for (let opt of [{}, { mode: 'polling', timeInterval: 5 }]) {
1738
+ let env = mkEnv()
1739
+ let el = new Ele(300, 40)
1740
+ let chart = { w: 300, h: 40, fixW: true }
1741
+ let r = env.dd(() => el, { ...opt, getBase: () => ({ width: chart.fixW ? null : chart.w, height: chart.h }) })
1742
+ r.d.on('resize', (m) => { //使用端於事件內套用新尺寸, 否則之後每次量測皆判定為變化
1743
+ chart.w = m.snew.width
1744
+ chart.h = m.snew.height
1745
+ })
1746
+ env.frame()
1747
+ await sleep(30)
1748
+ el.size(380, 40)
1749
+ env.frame()
1750
+ await sleep(30)
1751
+ let n = r.resize.length
1752
+ chart.fixW = false
1753
+ r.d.refresh()
1754
+ let nSync = r.resize.length
1755
+ await sleep(20) //延後發出之計時器於負載下可能延誤, 等待放寬
1756
+ assert.strict.deepStrictEqual([n, nSync, ws(r)], [0, 0, [380]], JSON.stringify(opt))
1757
+ for (let d of env.dds) {
1758
+ d.clear()
1759
+ }
1760
+ }
1761
+ })
1762
+
1763
+ it(`should do nothing on refresh after clear or before the element exists`, async function() {
1764
+ for (let opt of [{}, { mode: 'polling', timeInterval: 5 }]) {
1765
+ let env = mkEnv()
1766
+ let el = null
1767
+ let calls = 0
1768
+ let r = env.dd(() => {
1769
+ calls++
1770
+ return el
1771
+ }, opt)
1772
+ assert.doesNotThrow(() => r.d.refresh(), JSON.stringify(opt))
1773
+ el = new Ele(300, 40)
1774
+ r.d.clear()
1775
+ let callsAtClear = calls
1776
+ assert.doesNotThrow(() => r.d.refresh(), JSON.stringify(opt))
1777
+ env.frame()
1778
+ await sleep(10)
1779
+ assert.strict.deepStrictEqual([r.resize.length, env.moActive(), env.listeners.length, calls - callsAtClear], [0, 0, 0, 0], JSON.stringify(opt))
1780
+ }
1781
+ })
1782
+
1783
+ it(`should not loop when a listener calls refresh`, async function() {
1784
+ let env = mkEnv()
1785
+ let el = new Ele(300, 40)
1786
+ let r = env.dd(() => el)
1787
+ r.d.on('resize', () => r.d.refresh())
1788
+ env.frame()
1789
+ await sleep(20)
1790
+ assert.strict.deepStrictEqual(ws(r), [300])
1791
+ })
1792
+
642
1793
  //--- 不變式 ---
643
1794
 
644
1795
  it(`should never emit a zero size, and end within the tolerance of the actual size`, async function() {
@@ -681,4 +1832,90 @@ describe(`domDetect`, function() {
681
1832
  }
682
1833
  })
683
1834
 
1835
+ it(`should never call a listener while the element is hidden, and end with the actual size, under hides, refreshes and sync`, async function() {
1836
+ //逐幀模型(尺寸未變即不回報), 以固定種子之亂數穿插: 變化、隱藏(於量測與發出之間亦然)、於下一幀前恢復顯示、refresh、推進一幀、推進時間, 於延後、sync、有無getBase四種模式須滿足:
1837
+ // (1) 監聽器被呼叫時元素當下必被繪製 (2) 恢復顯示並靜止後, 最後一次發出之尺寸(有給getBase者為使用端所套用之尺寸)與實際尺寸之差不超過容許誤差(無永久漏發, 亦不以過期尺寸收尾)
1838
+ let seed = 20260926
1839
+ let rnd = () => {
1840
+ seed = (seed + 0x6D2B79F5) | 0
1841
+ let v = Math.imul(seed ^ (seed >>> 15), 1 | seed)
1842
+ v = (v + Math.imul(v ^ (v >>> 7), 61 | v)) ^ v
1843
+ return ((v ^ (v >>> 14)) >>> 0) / 4294967296
1844
+ }
1845
+ for (let [mode, sync, useBase] of [['deferred', false, false], ['sync', true, false], ['deferredGetBase', false, true], ['syncGetBase', true, true]]) {
1846
+ for (let k = 0; k < 40; k++) {
1847
+ let env = mkEnv()
1848
+ let tol = k % 3
1849
+ let el = new Ele(300, 40)
1850
+ let base = { w: 300, h: 40 }
1851
+ let opt = { tolerancePixel: tol, sync }
1852
+ if (useBase) {
1853
+ opt.getBase = () => ({ width: base.w, height: base.h })
1854
+ }
1855
+ let r = env.dd(() => el, opt)
1856
+ let bad = 0
1857
+ r.d.on('resize', (m) => {
1858
+ if (!(el.display !== 'none' && el.offsetWidth > 0 && el.offsetHeight > 0)) {
1859
+ bad++
1860
+ }
1861
+ base.w = m.snew.offsetWidth
1862
+ base.h = m.snew.offsetHeight
1863
+ })
1864
+ let w = 300
1865
+ let h = 40
1866
+ let visible = true
1867
+ let ops = []
1868
+ env.frame()
1869
+ await sleep(3)
1870
+ for (let i = 0; i < 24; i++) {
1871
+ let x = rnd()
1872
+ if (x < 0.2) {
1873
+ visible = false
1874
+ hide(el)
1875
+ ops.push('hide')
1876
+ }
1877
+ else if (x < 0.4) {
1878
+ visible = true
1879
+ show(el, w, h)
1880
+ ops.push('show')
1881
+ }
1882
+ else if (x < 0.5) {
1883
+ r.d.refresh()
1884
+ ops.push('refresh')
1885
+ }
1886
+ else {
1887
+ w = Math.max(1, w + Math.floor(rnd() * 41) - 20)
1888
+ h = Math.max(1, h + Math.floor(rnd() * 11) - 5)
1889
+ if (visible) {
1890
+ el.size(w, h)
1891
+ }
1892
+ ops.push(`size${w}x${h}`)
1893
+ }
1894
+ if (rnd() < 0.6) {
1895
+ env.frame()
1896
+ ops.push('frame')
1897
+ }
1898
+ if (rnd() < 0.5) {
1899
+ await sleep(3)
1900
+ ops.push('tick')
1901
+ }
1902
+ }
1903
+ show(el, w, h)
1904
+ for (let j = 0; j < 3; j++) {
1905
+ env.frame()
1906
+ await sleep(3)
1907
+ }
1908
+ let last = r.resize[r.resize.length - 1]
1909
+ let fw = useBase ? base.w : (last ? last.snew.offsetWidth : 0)
1910
+ let fh = useBase ? base.h : (last ? last.snew.offsetHeight : 0)
1911
+ let msg = `mode=${mode} run=${k} final=${fw}x${fh} real=${w}x${h} ops=${ops.join(',')}`
1912
+ assert.strict.deepStrictEqual(bad, 0, msg)
1913
+ assert.ok(Math.abs(fw - w) <= tol && Math.abs(fh - h) <= tol, msg)
1914
+ for (let d of env.dds) {
1915
+ d.clear()
1916
+ }
1917
+ }
1918
+ }
1919
+ })
1920
+
684
1921
  })