Box.js

Box for modal / toast

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.gf.qytechs.cn/scripts/447483/1071404/Boxjs.js

  1. // ==UserScript==
  2. // @name Box.js
  3. // @namespace https://github.com/invobzvr
  4. // @version 0.5
  5. // @description Box for modal / toast
  6. // @author invobzvr
  7. // @homepageURL https://github.com/invobzvr/invotoys.js/tree/main/box.js
  8. // @supportURL https://github.com/invobzvr/invotoys.js/issues
  9. // @license GPL-3.0
  10. // ==/UserScript==
  11.  
  12. const LIB_NAME = 'Box';
  13. !function (global, factory) {
  14. typeof exports === 'object' && typeof module !== 'undefined' ?
  15. module.exports = factory() :
  16. typeof define === 'function' && define.amd ?
  17. define(factory) :
  18. (global = global || self)[LIB_NAME] = factory();
  19. }(this, function () {
  20. 'use strict';
  21. function element(tag, props, body) {
  22. let el = document.createElement(tag);
  23. props && Object.entries(props).forEach(([key, val]) => val && (el[key] = val));
  24. body && body instanceof HTMLElement ? body.append(el) : typeof body === 'string' && document.querySelector(body).append(el);
  25. return el;
  26. }
  27.  
  28. class Box {
  29. constructor(params) {
  30. this.params = Object.assign({}, params);
  31. this.build();
  32. typeof this.params.didBuild === 'function' && this.params.didBuild(this.modal);
  33. this.register();
  34. this.await = new Promise(resolve => this.close = async ret => {
  35. resolve(ret);
  36. await this.hide();
  37. this[this.ctnr.childElementCount === 1 ? 'ctnr' : 'modal'].remove();
  38. });
  39. if (this.params.toast) {
  40. let time = this.params.time,
  41. bgc = {
  42. success: '#0a5',
  43. warning: '#fa0',
  44. error: '#f25',
  45. }[this.params.type];
  46. Number.isFinite(time) && time > 0 && setTimeout(() => this.close(), time);
  47. bgc && (this.modal.style.background = bgc);
  48. }
  49. this.params.show !== false && this.show();
  50. }
  51.  
  52. static mixin(mixinParams) {
  53. return class MixinBox extends this {
  54. constructor(params) {
  55. super(Object.assign({}, mixinParams, params));
  56. }
  57. }
  58. }
  59.  
  60. build() {
  61. if (this.params.toast) {
  62. this.ctnr = document.querySelector('.box-toaster') || element('div', { className: 'box-toaster' }, document.body);
  63. this.modal = element('div', { className: 'box-toast-item' }, this.ctnr);
  64. } else {
  65. this.ctnr = element('div', { className: 'box-container' }, document.body);
  66. this.modal = element('div', { className: 'box-modal' }, this.ctnr);
  67. }
  68. element('div', {
  69. className: 'box-title',
  70. innerText: this.params.title,
  71. }, this.modal);
  72. element('div', {
  73. className: 'box-content',
  74. innerText: this.params.text,
  75. innerHTML: this.params.html,
  76. }, this.modal);
  77. if (this.params.toast) {
  78. return;
  79. }
  80. let actions = element('div', { className: 'box-actions' }, this.modal);
  81. Object.entries(this.params.actions || { OK: () => this.close() }).forEach(([key, val]) => element('button', {
  82. className: 'box-button',
  83. innerText: key,
  84. onclick: () => this.close(val(this.modal)),
  85. }, actions));
  86. }
  87.  
  88. register() {
  89. let ignore = false;
  90. this.modal.addEventListener('mousedown', () => {
  91. this.ctnr.addEventListener('mouseup', evt => evt.target === this.ctnr && (ignore = true), { once: true });
  92. });
  93. this.ctnr.addEventListener('mousedown', () => {
  94. this.ctnr.addEventListener('mouseup', evt => (evt.target === this.modal || this.modal.contains(evt.target)) && (ignore = true), { once: true });
  95. });
  96. this.ctnr.addEventListener('click', evt => {
  97. ignore ? ignore = false : evt.target === this.ctnr && this.close();
  98. });
  99. }
  100.  
  101. show() {
  102. return new Promise(resolve => {
  103. setTimeout(() => {
  104. !this.params.toast && this.ctnr.classList.add('box-backdrop');
  105. this.modal.classList.add('in');
  106. }, 10);
  107. this.modal.addEventListener('transitionend', resolve, { once: true });
  108. });
  109. }
  110.  
  111. hide() {
  112. return new Promise(resolve => {
  113. !this.params.toast && this.ctnr.classList.remove('box-backdrop');
  114. this.modal.classList.remove('in');
  115. this.modal.addEventListener('transitionend', resolve, { once: true });
  116. });
  117. }
  118.  
  119. then(onFulfilled) {
  120. return this.await.then(onFulfilled);
  121. }
  122. }
  123.  
  124. element('style', null, document.head).innerHTML = `.box-container {
  125. bottom: 0;
  126. display: grid;
  127. left: 0;
  128. overflow: auto;
  129. pointer-events: none;
  130. position: fixed;
  131. right: 0;
  132. top: 0;
  133. transition: .2s;
  134. z-index: 200;
  135. }
  136.  
  137. .box-backdrop {
  138. background: #0006;
  139. pointer-events: auto;
  140. }
  141.  
  142. .box-modal {
  143. align-self: center;
  144. background: #fff;
  145. border-radius: 5px;
  146. box-shadow: 0 3px 20px #0006;
  147. justify-self: center;
  148. margin: 20px;
  149. opacity: 0;
  150. padding: 0 30px;
  151. transform: scale(.8);
  152. transition: .2s;
  153. user-select: none;
  154. }
  155.  
  156. .box-modal.in {
  157. opacity: 1;
  158. transform: scale(1);
  159. }
  160.  
  161. .box-title {
  162. font-size: 30px;
  163. padding: 20px;
  164. text-align: center;
  165. }
  166.  
  167. .box-input-group {
  168. display: table;
  169. margin-bottom: 10px;
  170. }
  171.  
  172. .box-label {
  173. display: table-cell;
  174. font-size: 16px;
  175. padding: 0 10px;
  176. text-align: center;
  177. width: 100%;
  178. }
  179.  
  180. .box-input {
  181. font-size: 20px;
  182. padding: 5px 9px;
  183. }
  184.  
  185. .box-input[type=checkbox] {
  186. vertical-align: middle;
  187. }
  188.  
  189. .box-options {
  190. margin: auto;
  191. width: 80%;
  192. }
  193.  
  194. .box-option-item {
  195. margin-right: 12px;
  196. white-space: nowrap;
  197. }
  198.  
  199. .box-option-item .box-label {
  200. font-size: 13px;
  201. padding: 0 0 0 3px;
  202. }
  203.  
  204. .box-actions {
  205. margin: 20px;
  206. text-align: center;
  207. }
  208.  
  209. .box-button {
  210. background: #09f;
  211. border-radius: 5px;
  212. border: none;
  213. color: #fff;
  214. cursor: pointer;
  215. font-size: 16px;
  216. margin: 5px;
  217. padding: 10px 18px;
  218. }
  219.  
  220. .box-toaster {
  221. display: grid;
  222. min-width: 360px;
  223. padding: 10px;
  224. position: fixed;
  225. right: 0;
  226. top: 0;
  227. width: 27%;
  228. z-index: 201;
  229. }
  230.  
  231. .box-toast-item {
  232. background: #09f;
  233. border-radius: 7px;
  234. box-shadow: 0 2px 10px #0005;
  235. color: #fff;
  236. margin-bottom: 10px;
  237. padding: 10px 10px 15px 10px;
  238. opacity: 0;
  239. transition: .2s;
  240. transform: scale(.8);
  241. }
  242.  
  243. .box-toast-item.in {
  244. opacity: 1;
  245. transform: scale(1);
  246. }
  247.  
  248. .box-toast-item .box-title,
  249. .box-toast-item .box-content {
  250. padding: 0 10px;
  251. text-align: left;
  252. word-break: break-word;
  253. }`;
  254.  
  255. return Box;
  256. });

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址