keycloakify 10.1.0-rc.0 → 10.1.0

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 (205) hide show
  1. package/PUBLIC_URL.js +2 -2
  2. package/PUBLIC_URL.js.map +1 -1
  3. package/account/KcContext/kcContextMocks.js +3 -3
  4. package/account/KcContext/kcContextMocks.js.map +1 -1
  5. package/bin/193.index.js +198 -62
  6. package/bin/{365.index.js → 20.index.js} +302 -263
  7. package/bin/31.index.js +15 -23
  8. package/bin/{430.index.js → 33.index.js} +579 -4
  9. package/bin/{678.index.js → 36.index.js} +2 -577
  10. package/bin/{440.index.js → 499.index.js} +259 -181
  11. package/bin/526.index.js +3 -784
  12. package/bin/599.index.js +4 -1
  13. package/bin/780.index.js +1 -1
  14. package/bin/{525.index.js → 903.index.js} +4840 -97
  15. package/bin/932.index.js +115 -886
  16. package/bin/main.js +4 -4
  17. package/bin/shared/buildContext.d.ts +0 -2
  18. package/bin/shared/buildContext.js.map +1 -1
  19. package/bin/shared/constants.d.ts +5 -6
  20. package/bin/shared/constants.js +5 -6
  21. package/bin/shared/constants.js.map +1 -1
  22. package/bin/shared/copyKeycloakResourcesToPublic.d.ts +2 -4
  23. package/bin/shared/copyKeycloakResourcesToPublic.js.map +1 -1
  24. package/login/KcContext/KcContext.d.ts +9 -18
  25. package/login/KcContext/KcContext.js.map +1 -1
  26. package/login/KcContext/kcContextMocks.js +6 -10
  27. package/login/KcContext/kcContextMocks.js.map +1 -1
  28. package/login/Template.js +4 -59
  29. package/login/Template.js.map +1 -1
  30. package/login/Template.useStylesAndScripts.d.ts +17 -0
  31. package/login/Template.useStylesAndScripts.js +69 -0
  32. package/login/Template.useStylesAndScripts.js.map +1 -0
  33. package/login/pages/Login.js +1 -1
  34. package/login/pages/Login.js.map +1 -1
  35. package/login/pages/LoginIdpLinkConfirmOverride.js +0 -1
  36. package/login/pages/LoginIdpLinkConfirmOverride.js.map +1 -1
  37. package/login/pages/LoginPasskeysConditionalAuthenticate.js +6 -49
  38. package/login/pages/LoginPasskeysConditionalAuthenticate.js.map +1 -1
  39. package/login/pages/LoginPasskeysConditionalAuthenticate.useScript.d.ts +20 -0
  40. package/login/pages/LoginPasskeysConditionalAuthenticate.useScript.js +49 -0
  41. package/login/pages/LoginPasskeysConditionalAuthenticate.useScript.js.map +1 -0
  42. package/login/pages/LoginRecoveryAuthnCodeConfig.js +4 -126
  43. package/login/pages/LoginRecoveryAuthnCodeConfig.js.map +1 -1
  44. package/login/pages/LoginRecoveryAuthnCodeConfig.useScript.d.ts +9 -0
  45. package/login/pages/LoginRecoveryAuthnCodeConfig.useScript.js +133 -0
  46. package/login/pages/LoginRecoveryAuthnCodeConfig.useScript.js.map +1 -0
  47. package/login/pages/LoginUsername.js +1 -1
  48. package/login/pages/LoginUsername.js.map +1 -1
  49. package/login/pages/Register.js +6 -3
  50. package/login/pages/Register.js.map +1 -1
  51. package/login/pages/WebauthnAuthenticate.js +13 -116
  52. package/login/pages/WebauthnAuthenticate.js.map +1 -1
  53. package/login/pages/WebauthnAuthenticate.useScript.d.ts +21 -0
  54. package/login/pages/WebauthnAuthenticate.useScript.js +41 -0
  55. package/login/pages/WebauthnAuthenticate.useScript.js.map +1 -0
  56. package/login/pages/WebauthnRegister.js +8 -178
  57. package/login/pages/WebauthnRegister.js.map +1 -1
  58. package/login/pages/WebauthnRegister.useScript.d.ts +27 -0
  59. package/login/pages/WebauthnRegister.useScript.js +49 -0
  60. package/login/pages/WebauthnRegister.useScript.js.map +1 -0
  61. package/package.json +125 -15
  62. package/res/account-v1/account.ftl +70 -0
  63. package/res/account-v1/applications.ftl +76 -0
  64. package/res/account-v1/federatedIdentity.ftl +42 -0
  65. package/res/account-v1/log.ftl +35 -0
  66. package/res/account-v1/messages/messages_ar.properties +406 -0
  67. package/res/account-v1/messages/messages_ca.properties +147 -0
  68. package/res/account-v1/messages/messages_cs.properties +171 -0
  69. package/res/account-v1/messages/messages_da.properties +339 -0
  70. package/res/account-v1/messages/messages_de.properties +353 -0
  71. package/res/account-v1/messages/messages_en.properties +404 -0
  72. package/res/account-v1/messages/messages_es.properties +147 -0
  73. package/res/account-v1/messages/messages_fi.properties +400 -0
  74. package/res/account-v1/messages/messages_fr.properties +180 -0
  75. package/res/account-v1/messages/messages_hu.properties +334 -0
  76. package/res/account-v1/messages/messages_it.properties +336 -0
  77. package/res/account-v1/messages/messages_ja.properties +335 -0
  78. package/res/account-v1/messages/messages_lt.properties +154 -0
  79. package/res/account-v1/messages/messages_lv.properties +197 -0
  80. package/res/account-v1/messages/messages_nl.properties +371 -0
  81. package/res/account-v1/messages/messages_no.properties +152 -0
  82. package/res/account-v1/messages/messages_pl.properties +248 -0
  83. package/res/account-v1/messages/messages_pt_BR.properties +349 -0
  84. package/res/account-v1/messages/messages_ru.properties +235 -0
  85. package/res/account-v1/messages/messages_sk.properties +196 -0
  86. package/res/account-v1/messages/messages_sv.properties +150 -0
  87. package/res/account-v1/messages/messages_tr.properties +315 -0
  88. package/res/account-v1/messages/messages_zh_CN.properties +153 -0
  89. package/res/account-v1/password.ftl +59 -0
  90. package/res/account-v1/resource-detail.ftl +277 -0
  91. package/res/account-v1/resources/css/account.css +277 -0
  92. package/res/account-v1/resources/img/icon-sidebar-active.png +0 -0
  93. package/res/account-v1/resources/img/keycloak-logo.png +0 -0
  94. package/res/account-v1/resources/img/logo.png +0 -0
  95. package/res/account-v1/resources/resources-common/img/favicon.ico +0 -0
  96. package/res/account-v1/resources/resources-common/node_modules/patternfly/dist/css/patternfly-additions.min.css +5 -0
  97. package/res/account-v1/resources/resources-common/node_modules/patternfly/dist/css/patternfly.min.css +8 -0
  98. package/res/account-v1/resources/resources-common/node_modules/patternfly/dist/fonts/OpenSans-Bold-webfont.woff2 +0 -0
  99. package/res/account-v1/resources/resources-common/node_modules/patternfly/dist/fonts/OpenSans-Light-webfont.woff2 +0 -0
  100. package/res/account-v1/resources/resources-common/node_modules/patternfly/dist/fonts/OpenSans-Regular-webfont.woff2 +0 -0
  101. package/res/account-v1/resources/resources-common/node_modules/patternfly/dist/fonts/OpenSans-Semibold-webfont.woff2 +0 -0
  102. package/res/account-v1/resources/resources-common/node_modules/patternfly/dist/fonts/PatternFlyIcons-webfont.ttf +0 -0
  103. package/res/account-v1/resources/resources-common/node_modules/patternfly/dist/fonts/PatternFlyIcons-webfont.woff +0 -0
  104. package/res/account-v1/resources.ftl +403 -0
  105. package/res/account-v1/sessions.ftl +44 -0
  106. package/res/account-v1/template.ftl +88 -0
  107. package/res/account-v1/theme.properties +14 -0
  108. package/res/account-v1/totp.ftl +141 -0
  109. package/res/public/.keycloakify/account/css/account.css +277 -0
  110. package/res/public/.keycloakify/account/img/icon-sidebar-active.png +0 -0
  111. package/res/public/.keycloakify/account/img/keycloak-logo.png +0 -0
  112. package/res/public/.keycloakify/account/img/logo.png +0 -0
  113. package/res/public/.keycloakify/account/resources-common/img/favicon.ico +0 -0
  114. package/res/public/.keycloakify/account/resources-common/node_modules/patternfly/dist/css/patternfly-additions.min.css +5 -0
  115. package/res/public/.keycloakify/account/resources-common/node_modules/patternfly/dist/css/patternfly.min.css +8 -0
  116. package/res/public/.keycloakify/account/resources-common/node_modules/patternfly/dist/fonts/OpenSans-Bold-webfont.woff2 +0 -0
  117. package/res/public/.keycloakify/account/resources-common/node_modules/patternfly/dist/fonts/OpenSans-Light-webfont.woff2 +0 -0
  118. package/res/public/.keycloakify/account/resources-common/node_modules/patternfly/dist/fonts/OpenSans-Regular-webfont.woff2 +0 -0
  119. package/res/public/.keycloakify/account/resources-common/node_modules/patternfly/dist/fonts/OpenSans-Semibold-webfont.woff2 +0 -0
  120. package/res/public/.keycloakify/account/resources-common/node_modules/patternfly/dist/fonts/PatternFlyIcons-webfont.ttf +0 -0
  121. package/res/public/.keycloakify/account/resources-common/node_modules/patternfly/dist/fonts/PatternFlyIcons-webfont.woff +0 -0
  122. package/res/public/.keycloakify/login/css/login.css +629 -0
  123. package/res/public/.keycloakify/login/img/feedback-error-arrow-down.png +0 -0
  124. package/res/public/.keycloakify/login/img/feedback-error-sign.png +0 -0
  125. package/res/public/.keycloakify/login/img/feedback-success-arrow-down.png +0 -0
  126. package/res/public/.keycloakify/login/img/feedback-success-sign.png +0 -0
  127. package/res/public/.keycloakify/login/img/feedback-warning-arrow-down.png +0 -0
  128. package/res/public/.keycloakify/login/img/feedback-warning-sign.png +0 -0
  129. package/res/public/.keycloakify/login/img/keycloak-bg.png +0 -0
  130. package/res/public/.keycloakify/login/img/keycloak-logo-text.png +0 -0
  131. package/res/public/.keycloakify/login/img/keycloak-logo.png +0 -0
  132. package/res/public/.keycloakify/login/js/authChecker.js +49 -0
  133. package/res/public/.keycloakify/login/js/common.js +48 -0
  134. package/res/public/.keycloakify/login/js/kcMultivalued.js +106 -0
  135. package/res/public/.keycloakify/login/js/kcNumberFormat.js +21 -0
  136. package/res/public/.keycloakify/login/js/kcNumberUnFormat.js +19 -0
  137. package/res/public/.keycloakify/login/js/menu-button-links.js +315 -0
  138. package/{src/bin/shared/downloadKeycloakDefaultTheme/extra-assets → res/public/.keycloakify/login/js}/passkeysConditionalAuth.js +1 -1
  139. package/res/public/.keycloakify/login/js/passwordVisibility.js +15 -0
  140. package/res/public/.keycloakify/login/js/userProfile.js +71 -0
  141. package/res/public/.keycloakify/login/js/webauthnRegister.js +140 -0
  142. package/res/public/.keycloakify/login/resources-common/img/favicon.ico +0 -0
  143. package/res/public/.keycloakify/login/resources-common/lib/pficon/pficon.css +22 -0
  144. package/res/public/.keycloakify/login/resources-common/lib/pficon/pficon.woff2 +0 -0
  145. package/res/public/.keycloakify/login/resources-common/node_modules/@patternfly/patternfly/patternfly.min.css +2 -0
  146. package/res/public/.keycloakify/login/resources-common/node_modules/jquery/dist/jquery.min.js +2 -0
  147. package/res/public/.keycloakify/login/resources-common/node_modules/patternfly/dist/css/patternfly-additions.min.css +5 -0
  148. package/res/public/.keycloakify/login/resources-common/node_modules/patternfly/dist/css/patternfly.min.css +8 -0
  149. package/res/public/.keycloakify/login/resources-common/node_modules/patternfly/dist/fonts/OpenSans-Bold-webfont.ttf +0 -0
  150. package/res/public/.keycloakify/login/resources-common/node_modules/patternfly/dist/fonts/OpenSans-Bold-webfont.woff +0 -0
  151. package/res/public/.keycloakify/login/resources-common/node_modules/patternfly/dist/fonts/OpenSans-Bold-webfont.woff2 +0 -0
  152. package/res/public/.keycloakify/login/resources-common/node_modules/patternfly/dist/fonts/OpenSans-Light-webfont.woff2 +0 -0
  153. package/res/public/.keycloakify/login/resources-common/node_modules/patternfly/dist/fonts/OpenSans-Regular-webfont.woff2 +0 -0
  154. package/res/public/.keycloakify/login/resources-common/node_modules/patternfly/dist/fonts/OpenSans-Semibold-webfont.woff2 +0 -0
  155. package/res/public/.keycloakify/login/resources-common/node_modules/patternfly/dist/fonts/PatternFlyIcons-webfont.ttf +0 -0
  156. package/res/public/.keycloakify/login/resources-common/node_modules/patternfly/dist/fonts/PatternFlyIcons-webfont.woff +0 -0
  157. package/res/public/.keycloakify/login/resources-common/node_modules/patternfly/dist/fonts/fontawesome-webfont.woff2 +0 -0
  158. package/res/public/.keycloakify/login/resources-common/node_modules/patternfly/dist/img/bg-login.jpg +0 -0
  159. package/res/public/.keycloakify/login/resources-common/node_modules/rfc4648/lib/rfc4648.js +178 -0
  160. package/src/PUBLIC_URL.ts +5 -3
  161. package/src/account/KcContext/kcContextMocks.ts +3 -3
  162. package/src/bin/copy-keycloak-resources-to-public.ts +1 -1
  163. package/src/bin/initialize-account-theme/initializeAccountTheme_singlePage.ts +4 -1
  164. package/src/bin/initialize-email-theme.ts +28 -8
  165. package/src/bin/keycloakify/buildJars/buildJar.ts +3 -7
  166. package/src/bin/keycloakify/generateFtl/generateFtl.ts +3 -7
  167. package/src/bin/keycloakify/generateResources/generateResourcesForMainTheme.ts +65 -90
  168. package/src/bin/keycloakify/keycloakify.ts +38 -21
  169. package/src/bin/keycloakify/replacers/replaceImportsInCssCode.ts +2 -2
  170. package/src/bin/keycloakify/replacers/replaceImportsInJsCode/vite.ts +3 -3
  171. package/src/bin/keycloakify/replacers/replaceImportsInJsCode/webpack.ts +3 -3
  172. package/src/bin/shared/buildContext.ts +1 -8
  173. package/src/bin/shared/constants.ts +5 -7
  174. package/src/bin/shared/copyKeycloakResourcesToPublic.ts +30 -40
  175. package/src/bin/start-keycloak/start-keycloak.ts +3 -7
  176. package/src/login/KcContext/KcContext.ts +9 -18
  177. package/src/login/KcContext/kcContextMocks.ts +5 -13
  178. package/src/login/Template.tsx +4 -67
  179. package/src/login/Template.useStylesAndScripts.ts +94 -0
  180. package/src/login/pages/Login.tsx +1 -1
  181. package/src/login/pages/LoginIdpLinkConfirmOverride.tsx +0 -1
  182. package/src/login/pages/LoginPasskeysConditionalAuthenticate.tsx +6 -81
  183. package/src/login/pages/LoginPasskeysConditionalAuthenticate.useScript.tsx +72 -0
  184. package/src/login/pages/LoginRecoveryAuthnCodeConfig.tsx +4 -126
  185. package/src/login/pages/LoginRecoveryAuthnCodeConfig.useScript.tsx +142 -0
  186. package/src/login/pages/LoginUsername.tsx +1 -1
  187. package/src/login/pages/Register.tsx +35 -13
  188. package/src/login/pages/WebauthnAuthenticate.tsx +20 -133
  189. package/src/login/pages/WebauthnAuthenticate.useScript.tsx +64 -0
  190. package/src/login/pages/WebauthnRegister.tsx +8 -195
  191. package/src/login/pages/WebauthnRegister.useScript.tsx +93 -0
  192. package/src/tools/useInsertScriptTags.ts +14 -4
  193. package/src/vite-plugin/vite-plugin.ts +11 -15
  194. package/tools/useInsertScriptTags.d.ts +2 -2
  195. package/tools/useInsertScriptTags.js +8 -2
  196. package/tools/useInsertScriptTags.js.map +1 -1
  197. package/vite-plugin/index.js +3357 -47132
  198. package/bin/697.index.js +0 -4749
  199. package/bin/shared/downloadKeycloakStaticResources.d.ts +0 -9
  200. package/bin/shared/downloadKeycloakStaticResources.js.map +0 -1
  201. package/src/bin/keycloakify/generateResources/bringInAccountV1.ts +0 -89
  202. package/src/bin/shared/downloadKeycloakDefaultTheme/downloadKeycloakDefaultTheme.ts +0 -337
  203. package/src/bin/shared/downloadKeycloakDefaultTheme/index.ts +0 -1
  204. package/src/bin/shared/downloadKeycloakStaticResources.ts +0 -53
  205. /package/{src/bin/shared/downloadKeycloakDefaultTheme/extra-assets → res/public/.keycloakify/login/js}/webauthnAuthenticate.js +0 -0
