playbook_ui 16.4.0.pre.alpha.play2838formcustomvalidationsconsistency15347 → 16.4.0.pre.alpha.play287215277

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 47486b650716761b037a855cbb4d7bc840decb2bdc016abb00e36c7c38a18bd9
4
- data.tar.gz: 7e75068a145d827a2c0d3c507aa93b860271e6866f82a385566f37ec6a628343
3
+ metadata.gz: f192477f8ce86f7e7d34c1fcd3e04ce3ec19bce384af58ee7b670ad2020bb7da
4
+ data.tar.gz: cdf3515b0efd18f633a9f0b9c971fba9f5e1c86ba26e1072be960e42d0dce68d
5
5
  SHA512:
6
- metadata.gz: edfbc7125580ec249fa053b8e0bc2158d2b352ec1b470a51b59105a2973ae4ecc7fe7465e21c1b534b608325167f380a6d480ea20e3f760a4533d97c0e6b288f
7
- data.tar.gz: b81a924f047e85a95faed9d5a2d0e2a3ae0dde3c981d740047141b07624937d54a3b0899c6ac5b30448e55221645f4c38db5d97adb1286f5e324a0527ffbc25c
6
+ metadata.gz: 7ea0752cc8fa230d382be06fa02a93b19830976599733888686a05cc85feccef9024d16debed509130b7902c7251c62b6e1f2604796b4e0b1ca8259f020738d0
7
+ data.tar.gz: 92b037ce1dded39bc8a28f90c79fdfbbcbff46056af9b9c111d9a51e43a1e2b60e441f9ac77c25ab022fd5a6a6c238d1800f92416935fb634550f877f527819e
@@ -9,8 +9,6 @@ const ERROR_MESSAGE_SELECTOR = '.pb_body_kit_negative'
9
9
  const FORM_SELECTOR = 'form[data-pb-form-validation="true"]'
10
10
  const REQUIRED_FIELDS_SELECTOR = 'input[required],textarea[required],select[required]'
11
11
  const PHONE_NUMBER_VALIDATION_ERROR_SELECTOR = '[data-pb-phone-validation-error="true"]'
12
- const FORM_VALIDATION_MESSAGE_ATTR = 'data-pb-form-validation-message'
13
- const FORM_VALIDATION_MESSAGE_REUSED_ATTR = 'data-pb-form-validation-message-reused'
14
12
 
