@radioactive-labs/plutonium 0.2.1 → 0.3.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 (41) hide show
  1. package/package.json +6 -2
  2. package/src/css/plutonium.css +3 -0
  3. package/src/dist/css/plutonium.css +9 -1
  4. package/src/dist/js/plutonium.js +23273 -9599
  5. package/src/dist/js/plutonium.js.map +4 -4
  6. package/src/dist/js/plutonium.min.js +159 -49
  7. package/src/dist/js/plutonium.min.js.map +4 -4
  8. package/src/js/controllers/attachment_input_controller.js +241 -0
  9. package/src/js/controllers/attachment_preview_container_controller.js +15 -0
  10. package/src/js/controllers/attachment_preview_controller.js +63 -0
  11. package/src/js/controllers/color_mode_controller.js +0 -1
  12. package/src/js/controllers/easymde_controller.js +0 -1
  13. package/src/js/controllers/flatpickr_controller.js +0 -1
  14. package/src/js/controllers/form_controller.js +0 -1
  15. package/src/js/controllers/frame_navigator_controller.js +16 -12
  16. package/src/js/controllers/intl_tel_input_controller.js +0 -1
  17. package/src/js/controllers/register_controllers.js +12 -30
  18. package/src/js/controllers/resource_collapse_controller.js +0 -1
  19. package/src/js/controllers/resource_dismiss_controller.js +0 -1
  20. package/src/js/controllers/resource_drop_down_controller.js +161 -9
  21. package/src/js/controllers/{header_controller.js → resource_header_controller.js} +1 -0
  22. package/src/js/controllers/resource_tab_list_controller.js +64 -0
  23. package/src/js/controllers/select_navigator.js +16 -0
  24. package/src/js/controllers/slim_select_controller.js +0 -1
  25. package/src/js/support/dom_element.js +78 -0
  26. package/src/js/support/mime_icon.js +127 -0
  27. package/src/js/controllers/has_many_panel_controller.js +0 -8
  28. package/src/js/controllers/interactive_action_form_controller.js +0 -13
  29. package/src/js/controllers/nav_grid_menu_controller.js +0 -8
  30. package/src/js/controllers/nav_grid_menu_item_controller.js +0 -8
  31. package/src/js/controllers/nav_user_controller.js +0 -8
  32. package/src/js/controllers/nav_user_link_controller.js +0 -8
  33. package/src/js/controllers/nav_user_section_controller.js +0 -8
  34. package/src/js/controllers/resource_layout_controller.js +0 -8
  35. package/src/js/controllers/sidebar_controller.js +0 -8
  36. package/src/js/controllers/sidebar_menu_controller.js +0 -8
  37. package/src/js/controllers/sidebar_menu_item_controller.js +0 -8
  38. package/src/js/controllers/table_controller.js +0 -8
  39. package/src/js/controllers/table_search_input_controller.js +0 -8
  40. package/src/js/controllers/table_toolbar_controller.js +0 -8
  41. package/src/js/controllers/toolbar_controller.js +0 -8