@@ -0,0 +1,48 @@
1
+ export const formatNumber = (input, format) => {
2
+ if (!input) {
3
+ return "";
4
+ }
5
+
6
+ // array holding the patterns for the number of expected digits in each part
7
+ const digitPattern = format.match(/{\d+}/g);
8
+
9
+ if (!digitPattern) {
10
+ return "";
11
+ }
12
+
13
+ // calculate the maximum size of the given pattern based on the sum of the expected digits
14
+ const maxSize = digitPattern.reduce((total, p) => total + parseInt(p.replace("{", "").replace("}", "")), 0)
15
+
16
+ // keep only digits
17
+ let rawValue = input.replace(/\D+/g, '');
18
+
19
+ // make sure the value is a number
20
+ if (parseInt(rawValue) != rawValue) {
21
+ return "";
22
+ }
23
+
24
+ // make sure the number of digits does not exceed the maximum size
25
+ if (rawValue.length > maxSize) {
26
+ rawValue = rawValue.substring(0, maxSize);
27
+ }
28
+
29
+ // build the regex based based on the expected digits in each part
30
+ const formatter = digitPattern.reduce((result, p) => result + `(\\d${p})`, "^");
31
+
32
+ // if the current digits match the pattern we have each group of digits in an array
33
+ let digits = new RegExp(formatter).exec(rawValue);
34
+
35
+ // no match, return the raw value without any format
36
+ if (!digits) {
37
+ return input;
38
+ }
39
+
40
+ let result = format;
41
+
42
+ // finally format the current digits accordingly to the given format
43
+ for (let i = 0; i < digitPattern.length; i++) {
44
+ result = result.replace(digitPattern[i], digits[i + 1]);
45
+ }
46
+
47
+ return result;
48
+ }
@@ -0,0 +1,106 @@
1
+ const DATA_KC_MULTIVALUED = 'data-kcMultivalued';
2
+ const KC_ADD_ACTION_PREFIX = "kc-add-";
3
+ const KC_REMOVE_ACTION_PREFIX = "kc-remove-";
4
+ const KC_ACTION_CLASS = "pf-c-button pf-m-inline pf-m-link";
5
+
6
+ function createAddAction(element) {
7
+ const action = createAction("Add value",
8
+ KC_ADD_ACTION_PREFIX,
9
+ element,
10
+ () => {
11
+ const name = element.getAttribute("name");
12
+ const elements = getInputElementsByName().get(name);
13
+ const length = elements.length;
14
+
15
+ if (length === 0) {
16
+ return;
17
+ }
18
+
19
+ const lastNode = elements[length - 1];
20
+ const newNode = lastNode.cloneNode(true);
21
+ newNode.setAttribute("id", name + "-" + elements.length);
22
+ newNode.value = "";
23
+ lastNode.after(newNode);
24
+
25
+ render();
26
+ });
27
+
28
+ element.after(action);
29
+ }
30
+
31
+ function createRemoveAction(element, isLastElement) {
32
+ let text = "Remove";
33
+
34
+ if (isLastElement) {
35
+ text = text + " | ";
36
+ }
37
+
38
+ const action = createAction(text, KC_REMOVE_ACTION_PREFIX, element, () => {
39
+ removeActions(element);
40
+ element.remove();
41
+ render();
42
+ });
43
+
44
+ element.insertAdjacentElement('afterend', action);
45
+ }
46
+
47
+ function getInputElementsByName() {
48
+ const selector = document.querySelectorAll(`[${DATA_KC_MULTIVALUED}]`);
49
+ const elementsByName = new Map();
50
+
51
+ for (let element of Array.from(selector.values())) {
52
+ let name = element.getAttribute("name");
53
+ let elements = elementsByName.get(name);
54
+
55
+ if (!elements) {
56
+ elements = [];
57
+ elementsByName.set(name, elements);
58
+ }
59
+
60
+ elements.push(element);
61
+ }
62
+
63
+ return elementsByName;
64
+ }
65
+
66
+ function removeActions(element) {
67
+ for (let actionPrefix of [KC_ADD_ACTION_PREFIX, KC_REMOVE_ACTION_PREFIX]) {
68
+ const action = document.getElementById(actionPrefix + element.getAttribute("id"));
69
+
70
+ if (action) {
71
+ action.remove();
72
+ }
73
+ }
74
+ }
75
+
76
+ function createAction(text, type, element, onClick) {
77
+ const action = document.createElement("button")
78
+ action.setAttribute("id", type + element.getAttribute("id"));
79
+ action.setAttribute("type", "button");
80
+ action.innerText = text;
81
+ action.setAttribute("class", KC_ACTION_CLASS);
82
+ action.addEventListener("click", onClick);
83
+ return action;
84
+ }
85
+
86
+ function render() {
87
+ getInputElementsByName().forEach((elements, name) => {
88
+ elements.forEach((element, index) => {
89
+ removeActions(element);
90
+
91
+ element.setAttribute("id", name + "-" + index);
92
+
93
+ const lastNode = element === elements[elements.length - 1];
94
+
95
+ if (lastNode) {
96
+ createAddAction(element);
97
+ }
98
+
99
+ if (elements.length > 1) {
100
+ createRemoveAction(element, lastNode);
101
+ }
102
+ });
103
+ });
104
+ }
105
+
106
+ render();
@@ -0,0 +1,21 @@
1
+ // @ts-check
2
+ import { formatNumber } from "./common.js";
3
+ import { registerElementAnnotatedBy } from "./userProfile.js";
4
+
5
+ const KC_NUMBER_FORMAT = "kcNumberFormat";
6
+
7
+ registerElementAnnotatedBy({
8
+ name: KC_NUMBER_FORMAT,
9
+ onAdd(element) {
10
+ const formatValue = () => {
11
+ const format = element.getAttribute(`data-${KC_NUMBER_FORMAT}`);
12
+ element.value = formatNumber(element.value, format);
13
+ };
14
+
15
+ element.addEventListener("keyup", formatValue);
16
+
17
+ formatValue();
18
+
19
+ return () => element.removeEventListener("keyup", formatValue);
20
+ },
21
+ });
@@ -0,0 +1,19 @@
1
+ // @ts-check
2
+ import { formatNumber } from "./common.js";
3
+ import { registerElementAnnotatedBy } from "./userProfile.js";
4
+
5
+ const KC_NUMBER_UNFORMAT = 'kcNumberUnFormat';
6
+
7
+ registerElementAnnotatedBy({
8
+ name: KC_NUMBER_UNFORMAT,
9
+ onAdd(element) {
10
+ for (let form of document.forms) {
11
+ form.addEventListener('submit', (event) => {
12
+ const rawFormat = element.getAttribute(`data-${KC_NUMBER_UNFORMAT}`);
13
+ if (rawFormat) {
14
+ element.value = formatNumber(element.value, rawFormat);
15
+ }
16
+ });
17
+ }
18
+ },
19
+ });
@@ -0,0 +1,315 @@
1
+ // @ts-check
2
+ /*
3
+ * This content is licensed according to the W3C Software License at
4
+ * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
5
+ *
6
+ * File: menu-button-links.js
7
+ *
8
+ * Desc: Creates a menu button that opens a menu of links
9
+ *
10
+ * Modified by Peter Keuter to adhere to the coding standards of Keycloak
11
+ * Original file: https://www.w3.org/WAI/content-assets/wai-aria-practices/patterns/menu-button/examples/js/menu-button-links.js
12
+ * Source: https://www.w3.org/TR/wai-aria-practices/examples/menu-button/menu-button-links.html
13
+ */
14
+
15
+ class MenuButtonLinks {
16
+ constructor(domNode) {
17
+ this.domNode = domNode;
18
+ this.buttonNode = domNode.querySelector("button");
19
+ this.menuNode = domNode.querySelector('[role="menu"]');
20
+ this.menuitemNodes = [];
21
+ this.firstMenuitem = false;
22
+ this.lastMenuitem = false;
23
+ this.firstChars = [];
24
+
25
+ this.buttonNode.addEventListener("keydown", (e) => this.onButtonKeydown(e));
26
+ this.buttonNode.addEventListener("click", (e) => this.onButtonClick(e));
27
+
28
+ const nodes = domNode.querySelectorAll('[role="menuitem"]');
29
+
30
+ for (const menuitem of nodes) {
31
+ this.menuitemNodes.push(menuitem);
32
+ menuitem.tabIndex = -1;
33
+ this.firstChars.push(menuitem.textContent.trim()[0].toLowerCase());
34
+
35
+ menuitem.addEventListener("keydown", (e) => this.onMenuitemKeydown(e));
36
+
37
+ menuitem.addEventListener("mouseover", (e) =>
38
+ this.onMenuitemMouseover(e)
39
+ );
40
+
41
+ if (!this.firstMenuitem) {
42
+ this.firstMenuitem = menuitem;
43
+ }
44
+ this.lastMenuitem = menuitem;
45
+ }
46
+
47
+ domNode.addEventListener("focusin", () => this.onFocusin());
48
+ domNode.addEventListener("focusout", () => this.onFocusout());
49
+
50
+ window.addEventListener(
51
+ "mousedown",
52
+ (e) => this.onBackgroundMousedown(e),
53
+ true
54
+ );
55
+ }
56
+
57
+ setFocusToMenuitem = (newMenuitem) =>
58
+ this.menuitemNodes.forEach((item) => {
59
+ if (item === newMenuitem) {
60
+ item.tabIndex = 0;
61
+ newMenuitem.focus();
62
+ } else {
63
+ item.tabIndex = -1;
64
+ }
65
+ });
66
+
67
+ setFocusToFirstMenuitem = () => this.setFocusToMenuitem(this.firstMenuitem);
68
+
69
+ setFocusToLastMenuitem = () => this.setFocusToMenuitem(this.lastMenuitem);
70
+
71
+ setFocusToPreviousMenuitem = (currentMenuitem) => {
72
+ let newMenuitem, index;
73
+
74
+ if (currentMenuitem === this.firstMenuitem) {
75
+ newMenuitem = this.lastMenuitem;
76
+ } else {
77
+ index = this.menuitemNodes.indexOf(currentMenuitem);
78
+ newMenuitem = this.menuitemNodes[index - 1];
79
+ }
80
+
81
+ this.setFocusToMenuitem(newMenuitem);
82
+
83
+ return newMenuitem;
84
+ };
85
+
86
+ setFocusToNextMenuitem = (currentMenuitem) => {
87
+ let newMenuitem, index;
88
+
89
+ if (currentMenuitem === this.lastMenuitem) {
90
+ newMenuitem = this.firstMenuitem;
91
+ } else {
92
+ index = this.menuitemNodes.indexOf(currentMenuitem);
93
+ newMenuitem = this.menuitemNodes[index + 1];
94
+ }
95
+ this.setFocusToMenuitem(newMenuitem);
96
+
97
+ return newMenuitem;
98
+ };
99
+
100
+ setFocusByFirstCharacter = (currentMenuitem, char) => {
101
+ let start, index;
102
+
103
+ if (char.length > 1) {
104
+ return;
105
+ }
106
+
107
+ char = char.toLowerCase();
108
+
109
+ // Get start index for search based on position of currentItem
110
+ start = this.menuitemNodes.indexOf(currentMenuitem) + 1;
111
+ if (start >= this.menuitemNodes.length) {
112
+ start = 0;
113
+ }
114
+
115
+ // Check remaining slots in the menu
116
+ index = this.firstChars.indexOf(char, start);
117
+
118
+ // If not found in remaining slots, check from beginning
119
+ if (index === -1) {
120
+ index = this.firstChars.indexOf(char, 0);
121
+ }
122
+
123
+ // If match was found...
124
+ if (index > -1) {
125
+ this.setFocusToMenuitem(this.menuitemNodes[index]);
126
+ }
127
+ };
128
+
129
+ // Utilities
130
+
131
+ getIndexFirstChars = (startIndex, char) => {
132
+ for (let i = startIndex; i < this.firstChars.length; i++) {
133
+ if (char === this.firstChars[i]) {
134
+ return i;
135
+ }
136
+ }
137
+ return -1;
138
+ };
139
+
140
+ // Popup menu methods
141
+
142
+ openPopup = () => {
143
+ this.menuNode.style.display = "block";
144
+ this.buttonNode.setAttribute("aria-expanded", "true");
145
+ };
146
+
147
+ closePopup = () => {
148
+ if (this.isOpen()) {
149
+ this.buttonNode.setAttribute("aria-expanded", "false");
150
+ this.menuNode.style.removeProperty("display");
151
+ }
152
+ };
153
+
154
+ isOpen = () => {
155
+ return this.buttonNode.getAttribute("aria-expanded") === "true";
156
+ };
157
+
158
+ // Menu event handlers
159
+
160
+ onFocusin = () => {
161
+ this.domNode.classList.add("focus");
162
+ };
163
+
164
+ onFocusout = () => {
165
+ this.domNode.classList.remove("focus");
166
+ };
167
+
168
+ onButtonKeydown = (event) => {
169
+ const key = event.key;
170
+ let flag = false;
171
+
172
+ switch (key) {
173
+ case " ":
174
+ case "Enter":
175
+ case "ArrowDown":
176
+ case "Down":
177
+ this.openPopup();
178
+ this.setFocusToFirstMenuitem();
179
+ flag = true;
180
+ break;
181
+
182
+ case "Esc":
183
+ case "Escape":
184
+ this.closePopup();
185
+ this.buttonNode.focus();
186
+ flag = true;
187
+ break;
188
+
189
+ case "Up":
190
+ case "ArrowUp":
191
+ this.openPopup();
192
+ this.setFocusToLastMenuitem();
193
+ flag = true;
194
+ break;
195
+
196
+ default:
197
+ break;
198
+ }
199
+
200
+ if (flag) {
201
+ event.stopPropagation();
202
+ event.preventDefault();
203
+ }
204
+ };
205
+
206
+ onButtonClick(event) {
207
+ if (this.isOpen()) {
208
+ this.closePopup();
209
+ this.buttonNode.focus();
210
+ } else {
211
+ this.openPopup();
212
+ this.setFocusToFirstMenuitem();
213
+ }
214
+
215
+ event.stopPropagation();
216
+ event.preventDefault();
217
+ }
218
+
219
+ onMenuitemKeydown(event) {
220
+ const tgt = event.currentTarget;
221
+ const key = event.key;
222
+ let flag = false;
223
+
224
+ const isPrintableCharacter = (str) => str.length === 1 && str.match(/\S/);
225
+
226
+ if (event.ctrlKey || event.altKey || event.metaKey) {
227
+ return;
228
+ }
229
+
230
+ if (event.shiftKey) {
231
+ if (isPrintableCharacter(key)) {
232
+ this.setFocusByFirstCharacter(tgt, key);
233
+ flag = true;
234
+ }
235
+
236
+ if (event.key === "Tab") {
237
+ this.buttonNode.focus();
238
+ this.closePopup();
239
+ flag = true;
240
+ }
241
+ } else {
242
+ switch (key) {
243
+ case " ":
244
+ window.location.href = tgt.href;
245
+ break;
246
+
247
+ case "Esc":
248
+ case "Escape":
249
+ this.closePopup();
250
+ this.buttonNode.focus();
251
+ flag = true;
252
+ break;
253
+
254
+ case "Up":
255
+ case "ArrowUp":
256
+ this.setFocusToPreviousMenuitem(tgt);
257
+ flag = true;
258
+ break;
259
+
260
+ case "ArrowDown":
261
+ case "Down":
262
+ this.setFocusToNextMenuitem(tgt);
263
+ flag = true;
264
+ break;
265
+
266
+ case "Home":
267
+ case "PageUp":
268
+ this.setFocusToFirstMenuitem();
269
+ flag = true;
270
+ break;
271
+
272
+ case "End":
273
+ case "PageDown":
274
+ this.setFocusToLastMenuitem();
275
+ flag = true;
276
+ break;
277
+
278
+ case "Tab":
279
+ this.closePopup();
280
+ break;
281
+
282
+ default:
283
+ if (isPrintableCharacter(key)) {
284
+ this.setFocusByFirstCharacter(tgt, key);
285
+ flag = true;
286
+ }
287
+ break;
288
+ }
289
+ }
290
+
291
+ if (flag) {
292
+ event.stopPropagation();
293
+ event.preventDefault();
294
+ }
295
+ }
296
+
297
+ onMenuitemMouseover(event) {
298
+ const tgt = event.currentTarget;
299
+ tgt.focus();
300
+ }
301
+
302
+ onBackgroundMousedown(event) {
303
+ if (!this.domNode.contains(event.target)) {
304
+ if (this.isOpen()) {
305
+ this.closePopup();
306
+ this.buttonNode.focus();
307
+ }
308
+ }
309
+ }
310
+ }
311
+
312
+ const menuButtons = document.querySelectorAll(".menu-button-links");
313
+ for (const button of menuButtons) {
314
+ new MenuButtonLinks(button);
315
+ }
@@ -76,4 +76,4 @@ function getAllowCredentials() {
76
76
  }
