themes.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. /**
  2. * DevExtreme (ui/themes.js)
  3. * Version: 19.1.16
  4. * Build date: Tue Oct 18 2022
  5. *
  6. * Copyright (c) 2012 - 2022 Developer Express Inc. ALL RIGHTS RESERVED
  7. * Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
  8. */
  9. "use strict";
  10. var $ = require("../core/renderer");
  11. var domAdapter = require("../core/dom_adapter");
  12. var windowUtils = require("../core/utils/window");
  13. var window = windowUtils.getWindow();
  14. var Deferred = require("../core/utils/deferred").Deferred;
  15. var errors = require("./widget/ui.errors");
  16. var domUtils = require("../core/utils/dom");
  17. var readyCallbacks = require("../core/utils/ready_callbacks");
  18. var ready = readyCallbacks.add;
  19. var each = require("../core/utils/iterator").each;
  20. var devices = require("../core/devices");
  21. var viewPortUtils = require("../core/utils/view_port");
  22. var themeReadyCallback = require("./themes_callback");
  23. var viewPort = viewPortUtils.value;
  24. var Promise = require("../core/polyfills/promise");
  25. var viewPortChanged = viewPortUtils.changeCallback;
  26. var DX_LINK_SELECTOR = "link[rel=dx-theme]";
  27. var THEME_ATTR = "data-theme";
  28. var ACTIVE_ATTR = "data-active";
  29. var DX_HAIRLINES_CLASS = "dx-hairlines";
  30. var context;
  31. var $activeThemeLink;
  32. var knownThemes;
  33. var currentThemeName;
  34. var pendingThemeName;
  35. var timerId;
  36. var THEME_MARKER_PREFIX = "dx.";
  37. function readThemeMarker() {
  38. if (!windowUtils.hasWindow()) {
  39. return null
  40. }
  41. var element = $("<div>", context).addClass("dx-theme-marker").appendTo(context.documentElement);
  42. var result;
  43. try {
  44. result = element.css("fontFamily");
  45. if (!result) {
  46. return null
  47. }
  48. result = result.replace(/["']/g, "");
  49. if (result.substr(0, THEME_MARKER_PREFIX.length) !== THEME_MARKER_PREFIX) {
  50. return null
  51. }
  52. return result.substr(THEME_MARKER_PREFIX.length)
  53. } finally {
  54. element.remove()
  55. }
  56. }
  57. function waitForThemeLoad(themeName) {
  58. var waitStartTime;
  59. pendingThemeName = themeName;
  60. function handleLoaded() {
  61. pendingThemeName = null;
  62. themeReadyCallback.fire();
  63. themeReadyCallback.empty()
  64. }
  65. if (isPendingThemeLoaded()) {
  66. handleLoaded()
  67. } else {
  68. waitStartTime = Date.now();
  69. timerId = setInterval(function() {
  70. var isLoaded = isPendingThemeLoaded();
  71. var isTimeout = !isLoaded && Date.now() - waitStartTime > 15e3;
  72. if (isTimeout) {
  73. errors.log("W0004", pendingThemeName)
  74. }
  75. if (isLoaded || isTimeout) {
  76. clearInterval(timerId);
  77. timerId = void 0;
  78. handleLoaded()
  79. }
  80. }, 10)
  81. }
  82. }
  83. function isPendingThemeLoaded() {
  84. return !pendingThemeName || readThemeMarker() === pendingThemeName
  85. }
  86. function processMarkup() {
  87. var $allThemeLinks = $(DX_LINK_SELECTOR, context);
  88. if (!$allThemeLinks.length) {
  89. return
  90. }
  91. knownThemes = {};
  92. $activeThemeLink = $(domUtils.createMarkupFromString("<link rel=stylesheet>"), context);
  93. $allThemeLinks.each(function() {
  94. var link = $(this, context);
  95. var fullThemeName = link.attr(THEME_ATTR);
  96. var url = link.attr("href");
  97. var isActive = "true" === link.attr(ACTIVE_ATTR);
  98. knownThemes[fullThemeName] = {
  99. url: url,
  100. isActive: isActive
  101. }
  102. });
  103. $allThemeLinks.last().after($activeThemeLink);
  104. $allThemeLinks.remove()
  105. }
  106. function resolveFullThemeName(desiredThemeName) {
  107. var desiredThemeParts = desiredThemeName.split(".");
  108. var result = null;
  109. if (knownThemes) {
  110. if (desiredThemeName in knownThemes) {
  111. return desiredThemeName
  112. }
  113. each(knownThemes, function(knownThemeName, themeData) {
  114. var knownThemeParts = knownThemeName.split(".");
  115. if (knownThemeParts[0] !== desiredThemeParts[0]) {
  116. return
  117. }
  118. if (desiredThemeParts[1] && desiredThemeParts[1] !== knownThemeParts[1]) {
  119. return
  120. }
  121. if (desiredThemeParts[2] && desiredThemeParts[2] !== knownThemeParts[2]) {
  122. return
  123. }
  124. if (!result || themeData.isActive) {
  125. result = knownThemeName
  126. }
  127. if (themeData.isActive) {
  128. return false
  129. }
  130. })
  131. }
  132. return result
  133. }
  134. function initContext(newContext) {
  135. try {
  136. if (newContext !== context) {
  137. knownThemes = null
  138. }
  139. } catch (x) {
  140. knownThemes = null
  141. }
  142. context = newContext
  143. }
  144. function init(options) {
  145. options = options || {};
  146. initContext(options.context || domAdapter.getDocument());
  147. if (!context) {
  148. return
  149. }
  150. processMarkup();
  151. currentThemeName = void 0;
  152. current(options)
  153. }
  154. function current(options) {
  155. if (!arguments.length) {
  156. currentThemeName = currentThemeName || readThemeMarker();
  157. return currentThemeName
  158. }
  159. detachCssClasses(viewPort());
  160. options = options || {};
  161. if ("string" === typeof options) {
  162. options = {
  163. theme: options
  164. }
  165. }
  166. var isAutoInit = options._autoInit;
  167. var loadCallback = options.loadCallback;
  168. var currentThemeData;
  169. currentThemeName = options.theme || currentThemeName;
  170. if (isAutoInit && !currentThemeName) {
  171. currentThemeName = themeNameFromDevice(devices.current())
  172. }
  173. currentThemeName = resolveFullThemeName(currentThemeName);
  174. if (currentThemeName) {
  175. currentThemeData = knownThemes[currentThemeName]
  176. }
  177. if (loadCallback) {
  178. themeReadyCallback.add(loadCallback)
  179. }
  180. if (currentThemeData) {
  181. $activeThemeLink.attr("href", knownThemes[currentThemeName].url);
  182. if ((themeReadyCallback.has() || options._forceTimeout) && !timerId) {
  183. waitForThemeLoad(currentThemeName)
  184. } else {
  185. if (pendingThemeName) {
  186. pendingThemeName = currentThemeName
  187. }
  188. }
  189. } else {
  190. if (isAutoInit) {
  191. themeReadyCallback.fire();
  192. themeReadyCallback.empty()
  193. } else {
  194. throw errors.Error("E0021", currentThemeName)
  195. }
  196. }
  197. checkThemeDeprecation();
  198. attachCssClasses(viewPortUtils.originalViewPort(), currentThemeName)
  199. }
  200. function themeNameFromDevice(device) {
  201. var themeName = device.platform;
  202. switch (themeName) {
  203. case "ios":
  204. return "ios7";
  205. case "android":
  206. case "win":
  207. return "generic"
  208. }
  209. return themeName
  210. }
  211. function getCssClasses(themeName) {
  212. themeName = themeName || current();
  213. var result = [];
  214. var themeNameParts = themeName && themeName.split(".");
  215. if (themeNameParts) {
  216. result.push("dx-theme-" + themeNameParts[0], "dx-theme-" + themeNameParts[0] + "-typography");
  217. if (themeNameParts.length > 1) {
  218. result.push("dx-color-scheme-" + themeNameParts[1] + (isMaterial(themeName) ? "-" + themeNameParts[2] : ""))
  219. }
  220. }
  221. return result
  222. }
  223. var themeClasses;
  224. function attachCssClasses(element, themeName) {
  225. themeClasses = getCssClasses(themeName).join(" ");
  226. $(element).addClass(themeClasses);
  227. var activateHairlines = function() {
  228. var pixelRatio = windowUtils.hasWindow() && window.devicePixelRatio;
  229. if (!pixelRatio || pixelRatio < 2) {
  230. return
  231. }
  232. var $tester = $("<div>");
  233. $tester.css("border", ".5px solid transparent");
  234. $("body").append($tester);
  235. if (1 === $tester.outerHeight()) {
  236. $(element).addClass(DX_HAIRLINES_CLASS);
  237. themeClasses += " " + DX_HAIRLINES_CLASS
  238. }
  239. $tester.remove()
  240. };
  241. activateHairlines()
  242. }
  243. function detachCssClasses(element) {
  244. $(element).removeClass(themeClasses)
  245. }
  246. function themeReady(callback) {
  247. themeReadyCallback.add(callback)
  248. }
  249. function isTheme(themeRegExp, themeName) {
  250. if (!themeName) {
  251. themeName = currentThemeName || readThemeMarker()
  252. }
  253. return new RegExp(themeRegExp).test(themeName)
  254. }
  255. function isMaterial(themeName) {
  256. return isTheme("material", themeName)
  257. }
  258. function isIos7(themeName) {
  259. return isTheme("ios7", themeName)
  260. }
  261. function isGeneric(themeName) {
  262. return isTheme("generic", themeName)
  263. }
  264. function checkThemeDeprecation() {
  265. if (isIos7()) {
  266. errors.log("W0010", "The 'ios7' theme", "19.1", "Use the 'generic' theme instead.")
  267. }
  268. }
  269. function isWebFontLoaded(text, fontWeight) {
  270. var testedFont = "Roboto, RobotoFallback, Arial";
  271. var etalonFont = "Arial";
  272. var document = domAdapter.getDocument();
  273. var testElement = document.createElement("span");
  274. testElement.style.position = "absolute";
  275. testElement.style.top = "-9999px";
  276. testElement.style.left = "-9999px";
  277. testElement.style.visibility = "hidden";
  278. testElement.style.fontFamily = etalonFont;
  279. testElement.style.fontSize = "250px";
  280. testElement.style.fontWeight = fontWeight;
  281. testElement.innerHTML = text;
  282. document.body.appendChild(testElement);
  283. var etalonFontWidth = testElement.offsetWidth;
  284. testElement.style.fontFamily = testedFont;
  285. var testedFontWidth = testElement.offsetWidth;
  286. testElement.parentNode.removeChild(testElement);
  287. return etalonFontWidth !== testedFontWidth
  288. }
  289. function waitWebFont(text, fontWeight) {
  290. var interval = 15;
  291. var timeout = 2e3;
  292. return new Promise(function(resolve) {
  293. var check = function() {
  294. if (isWebFontLoaded(text, fontWeight)) {
  295. clear()
  296. }
  297. };
  298. var clear = function() {
  299. clearInterval(intervalId);
  300. clearTimeout(timeoutId);
  301. resolve()
  302. };
  303. var intervalId = setInterval(check, interval);
  304. var timeoutId = setTimeout(clear, timeout)
  305. })
  306. }
  307. var initDeferred = new Deferred;
  308. function autoInit() {
  309. init({
  310. _autoInit: true,
  311. _forceTimeout: true
  312. });
  313. if ($(DX_LINK_SELECTOR, context).length) {
  314. throw errors.Error("E0022")
  315. }
  316. initDeferred.resolve()
  317. }
  318. if (windowUtils.hasWindow()) {
  319. autoInit()
  320. } else {
  321. ready(autoInit)
  322. }
  323. viewPortChanged.add(function(viewPort, prevViewPort) {
  324. initDeferred.done(function() {
  325. detachCssClasses(prevViewPort);
  326. attachCssClasses(viewPort)
  327. })
  328. });
  329. devices.changed.add(function() {
  330. init({
  331. _autoInit: true
  332. })
  333. });
  334. exports.current = current;
  335. exports.ready = themeReady;
  336. exports.init = init;
  337. exports.attachCssClasses = attachCssClasses;
  338. exports.detachCssClasses = detachCssClasses;
  339. exports.themeNameFromDevice = themeNameFromDevice;
  340. exports.waitForThemeLoad = waitForThemeLoad;
  341. exports.isMaterial = isMaterial;
  342. exports.isIos7 = isIos7;
  343. exports.isGeneric = isGeneric;
  344. exports.isWebFontLoaded = isWebFontLoaded;
  345. exports.waitWebFont = waitWebFont;
  346. exports.resetTheme = function() {
  347. $activeThemeLink && $activeThemeLink.attr("href", "about:blank");
  348. currentThemeName = null;
  349. pendingThemeName = null
  350. };
  351. module.exports.default = module.exports;