nanosplash 2.3.1 → 2.4.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.
@@ -1,20 +1,32 @@
1
1
  import {Destination} from "../types";
2
2
  import {Nanosplash} from "./Nanosplash";
3
3
  import {addClass, mk, move, setAttr} from "../utilities/dom";
4
- import {NanosplashRepository} from "../repositories/NanosplashRepository";
5
4
  import {IdentityInterface} from "../Interfaces/IdentityInterface";
5
+ import {NanosplashRepository} from "../repositories/NanosplashRepository";
6
6
 
7
+ /**
8
+ * SplashInstance
9
+ * @author Isak K. Hauge <isakhauge@gmail.com>
10
+ */
7
11
  export class SplashInstance implements IdentityInterface {
8
12
  private readonly id: string
9
13
  private imgSrc: string | undefined
10
14
  private readonly nsInstance: Nanosplash
15
+ private destinationNode: HTMLElement | undefined
11
16
  private readonly nsRootElement: HTMLDivElement = mk('div') as HTMLDivElement
12
- private readonly nsWrapperElement: HTMLDivElement = mk('div') as HTMLDivElement
17
+ private readonly nsTextElement: HTMLDivElement = mk('div') as HTMLDivElement
18
+ private readonly nsTextContainerElement: HTMLDivElement = mk('div') as HTMLDivElement
19
+ private readonly nsSpinnerElement: HTMLDivElement = NanosplashRepository.createNanosplashSpinnerElement()
13
20
  private readonly nsWindowElement: HTMLDivElement = mk('div') as HTMLDivElement
21
+ private readonly nsWrapperElement: HTMLDivElement = mk('div') as HTMLDivElement
14
22
  private readonly nsContentElement: HTMLDivElement = mk('div') as HTMLDivElement
15
23
  private readonly nsImageElement: HTMLImageElement = mk('img') as HTMLImageElement
16
- private readonly nsTextElement: HTMLDivElement = mk('div') as HTMLDivElement
17
24
 
25
+ /**
26
+ * @param {Nanosplash} ns Nanosplash instance
27
+ * @param {string} text Text
28
+ * @param {string|undefined} imgSrc Image source URL
29
+ */
18
30
  public constructor(ns: Nanosplash, text: string, imgSrc?: string) {
19
31
  this.id = Math.random().toString(36).substring(2)
20
32
  this.nsInstance = ns
@@ -24,27 +36,48 @@ export class SplashInstance implements IdentityInterface {
24
36
  this.nsImageElement.src = this.imgSrc ?? ''
25
37
  this.nsImageElement.alt = Nanosplash.APP_NAME
26
38
 
39
+ this.nsRootElement.style.fontSize = ns.fontSize
40
+ this.nsSpinnerElement.style.display = ns.showSpinner ? 'flex' : 'none'
41
+
27
42
  this.assembleNSComponent()
28
43
  this.setImgSrc(imgSrc)
29
44
  }
30
45
 
46
+ /**
47
+ * # Assign CSS Classes
48
+ * Assigns CSS class names to all elements connected to this splash instance.
49
+ * @private
50
+ */
31
51
  private assignCSSClasses(): void
32
52
  {
33
53
  addClass(this.nsContentElement, 'ns-container')
34
54
  addClass(this.nsWrapperElement, 'ns-blur')
35
55
  addClass(this.nsImageElement, 'ns-img')
36
56
  addClass(this.nsTextElement, 'ns-text')
57
+ addClass(this.nsTextContainerElement, 'ns-text-container')
58
+ addClass(this.nsSpinnerElement, 'ns-spinner')
37
59
  addClass(this.nsWindowElement, 'ns', 'ns-window')
38
60
  addClass(this.nsRootElement, 'ns-wrapper')
39
61
  }
40
62
 
63
+ /**
64
+ * # Assemble Element Structure
65
+ * Assembles all elements connected to this splash instance in a fixed structure.
66
+ * @private
67
+ */
41
68
  private assembleElementStructure(): void
42
69
  {
43
- this.nsContentElement.append(this.nsImageElement, this.nsTextElement)
70
+ this.nsTextContainerElement.append(this.nsTextElement, this.nsSpinnerElement)
71
+ this.nsContentElement.append(this.nsImageElement, this.nsTextContainerElement)
44
72
  this.nsWindowElement.append(this.nsContentElement)
45
73
  this.nsRootElement.append(this.nsWrapperElement, this.nsWindowElement)
46
74
  }
47
75
 
76
+ /**
77
+ * # Assemble Nanosplash Component
78
+ * Assembles all elements into one component that represents the Nanosplash component.
79
+ * @private
80
+ */
48
81
  private assembleNSComponent(): void {
49
82
  this.nsRootElement.id = this.getId()
50
83
  setAttr(this.nsRootElement, 'data-ctx', 'nanosplash')
@@ -52,26 +85,48 @@ export class SplashInstance implements IdentityInterface {
52
85
  this.assignCSSClasses()
53
86
  }
54
87
 
88
+ /**
89
+ * # Get ID
90
+ * @return string
91
+ */
55
92
  public getId(): string
56
93
  {
57
94
  return this.id
58
95
  }
59
96
 
97
+ /**
98
+ * Get Text
99
+ * @return string
100
+ */
60
101
  public getText(): string
61
102
  {
62
103
  return this.nsTextElement.innerText
63
104
  }
64
105
 
106
+ /**
107
+ * # Set Text
108
+ * @param {string} text Text
109
+ * @return SplashInstance
110
+ */
65
111
  public setText(text: string): SplashInstance {
66
112
  this.nsTextElement.innerText = text
67
113
  return this
68
114
  }
69
115
 
116
+ /**
117
+ * # Get Image Source
118
+ * @return string | undefined
119
+ */
70
120
  public getImgSrc(): string | undefined
71
121
  {
72
122
  return this.imgSrc
73
123
  }
74
124
 
125
+ /**
126
+ * # Set Image Source
127
+ * @param {string|undefined} src Source URL
128
+ * @return SplashInstance
129
+ */
75
130
  public setImgSrc(src?: string): SplashInstance
76
131
  {
77
132
  this.nsImageElement.src = src ?? ''
@@ -80,7 +135,21 @@ export class SplashInstance implements IdentityInterface {
80
135
  return this
81
136
  }
82
137
 
83
- private cleanup(): void
138
+ /**
139
+ * # Get Destination
140
+ * @return HTMLElement | undefined
141
+ */
142
+ public getDestination(): HTMLElement | undefined
143
+ {
144
+ return this.destinationNode
145
+ }
146
+
147
+ /**
148
+ * # Clean And Restore
149
+ * Cleans and restore the DOM structure.
150
+ * @private
151
+ */
152
+ private cleanAndRestore(): void
84
153
  {
85
154
  const currentParent = this.nsRootElement.parentElement
86
155
  if (currentParent) {
@@ -88,12 +157,24 @@ export class SplashInstance implements IdentityInterface {
88
157
  }
89
158
  }
90
159
 
160
+ /**
161
+ * # Reset Fullscreen Attributes
162
+ * Removes the fullscreen attributes and CSS class names.
163
+ * @private
164
+ */
91
165
  private resetFullscreenAttributes(): void
92
166
  {
93
167
  setAttr(this.nsRootElement, 'style', '')
94
168
  this.nsRootElement.classList.remove('ns-fs')
95
169
  }
96
170
 
171
+ /**
172
+ * # Move with Regular Strategy
173
+ * Moves the Nanosplash root element to the target node's position and essentially wraps the target node with the
174
+ * Nanosplash root element.
175
+ * @param {Node} targetNode
176
+ * @private
177
+ */
97
178
  private moveWithRegularStrategy(targetNode: Node): void
98
179
  {
99
180
  const targetParentNode = targetNode.parentNode
@@ -104,40 +185,93 @@ export class SplashInstance implements IdentityInterface {
104
185
  }
105
186
  }
106
187
 
188
+ /**
189
+ * # Move With Fullscreen Strategy
190
+ * Moves the Nanosplash root element inside the body element.
191
+ * @private
192
+ */
107
193
  private moveWithFullscreenStrategy(): void
108
194
  {
109
195
  this.nsRootElement.classList.add('ns-fs')
110
196
  move(this.nsRootElement, document.body, true)
111
197
  }
112
198
 
199
+ /**
200
+ * # Replace Splash Instances Having Same Destination
201
+ * Delete all Splash instances residing inside the same destination node.
202
+ * @param {HTMLElement} destinationNode
203
+ * @private
204
+ */
205
+ private replaceSplashInstancesHavingSameDestination(destinationNode: HTMLElement): void
206
+ {
207
+ const fnNotSameInstance = (v: SplashInstance) => v.getId() !== this.getId()
208
+ const fnDelete = (v: SplashInstance) => v.delete()
209
+ this.nsInstance.getSplashesWithDestinationNode(destinationNode).filter(fnNotSameInstance).forEach(fnDelete)
210
+ }
211
+
212
+ /**
213
+ * # Move To
214
+ * This function moves the Nanosplash root element to a destination.
215
+ * @param {Destination} destination
216
+ */
113
217
  public moveTo(destination: Destination): void
114
218
  {
115
- this.cleanup()
116
- const targetNode = NanosplashRepository.destinationToNode(destination)
117
- const targetIsBody = targetNode === document.body
219
+ // Clean up and restore previous location if any.
220
+ this.cleanAndRestore()
221
+
222
+ // Convert the destination into a DOM Node
223
+ this.destinationNode = NanosplashRepository.destinationToNode(destination)
224
+
225
+ // Replace splash instances having the same destination node
226
+ this.replaceSplashInstancesHavingSameDestination(this.destinationNode)
227
+
228
+ // Assess whether the destination node is the Document body or not
229
+ const targetIsBody = this.destinationNode === document.body
230
+
231
+
118
232
  if (targetIsBody) {
119
233
  this.moveWithFullscreenStrategy()
120
234
  } else {
121
235
  this.resetFullscreenAttributes()
122
- this.moveWithRegularStrategy(targetNode)
236
+ this.moveWithRegularStrategy(this.destinationNode)
123
237
  }
238
+
239
+ // Assemble the Nanosplash component
124
240
  this.assembleNSComponent()
125
241
  }
126
242
 
243
+ /**
244
+ * # For Each Wrapped Node
245
+ * Invokes a callback for each wrapped node.
246
+ * @param {(node: Node) => void} callback
247
+ * @private
248
+ */
127
249
  private forEachWrappedNode(callback: (node: Node) => void): void
128
250
  {
129
251
  Array.from(this.nsWrapperElement.childNodes).forEach(callback)
130
252
  }
131
253
 
254
+ /**
255
+ * # Restore DOM Structure
256
+ * Undo the node wrapping and restore the wrapped nodes' original DOM position.
257
+ * @param {Node} parentNode
258
+ * @private
259
+ */
132
260
  private restoreDOMStructure(parentNode: Node): void
133
261
  {
134
262
  this.forEachWrappedNode((child: Node) => parentNode.insertBefore(child, this.nsRootElement))
135
263
  }
136
264
 
265
+ /**
266
+ * # Remove Elements From DOM
267
+ * @private
268
+ */
137
269
  private removeElementsFromDOM(): void
138
270
  {
139
271
  [
140
272
  this.nsTextElement,
273
+ this.nsSpinnerElement,
274
+ this.nsTextContainerElement,
141
275
  this.nsImageElement,
142
276
  this.nsContentElement,
143
277
  this.nsWrapperElement,
@@ -146,8 +280,12 @@ export class SplashInstance implements IdentityInterface {
146
280
  ].forEach(v => v.remove())
147
281
  }
148
282
 
283
+ /**
284
+ * # Delete
285
+ * Gracefully delete Splash instance and connected elements from DOM.
286
+ */
149
287
  public delete(): void {
150
- this.cleanup()
288
+ this.cleanAndRestore()
151
289
  this.removeElementsFromDOM()
152
290
  this.nsInstance.delete(this)
153
291
  }
@@ -1,4 +1,4 @@
1
- import {ImgFunction, ProgressFunction, ShowFunction, SplashJob, WhileFunction} from "../types";
1
+ import {ProgressFunction, ShowFunction, SplashJob, WhileFunction} from "../types";
2
2
  import {Nanosplash} from "../Core/Nanosplash";
3
3
  import {SplashInstance} from "../Core/SplashInstance";
4
4
  import {NanosplashRepository} from "../repositories/NanosplashRepository";
@@ -17,18 +17,6 @@ export class NanosplashFactory {
17
17
  return splash.setText(text).setImgSrc(splash.getImgSrc() ?? imgSrc)
18
18
  }
19
19
 
20
- public static createImgFunction(ns: Nanosplash, splash: SplashInstance | null): ImgFunction
21
- {
22
- return (src: string) => {
23
- splash = NanosplashFactory.ensureInstance(splash, ns, '', src)
24
- return {
25
- show: NanosplashFactory.createShowFunction(ns, splash),
26
- progress: NanosplashFactory.createProgressFunction(ns, splash),
27
- while: NanosplashFactory.createWhileFunction(ns, splash)
28
- }
29
- }
30
- }
31
-
32
20
  public static createShowFunction(ns: Nanosplash, splash: SplashInstance | null): ShowFunction
33
21
  {
34
22
  return (text: string) => {
@@ -1,4 +1,3 @@
1
1
  import {StrategyObject} from "../types";
2
- import {ImageInterface} from "./ImageInterface";
3
2
 
4
- export interface NanosplashInterface extends StrategyObject, ImageInterface {}
3
+ export interface NanosplashInterface extends StrategyObject {}
package/src/main.ts CHANGED
@@ -1,2 +1,8 @@
1
1
  import {Nanosplash} from "./Core/Nanosplash";
2
- window.addEventListener('load', () => window.ns = new Nanosplash())
2
+ import {NanosplashOptions} from "./types";
3
+ window.addEventListener('load', () => {
4
+ window.Nanosplash = Nanosplash
5
+ window.installNanosplash = (options?: NanosplashOptions) => {
6
+ window.ns = new Nanosplash(options)
7
+ }
8
+ })
@@ -1,4 +1,4 @@
1
- import {get} from "../utilities/dom";
1
+ import {addClass, get, mk} from "../utilities/dom";
2
2
  import {SplashInstance} from "../Core/SplashInstance";
3
3
  import {ContextualAPIObject, Destination} from "../types";
4
4
 
@@ -17,7 +17,8 @@ export class NanosplashRepository {
17
17
  throw new Error('Destination argument must string or Node')
18
18
  }
19
19
 
20
- public static createContextualApiObject(splash: SplashInstance): ContextualAPIObject {
20
+ public static createContextualApiObject(splash: SplashInstance): ContextualAPIObject
21
+ {
21
22
  const ctx = {
22
23
  getId: () => splash.getId(),
23
24
  remove: () => splash.delete(),
@@ -35,4 +36,16 @@ export class NanosplashRepository {
35
36
  },
36
37
  }
37
38
  }
39
+
40
+ public static createNanosplashSpinnerElement(): HTMLDivElement
41
+ {
42
+ const div = mk('div') as HTMLDivElement
43
+ addClass(div, 'ns-spinner')
44
+ div.innerHTML = `
45
+ <svg viewBox="0 0 50 50">
46
+ <circle class="path" cx="25" cy="25" r="20" fill="none"></circle>
47
+ </svg>
48
+ `
49
+ return div
50
+ }
38
51
  }
package/src/style.sass CHANGED
@@ -1,14 +1,17 @@
1
1
  $z-index-regular: 1
2
2
  $z-index-fullscreen: 2
3
+ $text-color: #5a6685
3
4
  $bg-color: rgba(White, 0.8)
4
5
 
5
6
  %blur
6
7
  overflow: hidden
7
8
  filter: blur(5px)
8
9
 
10
+ // Wraps the content replaced by Nanosplash
9
11
  .ns-wrapper
10
12
  position: relative
11
13
 
14
+ // Only active while in fullscreen mode
12
15
  .ns-fs
13
16
  top: 0
14
17
  left: 0
@@ -17,10 +20,11 @@ $bg-color: rgba(White, 0.8)
17
20
  min-height: 100vh
18
21
  z-index: $z-index-fullscreen
19
22
 
20
- // Fullscreen content blur
23
+ // Blurs the wrapped content
21
24
  body .ns-fs ~ *, .ns-blur
22
25
  @extend %blur
23
26
 
27
+ // Nanosplash window containing image and text
24
28
  .ns-window
25
29
  top: 0
26
30
  left: 0
@@ -33,26 +37,52 @@ body .ns-fs ~ *, .ns-blur
33
37
  z-index: $z-index-regular
34
38
  background-color: $bg-color
35
39
 
40
+ // Image
36
41
  .ns-img
37
- max-width: 9rem
42
+ width: 9rem
38
43
  max-height: 9rem
39
- margin-bottom: 1rem
44
+ margin-bottom: 2em
40
45
 
46
+ // Text container containing text and spinner
47
+ .ns-text-container
48
+ display: flex
49
+ justify-content: center
50
+ align-items: center
51
+
52
+ // Text node
41
53
  .ns-text
42
- color: #333
43
54
  display: flex
44
- text-align: center
45
- &::after
46
- margin-left: 0.25em
47
- content: '\00a0\00a0\00a0'
48
- animation: anim 1s infinite
55
+ justify-content: center
56
+ color: $text-color
57
+
58
+ // Spinner node
59
+ .ns-spinner
60
+ display: flex
61
+ margin-left: 1em
62
+ width: 1em
63
+ height: 1em
64
+ & > svg
65
+ animation: Rotate 2s linear infinite
66
+ position: relative
67
+ width: inherit
68
+ height: inherit
69
+ stroke-width: 8
70
+ & .path
71
+ stroke: $text-color
72
+ stroke-linecap: round
73
+ animation: Dash 1.5s ease-in-out infinite
49
74
 
50
- @keyframes anim
75
+ // Spinner animations:
76
+ @keyframes Rotate
77
+ 100%
78
+ transform: rotate(360deg)
79
+ @keyframes Dash
51
80
  0%
52
- content: '\00a0\00a0\00a0'
53
- 30%
54
- content: '.\00a0\00a0'
55
- 60%
56
- content: '..\00a0'
57
- 90%
58
- content: '...'
81
+ stroke-dasharray: 1, 150
82
+ stroke-dashoffset: 0
83
+ 50%
84
+ stroke-dasharray: 90, 150
85
+ stroke-dashoffset: -35
86
+ 100%
87
+ stroke-dasharray: 90, 150
88
+ stroke-dashoffset: -124
package/src/types.d.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  import {Nanosplash} from "./Core/Nanosplash";
2
- import {ShowInterface} from "./Interfaces/ShowInterface";
3
- import {InsideInterface} from "./Interfaces/InsideInterface";
4
2
  import {SplashInstance} from "./Core/SplashInstance";
3
+ import {ShowInterface} from "./Interfaces/ShowInterface";
5
4
  import {ContextualAPIInterface} from "./Interfaces/ContextualAPIInterface";
6
5
 
7
6
  declare global {
8
7
  interface Window {
8
+ Nanosplash: typeof Nanosplash
9
+ installNanosplash: (options?: NanosplashOptions) => void
9
10
  ns: Nanosplash
10
11
  }
11
12
  }
@@ -16,7 +17,7 @@ type SplashJob = [Promise<any>, string]
16
17
  /**
17
18
  * @internal
18
19
  */
19
- export type HTMLElementTag = keyof HTMLElementTagNameMap
20
+ export type ElementTag = keyof HTMLElementTagNameMap | keyof SVGElementTagNameMap
20
21
 
21
22
  /**
22
23
  * @internal
@@ -43,8 +44,10 @@ type ContextualAPIObject = {
43
44
  getImgSrc: () => string | undefined,
44
45
  setImgSrc: (src?: string) => SplashInstance
45
46
  }
47
+ type InstanceIterationCallback = (id: string, splashInstance: SplashInstance, i: number) => boolean
46
48
 
47
- declare module 'nanosplash' {
48
- class Nanosplash {}
49
- class SplashInstance {}
49
+ type NanosplashOptions = {
50
+ imgSrc?: string,
51
+ spinner?: boolean,
52
+ fontSize?: string
50
53
  }
@@ -1,4 +1,4 @@
1
- import {HTMLElementTag} from "../types";
1
+ import {ElementTag} from "../types";
2
2
 
3
3
  export function get<T>(selector: string): HTMLElement | T | null {
4
4
  return document.querySelector(selector) as HTMLElement | T | null
@@ -13,7 +13,7 @@ export function move(source: Node, destination: Node, asFirstChild: boolean = fa
13
13
  }
14
14
  }
15
15
 
16
- export const mk = (tag: HTMLElementTag) => document.createElement(tag)
16
+ export const mk = (tag: ElementTag) => document.createElement(tag)
17
17
 
18
18
  export function addClass(node: HTMLElement, ...classes: string[]): void
19
19
  {
@@ -1,5 +0,0 @@
1
- import {ImgFunction} from "../types";
2
-
3
- export interface ImageInterface {
4
- img: ImgFunction
5
- }