77
77
  }
78
78
  return allowCredentials;
79
- }
79
+ }
@@ -0,0 +1,15 @@
1
+ const toggle = (button) => {
2
+ const passwordElement = document.getElementById(button.getAttribute('aria-controls'));
3
+ if (passwordElement.type === "password") {
4
+ passwordElement.type = "text";
5
+ button.children.item(0).className = button.dataset.iconHide;
6
+ button.setAttribute("aria-label", button.dataset.labelHide);
7
+ } else if(passwordElement.type === "text") {
8
+ passwordElement.type = "password";
9
+ button.children.item(0).className = button.dataset.iconShow;
10
+ button.setAttribute("aria-label", button.dataset.labelShow);
11
+ }
12
+ }
13
+
14
+ document.querySelectorAll('[data-password-toggle]')
15
+ .forEach(button => button.onclick = () => toggle(button));
@@ -0,0 +1,71 @@
1
+ // @ts-check
2
+ /**
3
+ * @typedef {Object} AnnotationDescriptor
4
+ * @property {string} name - The name of the field to register (e.g. `numberFormat`).
5
+ * @property {(element: HTMLElement) => (() => void) | void} onAdd - The function to call when a new element is added to the DOM.
6
+ */
7
+
8
+ const observer = new MutationObserver(onMutate);
9
+ observer.observe(document.body, { childList: true, subtree: true });
10
+
11
+ /** @type {AnnotationDescriptor[]} */
12
+ const descriptors = [];
13
+
14
+ /** @type {WeakMap<HTMLElement, () => void>} */
15
+ const cleanupFunctions = new WeakMap();
16
+
17
+ /**
18
+ * @param {AnnotationDescriptor} descriptor
19
+ */
20
+ export function registerElementAnnotatedBy(descriptor) {
21
+ descriptors.push(descriptor);
22
+
23
+ document.querySelectorAll(`[data-${descriptor.name}]`).forEach((element) => {
24
+ if (element instanceof HTMLElement) {
25
+ handleNewElement(element, descriptor);
26
+ }
27
+ });
28
+ }
29
+
30
+ /**
31
+ * @type {MutationCallback}
32
+ */
33
+ function onMutate(mutations) {
34
+ const removedNodes = mutations.flatMap((mutation) => Array.from(mutation.removedNodes));
35
+
36
+ for (const node of removedNodes) {
37
+ if (!(node instanceof HTMLElement)) {
38
+ continue;
39
+ }
40
+
41
+ const handleRemovedElement = cleanupFunctions.get(node);
42
+
43
+ if (handleRemovedElement) {
44
+ handleRemovedElement();
45
+ }
46
+
47
+ cleanupFunctions.delete(node);
48
+ }
49
+
50
+ const addedNodes = mutations.flatMap((mutation) => Array.from(mutation.addedNodes));
51
+
52
+ for (const descriptor of descriptors) {
53
+ for (const node of addedNodes) {
54
+ if (node instanceof HTMLElement && node.hasAttribute(`data-${descriptor.name}`)) {
55
+ handleNewElement(node, descriptor);
56
+ }
57
+ }
58
+ }
59
+ }
60
+
61
+ /**
62
+ * @param {HTMLElement} element
63
+ * @param {AnnotationDescriptor} descriptor
64
+ */
65
+ function handleNewElement(element, descriptor) {
66
+ const cleanup = descriptor.onAdd(element);
67
+
68
+ if (cleanup) {
69
+ cleanupFunctions.set(element, cleanup);
70
+ }
71
+ }