15
13
  const FIELD_EVENTS = [
16
14
  'change',
@@ -22,58 +20,26 @@ class PbFormValidation extends PbEnhancedElement {
22
20
  return FORM_SELECTOR
23
21
  }
24
22
 
25
- static start() {
26
- if (this.__pbStarted) return
27
- this.__pbStarted = true
28
- super.start()
29
- }
30
-
31
23
  connect() {
32
- this.boundFields = new WeakSet()
33
- this._fieldHandlers = new WeakMap()
34
- this._typeaheadFieldHandlers = new WeakMap()
35
-
36
- this.bindValidationListeners()
37
- this._setupMutationObserver()
38
- this._setupSubmitHandler()
39
- }
24
+ this.formValidationFields.forEach((field) => {
25
+ // Skip phone number inputs - they handle their own validation
26
+ const isPhoneNumberInput = field.closest('.pb_phone_number_input')
27
+ if (isPhoneNumberInput) return
40
28
 
41
- _setupMutationObserver() {
42
- this._pendingFieldsToBind = new Set()
43
- this._flushPendingFieldBinds = debounce(() => {
44
- if (!this._pendingFieldsToBind?.size) return
45
- this._pendingFieldsToBind.forEach((f) => this.bindFieldValidationListeners(f))
46
- this._pendingFieldsToBind.clear()
47
- }, 50)
29
+ // Skip TimePicker inputs - they handle their own validation
30
+ const isTimePickerInput = field.closest('.pb_time_picker')
31
+ if (isTimePickerInput) return
48
32
 
49
- this.mutationObserver = new MutationObserver((mutations) => {
50
- let sawRequired = false
51
- mutations.forEach(({ addedNodes }) => {
52
- addedNodes.forEach((node) => {
53
- if (!(node instanceof Element)) return
54
- if (node.matches(REQUIRED_FIELDS_SELECTOR)) {
55
- this._pendingFieldsToBind.add(node)
56
- sawRequired = true
57
- }
58
- node.querySelectorAll?.(REQUIRED_FIELDS_SELECTOR)?.forEach?.((el) => {
59
- this._pendingFieldsToBind.add(el)
60
- sawRequired = true
61
- })
62
- })
33
+ FIELD_EVENTS.forEach((e) => {
34
+ field.addEventListener(e, debounce((event) => {
35
+ this.validateFormField(event)
36
+ }, 250), false)
63
37
  })
64
- if (sawRequired) this._flushPendingFieldBinds()
65
38
  })
66
39
 
67
- this.mutationObserver.observe(this.element, { childList: true, subtree: true })
68
- }
69
-
70
- _setupSubmitHandler() {
40
+ // Add event listener to check for phone number validation errors
71
41
  this.element.addEventListener('submit', (event) => {
72
- if (this.validateOnSubmit()) {
73
- event.preventDefault()
74
- return false
75
- }
76
-
42
+ // Use setTimeout to ensure React state updates have completed
77
43
  setTimeout(() => {
78
44
  if (this.hasPhoneNumberValidationErrors()) {
79
45
  event.preventDefault()
@@ -83,293 +49,61 @@ class PbFormValidation extends PbEnhancedElement {
83
49
  })
84
50
  }
85
51
 
86
- validateOnSubmit() {
87
- let foundInvalid = false
88
-
89
- this.formValidationFields.forEach((field) => {
90
- if (this.isTypeaheadField(field)) return
91
-
92
- const isPhoneNumberInput = field.closest('.pb_phone_number_input')
93
- if (isPhoneNumberInput) return
94
-
95
- const isTimePickerInput = field.closest('.pb_time_picker')
96
- if (isTimePickerInput) return
97
-
98
- field.setCustomValidity('')
99
- const ctx = this.getValidationContext(field)
100
-
101
- if (field.validity.valid) {
102
- this.clearError(field, ctx)
103
- return
104
- }
105
-
106
- const message = ctx.validationMessage
107
- if (message) field.setCustomValidity(message)
108
-
109
- foundInvalid = true
110
- this.showValidationMessage(field, ctx)
111
- })
112
-
113
- return foundInvalid
114
- }
115
-
116
- disconnect() {
117
- this.mutationObserver?.disconnect()
118
-
119
- this._pendingFieldsToBind?.clear()
120
- this._pendingFieldsToBind = null
121
- this._flushPendingFieldBinds = null
122
- this._typeaheadFieldHandlers = null
123
- }
124
-
125
- bindValidationListeners() {
126
- this.formValidationFields.forEach((field) => this.bindFieldValidationListeners(field))
127
- }
128
-
129
- bindFieldValidationListeners(field) {
130
- if (!field || !(field instanceof Element)) return
131
- if (this.boundFields?.has(field)) return
132
- if (this.isTypeaheadField(field)) {
133
- this.bindTypeaheadValidationListeners(field)
134
- return
135
- }
136
-
137
- const isPhoneNumberInput = field.closest('.pb_phone_number_input')
138
- if (isPhoneNumberInput) return
139
-
140
- const isTimePickerInput = field.closest('.pb_time_picker')
141
- if (isTimePickerInput) return
142
-
143
- let handlers = this._fieldHandlers?.get(field)
144
- if (!handlers) {
145
- const debouncedHandler = debounce((event) => {
146
- this.validateFormField(event)
147
- }, 250)
148
-
149
- const immediateHandler = (event) => {
150
- this.validateFormField(event)
151
- }
152
-
153
- handlers = { debouncedHandler, immediateHandler }
154
- this._fieldHandlers?.set(field, handlers)
155
- }
156
-
157
- FIELD_EVENTS.forEach((eventName) => {
158
- if (eventName === 'invalid') {
159
- field.addEventListener(eventName, handlers.immediateHandler, true)
160
- } else if (eventName === 'change' && field.tagName === 'SELECT') {
161
- field.addEventListener(eventName, handlers.immediateHandler, false)
162
- } else {
163
- field.addEventListener(eventName, handlers.debouncedHandler, false)
164
- }
165
- })
166
-
167
- this.boundFields?.add(field)
168
- }
169
-
170
- bindTypeaheadValidationListeners(field) {
171
- if (!field || !(field instanceof HTMLInputElement || field instanceof HTMLTextAreaElement || field instanceof HTMLSelectElement)) return
172
- if (this.boundFields?.has(field)) return
173
-
174
- let handlers = this._typeaheadFieldHandlers?.get(field)
175
- if (!handlers) {
176
- const applyCustomMessage = () => {
177
- const kitElement = this.getKitElement(field)
178
- const ctx = this.getValidationContext(field)
179
- const message = this.getValidationMessage(field, kitElement)
180
-
181
- field.setCustomValidity('')
182
- if (!message) {
183
- if (!this.isReactTypeaheadField(field)) this.showValidationMessage(field, ctx)
184
- return
185
- }
186
-
187
- if (field.validity.valueMissing || !field.validity.valid) {
188
- field.setCustomValidity(message)
189
- }
190
-
191
- if (!this.isReactTypeaheadField(field)) {
192
- this.showValidationMessage(field, ctx)
193
- }
194
- }
195
-
196
- const clearCustomMessage = () => {
197
- field.setCustomValidity('')
198
- if (!this.isReactTypeaheadField(field)) {
199
- this.clearError(field)
200
- }
201
- }
202
-
203
- handlers = { applyCustomMessage, clearCustomMessage }
204
- this._typeaheadFieldHandlers?.set(field, handlers)
205
- }
206
-
207
- field.addEventListener('invalid', handlers.applyCustomMessage, true)
208
- field.addEventListener('input', handlers.clearCustomMessage, false)
209
- field.addEventListener('change', handlers.clearCustomMessage, false)
210
-
211
- this.boundFields?.add(field)
212
- }
213
-
214
52
  validateFormField(event) {
215
53
  event.preventDefault()
216
54
  const { target } = event
217
- if (this.isTypeaheadField(target)) return
218
-
219
- // Suppress the native browser tooltip; PB shows its own inline error instead.
220
-
221
- const ctx = this.getValidationContext(target)
222
-
223
55
  target.setCustomValidity('')
224
-
225
- const isValid = target.validity.valid
56
+ const isValid = event.target.validity.valid
226
57
 
227
58
  if (isValid) {
228
- this.clearError(target, ctx)
59
+ this.clearError(target)
229
60
  } else {
230
- const message = ctx.validationMessage
231
- if (message) target.setCustomValidity(message)
232
- this.showValidationMessage(target, ctx)
61
+ this.showValidationMessage(target)
233
62
  }
234
63
  }
235
64
 
236
- getValidationContext(target) {
237
- const parentElement = target?.parentElement || null
238
- const kitElement = this.getKitElement(target)
239
- const controlWrapper = this.getControlWrapper(target, kitElement)
240
- const errorParent = this.getErrorParent(target, kitElement, parentElement)
241
- const validationMessage = this.getValidationMessage(target, kitElement)
65
+ showValidationMessage(target) {
66
+ const { parentElement } = target
67
+ const kitElement = parentElement.closest(KIT_SELECTOR)
242
68
 
243
- return {
244
- parentElement,
245
- kitElement,
246
- controlWrapper,
247
- errorParent,
248
- validationMessage,
249
- isPhoneNumberInput: !!kitElement?.classList?.contains('pb_phone_number_input'),
250
- isTimePickerInput: !!kitElement?.classList?.contains('pb_time_picker'),
251
- }
252
- }
69
+ // FIX: Add null check for kitElement
70
+ if (!kitElement) return
253
71
 
254
- showValidationMessage(target, ctx = this.getValidationContext(target)) {
255
- const { kitElement, controlWrapper, errorParent, validationMessage, isPhoneNumberInput, isTimePickerInput } = ctx
72
+ // Check if this is a phone number input
73
+ const isPhoneNumberInput = kitElement.classList.contains('pb_phone_number_input')
74
+
75
+ // Check if this is a TimePicker input
76
+ const isTimePickerInput = kitElement.classList.contains('pb_time_picker')
256
77
 
257
78
  // ensure clean error message state
258
- this.clearError(target, ctx)
259
- if (kitElement) kitElement.classList.add('error')
260
-
261
- if (controlWrapper) controlWrapper.classList.add('error')
79
+ this.clearError(target)
80
+ kitElement.classList.add('error')
262
81
 
82
+ // Only add error message if it's NOT a phone number input or TimePicker input
263
83
  if (!isPhoneNumberInput && !isTimePickerInput) {
264
- const message = validationMessage || target.validationMessage
84
+ // set the error message element
85
+ const errorMessageContainer = this.errorMessageContainer
265
86
 
266
- let errorMessageElement = errorParent.querySelector(`[${FORM_VALIDATION_MESSAGE_ATTR}="true"]`)
267
- if (!errorMessageElement) {
268
- const existingEmpty = Array.from(errorParent.querySelectorAll(ERROR_MESSAGE_SELECTOR)).find((el) => {
269
- return (el.textContent || '').trim() === ''
270
- })
271
- if (existingEmpty) {
272
- errorMessageElement = existingEmpty
273
- errorMessageElement.setAttribute(FORM_VALIDATION_MESSAGE_ATTR, 'true')
274
- errorMessageElement.setAttribute(FORM_VALIDATION_MESSAGE_REUSED_ATTR, 'true')
275
- }
276
- }
87
+ if (target.dataset.message) target.setCustomValidity(target.dataset.message)
277
88
 
278
- if (!errorMessageElement) {
279
- errorMessageElement = this.errorMessageContainer
280
- errorParent.appendChild(errorMessageElement)
281
- }
89
+ errorMessageContainer.innerHTML = target.validationMessage
282
90
 
283
- errorMessageElement.textContent = message
284
- // If we previously hid a reused error element, unhide it now.
285
- errorMessageElement.style.display = ""
91
+ // add the error message element to the dom tree
92
+ parentElement.appendChild(errorMessageContainer)
286
93
  }
287
94
  }
288
95
 
289
- clearError(target, ctx = this.getValidationContext(target)) {
290
- const { kitElement, controlWrapper, errorParent } = ctx
96
+ clearError(target) {
97
+ const { parentElement } = target
98
+ const kitElement = parentElement.closest(KIT_SELECTOR)
99
+ // Remove error class from kit element
291
100
  if (kitElement) kitElement.classList.remove('error')
292
- if (controlWrapper) controlWrapper.classList.remove('error')
293
- const messageEl = errorParent.querySelector(`[${FORM_VALIDATION_MESSAGE_ATTR}="true"]`)
294
- if (messageEl) {
295
- const reused = messageEl.getAttribute(FORM_VALIDATION_MESSAGE_REUSED_ATTR) === 'true'
296
- if (reused) {
297
- messageEl.textContent = ''
298
- messageEl.style.display = "none"
299
- messageEl.removeAttribute(FORM_VALIDATION_MESSAGE_ATTR)
300
- messageEl.removeAttribute(FORM_VALIDATION_MESSAGE_REUSED_ATTR)
301
- } else {
302
- messageEl.remove()
303
- }
304
- }
305
- }
306
-
307
- getErrorParent(target, kitElement, parentElement) {
308
- const candidate =
309
- target.closest('.text_input_wrapper') ||
310
- target.closest('.pb_select_kit_wrapper') ||
311
- target.closest('.dropdown_wrapper') ||
312
- target.closest('.input_wrapper') ||
313
- target.closest('.pb_typeahead_wrapper') ||
314
- kitElement ||
315
- parentElement
316
-
317
- if (kitElement && candidate && candidate !== kitElement && !kitElement.contains(candidate)) {
318
- return kitElement
319
- }
320
-
321
- return candidate
322
- }
323
-
324
- getControlWrapper(target, kitElement) {
325
- return (
326
- target.closest('.dropdown_wrapper') ||
327
- target.closest('.pb_select_kit_wrapper') ||
328
- target.closest('.text_input_wrapper') ||
329
- kitElement?.querySelector?.('.dropdown_wrapper') ||
330
- kitElement?.querySelector?.('.pb_select_kit_wrapper') ||
331
- null
332
- )
333
- }
334
-
335
- isReactTypeaheadField(el) {
336
- return !!(
337
- el?.closest?.('[data-pb-react-component="Typeahead"]') ||
338
- el?.closest?.('.pb_typeahead_kit.react-select')
339
- )
340
- }
341
-
342
- isTypeaheadField(el) {
343
- return !!el?.closest?.('.pb_typeahead_kit')
344
- }
345
-
346
- getKitElement(target) {
347
- return (
348
- target.closest(KIT_SELECTOR) ||
349
- target.parentElement?.closest(KIT_SELECTOR) ||
350
- // Some kits don't expose a *_kit class but do expose data hooks.
351
- target.closest('[data-pb-select]') ||
352
- target.closest('[data-pb-date-picker]') ||
353
- target.closest('[data-pb-typeahead-kit]') ||
354
- null
355
- )
356
- }
357
-
358
- getValidationMessage(target, kitElement) {
359
- const fromTarget = target.dataset?.message || target.dataset?.validationMessage
360
-
361
- const wrapperWithMessage =
362
- target.closest?.('[data-validation-message]') ||
363
- target.closest?.('[data-pb-select]') ||
364
- target.closest?.('.dropdown_wrapper') ||
365
- target.closest?.('.pb_select_kit_wrapper')
366
- const fromWrapper = wrapperWithMessage?.dataset?.validationMessage
367
-
368
- const fromKit = kitElement?.dataset?.validationMessage
369
-
370
- return fromTarget || fromWrapper || fromKit || ''
101
+ // Remove error message from parent element
102
+ const errorMessageContainer = parentElement.querySelector(ERROR_MESSAGE_SELECTOR)
103
+ if (errorMessageContainer) errorMessageContainer.remove()
371
104
  }
372
105
 
106
+ // Check if there are phone number input errors
373
107
  hasPhoneNumberValidationErrors() {
374
108
  const phoneNumberErrors = this.element.querySelectorAll(PHONE_NUMBER_VALIDATION_ERROR_SELECTOR)
375
109
  return phoneNumberErrors.length > 0
@@ -379,25 +113,12 @@ class PbFormValidation extends PbEnhancedElement {
379
113
  const errorContainer = document.createElement('div')
380
114
  const kitClassName = ERROR_MESSAGE_SELECTOR.replace(/\./, '')
381
115
  errorContainer.classList.add(kitClassName)
382
- errorContainer.setAttribute(FORM_VALIDATION_MESSAGE_ATTR, 'true')
383
- errorContainer.setAttribute('role', 'alert')
384
- errorContainer.setAttribute('aria-live', 'polite')
385
116
  return errorContainer
386
117
  }
387
118
  get formValidationFields() {
388
- return this.element.querySelectorAll(REQUIRED_FIELDS_SELECTOR)
119
+ return this._formValidationFields =
120
+ this._formValidationFields || this.element.querySelectorAll(REQUIRED_FIELDS_SELECTOR)
389
121
  }
390
122
  }
391
123
 
392
- window.PbFormValidation = PbFormValidation
393
-
394
- const __pbStartFormValidation = () => {
395
- if (!window.PbFormValidation || typeof window.PbFormValidation.start !== 'function') return
396
- if (window.__pbFormValidationStarted) return
397
- window.__pbFormValidationStarted = true
398
- window.PbFormValidation.start()
399
- }
400
-
401
- document.addEventListener('DOMContentLoaded', __pbStartFormValidation)
402
- document.addEventListener('turbo:load', __pbStartFormValidation)
403
- document.addEventListener('turbo:render', __pbStartFormValidation)
124
+ window.PbFormValidation = PbFormValidation
@@ -3,6 +3,7 @@ import classnames from 'classnames'
3
3
  import { buildAriaProps, buildDataProps, buildHtmlProps } from '../utilities/props'
4
4
  import { GlobalProps, globalProps } from '../utilities/globalProps'
5
5
  import { isValidEmoji } from '../utilities/validEmojiChecker'
6
+ import { warnFontAwesomeFallback } from '../utilities/iconFallbackWarning'
6
7
 
7
8
  export type IconSizes = "lg"
8
9
  | "xs"
@@ -164,6 +165,9 @@ const Icon = (props: IconProps) => {
164
165
  iconElement = <PowerIcon /> as ReactSVGElement
165
166
  } else {
166
167
  faClasses[`fa-${icon}`] = icon as string
168
+ if (icon && window.PB_ICONS && Object.keys(window.PB_ICONS).length > 0) {
169
+ warnFontAwesomeFallback(icon as string)
170
+ }
167
171
  }
168
172
  }
169
173
 
@@ -5,6 +5,7 @@
5
5
  <%= object.icon.html_safe %>
6
6
  </span>
7
7
  <% else %>
8
+ <%= object.warn_font_awesome_fallback %>
8
9
  <%= pb_content_tag(:i) do %>
9
10
  <% end %>
10
11
  <%= content_tag(:span, nil,
@@ -48,6 +48,29 @@ module Playbook
48
48
  emoji_regex.match?(icon)
49
49
  end
50
50
 
51
+ def warn_font_awesome_fallback
52
+ return "".html_safe if Rails.env.test? || Rails.env.production?
53
+ return "".html_safe if icon.nil? || icon.to_s.empty?
54
+
55
+ escaped_icon = icon.to_s.gsub("'", "\\\\'")
56
+ message = "[Playbook] Icon '#{escaped_icon}' not found in Playbook icons. Falling back to Font Awesome. Font Awesome will be removed from Nitro in the future. Please use Playbook Icons instead. See https://playbook.powerapp.cloud/playbook_icons for available icons."
57
+
58
+ script = "<script type=\"text/javascript\">\n"
59
+ script += "(function() {\n"
60
+ script += " var hostname = window.location.hostname;\n"
61
+ script += " var isLocalDev = hostname === 'localhost' || hostname === '127.0.0.1' || hostname.endsWith('.local') || hostname.includes('local.') || !hostname;\n"
62
+ script += " if (!isLocalDev) return;\n"
63
+ script += " if (!window.__PB_WARNED_ICONS__) window.__PB_WARNED_ICONS__ = new Set();\n"
64
+ script += " if (!window.__PB_WARNED_ICONS__.has('#{escaped_icon}')) {\n"
65
+ script += " window.__PB_WARNED_ICONS__.add('#{escaped_icon}');\n"
66
+ script += " console.warn('#{message}');\n"
67
+ script += " }\n"
68
+ script += "})();\n"
69
+ script += "</script>"
70
+
71
+ script.html_safe
72
+ end
73
+
51
74
  def classname
52
75
  generate_classname(
53
76
  "pb_icon_kit",
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Tracks which icons have already logged fallback warnings in this session
3
+ * to ensure we only log once per page load per icon
4
+ */
5
+ const warnedIcons = new Set<string>()
6
+
7
+ /**
8
+ * Logs a warning when a Playbook icon falls back to Font Awesome
9
+ * - Only logs once per icon per page load (prevents spam on re-renders)
10
+ * - Only logs in local development (not in production, QA, or test environments)
11
+ *
12
+ * @param iconName - The name of the icon that wasn't found in Playbook icons
13
+ *
14
+ * @example
15
+ * if (!PowerIcon) {
16
+ * warnFontAwesomeFallback('my-icon')
17
+ * }
18
+ */
19
+ export const warnFontAwesomeFallback = (iconName: string): void => {
20
+ if (typeof process !== 'undefined' && process.env?.NODE_ENV === 'test') {
21
+ return
22
+ }
23
+
24
+ if (typeof window !== 'undefined') {
25
+ const hostname = window.location?.hostname
26
+ const isLocalDev = hostname === 'localhost' ||
27
+ hostname === '127.0.0.1' ||
28
+ hostname?.endsWith('.local') ||
29
+ hostname?.includes('local.') ||
30
+ !hostname
31
+
32
+ if (!isLocalDev) {
33
+ return
34
+ }
35
+ }
36
+
37
+ if (warnedIcons.has(iconName)) {
38
+ return
39
+ }
40
+
41
+ warnedIcons.add(iconName)
42
+
43
+ console.warn(
44
+ `[Playbook] Icon '${iconName}' not found in Playbook icons. ` +
45
+ `Falling back to Font Awesome. ` +
46
+ `Font Awesome will be removed from Nitro in the future. Please use Playbook Icons instead. See https://playbook.powerapp.cloud/playbook_icons for available icons.`
47
+ )
48
+ }
49
+
50
+ /**
51
+ * Resets the warned icons tracker (useful for testing)
52
+ * @internal
53
+ */
54
+ export const resetIconFallbackWarnings = (): void => {
55
+ warnedIcons.clear()
56
+ }