@@ -0,0 +1,241 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ import Uppy from '@uppy/core'
4
+ import Dashboard from '@uppy/dashboard'
5
+ import ImageEditor from '@uppy/image-editor'
6
+ import XHRUpload from '@uppy/xhr-upload'
7
+ import DomElement from "../support/dom_element"
8
+
9
+ // Connects to data-controller="attachment-input"
10
+ export default class extends Controller {
11
+ static values = {
12
+ identifier: String,
13
+
14
+ maxFileSize: { type: Number, default: null },
15
+ minFileSize: { type: Number, default: null },
16
+ maxTotalSize: { type: Number, default: null },
17
+ maxFileNum: { type: Number, default: null },
18
+ minFileNum: { type: Number, default: null },
19
+ allowedFileTypes: { type: Array, default: null },
20
+ requiredMetaFields: { type: Array, default: [] },
21
+ }
22
+
23
+ static outlets = ["attachment-preview", "attachment-preview-container"]
24
+
25
+ //======= Lifecycle
26
+
27
+ connect() {
28
+ // initialize
29
+ this.uploadedFiles = []
30
+
31
+ // hide the input
32
+ this.element.style["display"] = "none"
33
+
34
+ // setup uppy
35
+ this.configureUppy()
36
+ // build trigger
37
+ this.#buildTriggers()
38
+ // init state
39
+ this.#onAttachmentsChanged()
40
+ }
41
+
42
+ disconnect() {
43
+ this.uppy = null
44
+ }
45
+
46
+ attachmentPreviewOutletConnected(outlet, element) {
47
+ this.#onAttachmentsChanged()
48
+ }
49
+
50
+ attachmentPreviewOutletDisconnected(outlet, element) {
51
+ this.#onAttachmentsChanged()
52
+ }
53
+
54
+ //======= Config
55
+
56
+ configureUppy() {
57
+ this.uppy = new Uppy({
58
+ restrictions: {
59
+ maxFileSize: this.maxFileSizeValue,
60
+ minFileSize: this.minFileSizeValue,
61
+ maxTotalFileSize: this.maxTotalSizeValue,
62
+ maxNumberOfFiles: this.maxFileNumValue,
63
+ minNumberOfFiles: this.minFileNumValue,
64
+ allowedFileTypes: this.allowedFileTypesValue,
65
+ requiredMetaFields: this.requiredMetaFieldsValue,
66
+ }
67
+ })
68
+ .use(Dashboard, { inline: false, closeAfterFinish: true })
69
+ .use(ImageEditor, { target: Dashboard })
70
+
71
+ this.#configureUploader()
72
+ this.#configureEventHandlers()
73
+ }
74
+
75
+ #configureUploader() {
76
+ this.uppy
77
+ .use(XHRUpload, {
78
+ endpoint: '/upload', // path to the upload endpoint
79
+ })
80
+ }
81
+
82
+ #configureEventHandlers() {
83
+ this.uppy.on('upload-success', this.#onUploadSuccess.bind(this))
84
+ }
85
+
86
+ //======= Events
87
+
88
+ #onModalTriggered() {
89
+ // ensure correct color mode is set
90
+ let theme = document.documentElement.getAttribute('data-bs-theme') || 'auto'
91
+ this.#dashboard.setOptions({ theme: theme })
92
+
93
+ // clear all successfully uploaded files
94
+ let file = null;
95
+ while (file = this.uploadedFiles.pop()) this.uppy.removeFile(file.id)
96
+
97
+ // open modal
98
+ this.#dashboard.openModal()
99
+ }
100
+
101
+ #onUploadSuccess(file, response) {
102
+ this.uploadedFiles.push(file)
103
+
104
+ // remove current preview
105
+ if (!this.multiple) this.attachmentPreviewOutlets.forEach(a => a.remove())
106
+
107
+ // retrieve uploaded file data
108
+ const uploadedFileData = response.body["data"]
109
+ const uploadedFileUrl = response.body["url"]
110
+
111
+ // set hidden field value to the uploaded file data so that it's submitted
112
+ // with the form as the attachment
113
+ this.attachmentPreviewContainerOutlet.element.appendChild(
114
+ this.#buildPreview(uploadedFileData, uploadedFileUrl)
115
+ )
116
+ }
117
+
118
+ #onAttachmentsChanged() {
119
+ if (!this.deleteAllTrigger) return
120
+
121
+ const len = this.attachmentPreviewOutlets.length
122
+ if (len > 1) {
123
+ this.deleteAllTrigger.style["display"] = 'initial'
124
+ this.deleteAllTrigger.textContent = `Delete ${this.attachmentPreviewOutlets.length}`
125
+ } else {
126
+ this.deleteAllTrigger.style["display"] = 'none'
127
+ }
128
+ }
129
+
130
+ //======= Builders
131
+
132
+ #buildTriggers() {
133
+ this.triggerContainer = document.createElement("div")
134
+ this.triggerContainer.className = "flex items-center gap-2" // Add flex container with alignment
135
+ this.element.insertAdjacentElement('afterend', this.triggerContainer)
136
+
137
+ this.#buildUploadTrigger()
138
+ // currently experiencing a weird issue where outlet disconnections are not triggering
139
+ // this.#buildDeleteAllTrigger()
140
+
141
+ if (this.uploadTrigger) this.triggerContainer.append(this.uploadTrigger)
142
+ if (this.deleteAllTrigger) this.triggerContainer.append(this.deleteAllTrigger)
143
+ }
144
+
145
+ #buildUploadTrigger() {
146
+ const triggerPrompt = this.multiple ? "Choose files" : "Choose file"
147
+ this.uploadTrigger = DomElement.fromTemplate(
148
+ `<button type="button" class="text-gray-900 bg-white border border-gray-300 focus:outline-none hover:bg-gray-100 focus:ring-4 focus:ring-gray-200 font-medium rounded-lg text-sm px-5 py-2.5 dark:bg-gray-800 dark:text-white dark:border-gray-600 dark:hover:bg-gray-700 dark:hover:border-gray-600 dark:focus:ring-gray-700 inline-flex items-center">
149
+ <svg class="w-4 h-4 mr-2" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 16">
150
+ <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 13h3a3 3 0 0 0 0-6h-.025A5.56 5.56 0 0 0 16 6.5 5.5 5.5 0 0 0 5.207 5.021C5.137 5.017 5.071 5 5 5a4 4 0 0 0 0 8h2.167M10 15V6m0 0L8 8m2-2 2 2"/>
151
+ </svg>
152
+ ${triggerPrompt}
153
+ </button>`,
154
+ false
155
+ )
156
+ this.uploadTrigger.addEventListener('click', this.#onModalTriggered.bind(this))
157
+ }
158
+
159
+ #buildDeleteAllTrigger() {
160
+ this.deleteAllTrigger = DomElement.fromTemplate(
161
+ `<button type="button" class="text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 dark:bg-red-600 dark:hover:bg-red-700 focus:outline-none dark:focus:ring-red-800 inline-flex items-center">
162
+ Delete ${this.attachmentPreviewOutlets.length}
163
+ </button>`,
164
+ false
165
+ )
166
+ this.deleteAllTrigger.addEventListener('click', () => {
167
+ if (confirm('Are you sure?')) this.attachmentPreviewContainerOutlet.clear()
168
+ })
169
+ }
170
+
171
+ #buildPreview(data, url) {
172
+ const filename = data.metadata.filename
173
+ const extension = filename.substring(filename.lastIndexOf('.') + 1, filename.length) || filename
174
+ const multiple = this.multiple ? 'multiple' : ''
175
+ const mimeType = data.metadata.mime_type
176
+
177
+ // List of commonly representable mime types
178
+ const representableMimeTypes = [
179
+ 'image/jpeg',
180
+ 'image/png',
181
+ 'image/gif',
182
+ 'image/webp',
183
+ 'image/svg+xml',
184
+ 'image/bmp',
185
+ 'image/tiff'
186
+ ]
187
+
188
+ const isRepresentable = representableMimeTypes.includes(mimeType.toLowerCase())
189
+
190
+ // build preview element
191
+ const previewElem = DomElement.fromTemplate(
192
+ this.#buildPreviewTemplate(filename, extension, mimeType, url, isRepresentable)
193
+ )
194
+
195
+ // build input element
196
+ const inputElem = DomElement.fromTemplate(
197
+ `<input name="${this.element.name}" ${multiple} type="hidden" autocomplete="off" hidden />`
198
+ )
199
+ inputElem.value = JSON.stringify(data)
200
+
201
+ // insert input element into preview
202
+ previewElem.appendChild(inputElem)
203
+
204
+ return previewElem
205
+ }
206
+
207
+ #buildPreviewTemplate(filename, extension, mimeType, url, isRepresentable) {
208
+ return `
209
+ <div class="${this.identifierValue} attachment-preview group relative bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-sm hover:shadow-md transition-all duration-300"
210
+ data-controller="attachment-preview"
211
+ data-attachment-preview-mime-type-value="${mimeType}"
212
+ data-attachment-preview-thumbnail-url-value="${isRepresentable ? url : ''}"
213
+ data-attachment-preview-target="thumbnail"
214
+ title="${filename}">
215
+ <a class="block aspect-square overflow-hidden rounded-t-lg"
216
+ data-attachment-preview-target="thumbnailLink">
217
+ ${isRepresentable
218
+ ? `<img src="${url}" class="w-full h-full object-cover" />`
219
+ : `<div class="w-full h-full flex items-center justify-center bg-gray-50 dark:bg-gray-900 text-gray-500 dark:text-gray-400 font-mono">.${extension}</div>`
220
+ }
221
+ </a>
222
+ <div class="px-2 py-1.5 text-sm text-gray-700 dark:text-gray-300 border-t border-gray-200 dark:border-gray-700 truncate text-center bg-white dark:bg-gray-800"
223
+ title="${filename}">
224
+ ${filename}
225
+ </div>
226
+ <button type="button"
227
+ class="w-full py-2 px-4 text-sm text-red-600 dark:text-red-400 bg-white dark:bg-gray-800 hover:bg-red-50 dark:hover:bg-red-900/50 rounded-b-lg transition-colors duration-200 flex items-center justify-center gap-2 border-t border-gray-200 dark:border-gray-700"
228
+ data-action="click->attachment-preview#remove">
229
+ <span class="bi bi-trash"></span>
230
+ Delete
231
+ </button>
232
+ </div>
233
+ `
234
+ }
235
+
236
+ //======= Getters
237
+
238
+ get #dashboard() { return this.uppy.getPlugin('Dashboard') }
239
+
240
+ get multiple() { return this.maxFileNumValue != 1 }
241
+ }
@@ -0,0 +1,15 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ // Connects to data-controller="attachment-preview-container"
4
+ export default class extends Controller {
5
+ connect() {
6
+ }
7
+
8
+ append(element) {
9
+ this.element.appendChild(element)
10
+ }
11
+
12
+ clear() {
13
+ this.element.innerHTML = null
14
+ }
15
+ }
@@ -0,0 +1,63 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+ import getIconByMime from "../support/mime_icon"
3
+ import DomElement from "../support/dom_element"
4
+
5
+ // Connects to data-controller="attachment-preview"
6
+ export default class extends Controller {
7
+ static targets = ["thumbnail", "thumbnailLink"]
8
+ static values = {
9
+ mimeType: String,
10
+ thumbnailUrl: String,
11
+ }
12
+
13
+ connect() {
14
+ if (!this.hasThumbnailTarget) return;
15
+
16
+ if (this.thumbnailUrlValue) {
17
+ this.useThumbnailPreview()
18
+ } else {
19
+ this.useMimeIconPreview()
20
+ }
21
+ }
22
+
23
+ remove() {
24
+ this.element.remove()
25
+ }
26
+
27
+ useThumbnailPreview() {
28
+ const thumbnail = DomElement.fromTemplate(`
29
+ <img src="${this.thumbnailUrlValue}" class="w-full h-full object-cover" />
30
+ `)
31
+
32
+ this.thumbnailLinkTarget.innerHTML = null
33
+ this.thumbnailLinkTarget.appendChild(thumbnail)
34
+ }
35
+
36
+ useMimeIconPreview() {
37
+ const mime = getIconByMime(this.mimeTypeValue)
38
+
39
+ // Configure the icon
40
+ mime.icon.classList.add(
41
+ 'w-3/5', // 60% width
42
+ 'h-4/5', // 80% height
43
+ 'rounded-lg',
44
+ 'shadow-lg',
45
+ 'bg-white',
46
+ 'p-2' // Add some padding inside the icon container
47
+ )
48
+
49
+ // Center the icon in the container
50
+ this.thumbnailLinkTarget.classList.add(
51
+ 'flex',
52
+ 'items-center',
53
+ 'justify-center'
54
+ )
55
+
56
+ // Set the background color
57
+ this.thumbnailLinkTarget.style.backgroundColor = mime.color
58
+
59
+ // Clear and append the icon
60
+ this.thumbnailLinkTarget.innerHTML = null
61
+ this.thumbnailLinkTarget.appendChild(mime.icon)
62
+ }
63
+ }
@@ -6,7 +6,6 @@ export default class extends Controller {
6
6
  // static targets = ["trigger", "menu"]
7
7
 
8
8
  connect() {
9
- console.log(`color-mode connected: ${this.element}`)
10
9
  this.updateColorMode()
11
10
  }
12
11
 
@@ -5,7 +5,6 @@ import { marked } from 'marked';
5
5
  // Connects to data-controller="easymde"
6
6
  export default class extends Controller {
7
7
  connect() {
8
- console.log(`easymde connected: ${this.element}`)
9
8
  this.easyMDE = new EasyMDE(this.#buildOptions())
10
9
  this.element.setAttribute("data-action", "turbo:morph-element->easymde#reconnect")
11
10
  }
@@ -3,7 +3,6 @@ import { Controller } from "@hotwired/stimulus"
3
3
  // Connects to data-controller="flatpickr"
4
4
  export default class extends Controller {
5
5
  connect() {
6
- console.log(`flatpickr connected: ${this.element}`)
7
6
  this.picker = new flatpickr(this.element, this.#buildOptions())
8
7
  this.element.setAttribute("data-action", "turbo:morph-element->flatpickr#reconnect")
9
8
  }
@@ -4,7 +4,6 @@ import debounce from "lodash.debounce";
4
4
  // Connects to data-controller="form"
5
5
  export default class extends Controller {
6
6
  connect() {
7
- console.log(`form connected: ${this.element}`)
8
7
  }
9
8
 
10
9
  submit() {
@@ -2,10 +2,9 @@ import { Controller } from "@hotwired/stimulus"
2
2
 
3
3
  // Connects to data-controller="frame-navigator"
4
4
  export default class extends Controller {
5
- static targets = ["frame", "refreshButton", "backButton", "homeButton"];
5
+ static targets = ["frame", "refreshButton", "backButton", "homeButton", "maximizeLink"];
6
6
 
7
7
  connect() {
8
- console.log(`frame-navigator connected: ${this.element}`)
9
8
  this.#loadingStarted()
10
9
 
11
10
  this.srcHistory = []
@@ -61,16 +60,7 @@ export default class extends Controller {
61
60
  this.#loadingStopped()
62
61
 
63
62
  let src = event.target.src
64
- if (src == this.currentSrc) {
65
- // this must be a refresh
66
- // do nothing
67
- }
68
- else if (src == this.originalFrameSrc)
69
- this.srcHistory = [src]
70
- else
71
- this.srcHistory.push(src)
72
-
73
- this.#updateNavigationButtonsDisplay()
63
+ this.#notifySrcChanged(src)
74
64
  }
75
65
 
76
66
  refreshButtonClicked(event) {
@@ -94,6 +84,20 @@ export default class extends Controller {
94
84
 
95
85
  get currentSrc() { return this.srcHistory[this.srcHistory.length - 1] }
96
86
 
87
+ #notifySrcChanged(src) {
88
+ if (src == this.currentSrc) {
89
+ // this must be a refresh
90
+ // do nothing
91
+ }
92
+ else if (src == this.originalFrameSrc)
93
+ this.srcHistory = [src]
94
+ else
95
+ this.srcHistory.push(src)
96
+
97
+ this.#updateNavigationButtonsDisplay()
98
+ if (this.hasMaximizeLinkTarget) this.maximizeLinkTarget.href = src
99
+ }
100
+
97
101
  #loadingStarted() {
98
102
  if (this.hasRefreshButtonTarget) this.refreshButtonTarget.classList.add("motion-safe:animate-spin")
99
103
  this.frameTarget.classList.add("motion-safe:animate-pulse")
@@ -5,7 +5,6 @@ export default class extends Controller {
5
5
  static targets = ["input"]
6
6
 
7
7
  connect() {
8
- console.log(`intl-tel-input connected: ${this.element}`)
9
8
  }
10
9
 
11
10
  disconnect() {
@@ -1,20 +1,6 @@
1
1
  // Import controllers here
2
- import ResourceLayoutController from "./resource_layout_controller.js"
3
- import NavGridMenuItemController from "./nav_grid_menu_item_controller.js"
4
- import NavGridMenuController from "./nav_grid_menu_controller.js"
5
- import NavUserSectionController from "./nav_user_section_controller.js"
6
- import NavUserLinkController from "./nav_user_link_controller.js"
7
- import NavUserController from "./nav_user_controller.js"
8
- import HeaderController from "./header_controller.js"
9
- import SidebarMenuItemController from "./sidebar_menu_item_controller.js"
10
- import SidebarMenuController from "./sidebar_menu_controller.js"
11
- import SidebarController from "./sidebar_controller.js"
12
- import HasManyPanelController from "./has_many_panel_controller.js"
2
+ import ResourceHeaderController from "./resource_header_controller.js"
13
3
  import NestedResourceFormFieldsController from "./nested_resource_form_fields_controller.js"
14
- import ToolbarController from "./toolbar_controller.js"
15
- import TableSearchInputController from "./table_search_input_controller.js"
16
- import TableToolbarController from "./table_toolbar_controller.js"
17
- import TableController from "./table_controller.js"
18
4
  import FormController from "./form_controller.js"
19
5
  import ResourceDropDownController from "./resource_drop_down_controller.js"
20
6
  import ResourceCollapseController from "./resource_collapse_controller.js"
@@ -25,25 +11,16 @@ import EasyMDEController from "./easymde_controller.js"
25
11
  import SlimSelectController from "./slim_select_controller.js"
26
12
  import FlatpickrController from "./flatpickr_controller.js"
27
13
  import IntlTelInputController from "./intl_tel_input_controller.js"
14
+ import SelectNavigatorController from "./select_navigator.js"
15
+ import ResourceTabListController from "./resource_tab_list_controller.js"
16
+ import AttachmentInputController from "./attachment_input_controller.js"
17
+ import AttachmentPreviewController from "./attachment_preview_controller.js"
18
+ import AttachmentPreviewContainerController from "./attachment_preview_container_controller.js"
28
19
 
29
20
  export default function (application) {
30
21
  // Register controllers here
31
- application.register("resource-layout", ResourceLayoutController)
32
- application.register("nav-grid-menu-item", NavGridMenuItemController)
33
- application.register("nav-grid-menu", NavGridMenuController)
34
- application.register("nav-user-section", NavUserSectionController)
35
- application.register("nav-user-link", NavUserLinkController)
36
- application.register("nav-user", NavUserController)
37
- application.register("header", HeaderController)
38
- application.register("sidebar-menu-item", SidebarMenuItemController)
39
- application.register("sidebar-menu", SidebarMenuController)
40
- application.register("sidebar", SidebarController)
41
- application.register("has-many-panel", HasManyPanelController)
22
+ application.register("resource-header", ResourceHeaderController)
42
23
  application.register("nested-resource-form-fields", NestedResourceFormFieldsController)
43
- application.register("toolbar", ToolbarController)
44
- application.register("table-search-input", TableSearchInputController)
45
- application.register("table-toolbar", TableToolbarController)
46
- application.register("table", TableController)
47
24
  application.register("form", FormController)
48
25
  application.register("resource-drop-down", ResourceDropDownController)
49
26
  application.register("resource-collapse", ResourceCollapseController)
@@ -54,4 +31,9 @@ export default function (application) {
54
31
  application.register("slim-select", SlimSelectController)
55
32
  application.register("flatpickr", FlatpickrController)
56
33
  application.register("intl-tel-input", IntlTelInputController)
34
+ application.register("select-navigator", SelectNavigatorController)
35
+ application.register("resource-tab-list", ResourceTabListController)
36
+ application.register("attachment-input", AttachmentInputController)
37
+ application.register("attachment-preview", AttachmentPreviewController)
38
+ application.register("attachment-preview-container", AttachmentPreviewContainerController)
57
39
  }
@@ -5,7 +5,6 @@ export default class extends Controller {
5
5
  static targets = ["trigger", "menu"]
6
6
 
7
7
  connect() {
8
- console.log(`resource-collapse connected: ${this.element}`)
9
8
 
10
9
  // Default to false if the data attribute isn't set
11
10
  if (!this.element.hasAttribute('data-visible')) {
@@ -7,7 +7,6 @@ export default class extends Controller {
7
7
  }
8
8
 
9
9
  connect() {
10
- console.log(`resource-dismiss connected: ${this.element}`)
11
10
 
12
11
  if (this.hasAfterValue && this.afterValue > 0) {
13
12
  this.autoDismissTimeout = setTimeout(() => {