@knowark/componarkjs 1.13.4 → 1.14.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 (35) hide show
  1. package/lib/base/component/component.js +17 -1
  2. package/lib/base/component/component.test.js +475 -389
  3. package/lib/base/utils/define.js +28 -6
  4. package/lib/base/utils/define.test.js +129 -42
  5. package/lib/base/utils/format.test.js +16 -16
  6. package/lib/base/utils/helpers.js +11 -4
  7. package/lib/base/utils/helpers.test.js +134 -115
  8. package/lib/base/utils/slots.test.js +38 -38
  9. package/lib/base/utils/uuid.test.js +13 -13
  10. package/lib/components/audio/components/audio.js +19 -1
  11. package/lib/components/audio/components/audio.test.js +120 -90
  12. package/lib/components/camera/components/camera.js +5 -0
  13. package/lib/components/camera/components/camera.test.js +96 -91
  14. package/lib/components/capture/components/capture.js +30 -2
  15. package/lib/components/capture/components/capture.test.js +165 -97
  16. package/lib/components/droparea/components/droparea-preview.js +58 -8
  17. package/lib/components/droparea/components/droparea-preview.test.js +262 -80
  18. package/lib/components/droparea/components/droparea.js +41 -4
  19. package/lib/components/droparea/components/droparea.test.js +309 -299
  20. package/lib/components/emit/components/emit.js +23 -3
  21. package/lib/components/emit/components/emit.test.js +192 -134
  22. package/lib/components/list/components/item.test.js +69 -68
  23. package/lib/components/list/components/list.js +33 -3
  24. package/lib/components/list/components/list.test.js +358 -227
  25. package/lib/components/paginator/components/paginator.test.js +146 -143
  26. package/lib/components/spinner/components/spinner.test.js +36 -41
  27. package/lib/components/splitview/components/splitview.detail.test.js +75 -73
  28. package/lib/components/splitview/components/splitview.js +36 -8
  29. package/lib/components/splitview/components/splitview.master.js +27 -2
  30. package/lib/components/splitview/components/splitview.master.test.js +52 -52
  31. package/lib/components/splitview/components/splitview.test.js +135 -31
  32. package/lib/components/translate/components/translate.js +28 -8
  33. package/lib/components/translate/components/translate.test.js +492 -133
  34. package/package.json +3 -27
  35. package/scripts/node-test-setup.js +94 -0
@@ -14,7 +14,7 @@ export class Capture extends Component {
14
14
 
15
15
  /** @param {Object} context */
16
16
  init (context = {}) {
17
- const data = JSON.parse(this._pop(':scope > data')?.textContent || null)
17
+ const data = this._parseJSON(this._pop(':scope > data')?.textContent)
18
18
  this.source = /** @type {object} */ (
19
19
  context.source) || data || this.source || {}
20
20
  this.template = context.template || this.template || (
@@ -42,7 +42,24 @@ export class Capture extends Component {
42
42
  }
43
43
 
44
44
  _format (template) {
45
- return (data) => Function(`return \`${template}\``).call(data)
45
+ let render = null
46
+
47
+ try {
48
+ render = Function(`return \`${template}\``)
49
+ } catch (error) {
50
+ this.emit('error', error)
51
+ }
52
+
53
+ return (data) => {
54
+ if (!render) return ''
55
+
56
+ try {
57
+ return render.call(data)
58
+ } catch (error) {
59
+ this.emit('error', error)
60
+ return ''
61
+ }
62
+ }
46
63
  }
47
64
 
48
65
  _pop (selector) {
@@ -50,5 +67,16 @@ export class Capture extends Component {
50
67
  element?.remove()
51
68
  return element
52
69
  }
70
+
71
+ _parseJSON (source) {
72
+ if (!source) return null
73
+
74
+ try {
75
+ return JSON.parse(source)
76
+ } catch (error) {
77
+ this.emit('error', error)
78
+ return null
79
+ }
80
+ }
53
81
  }
54
82
  Component.define(tag, Capture)
@@ -1,112 +1,180 @@
1
+ import { it } from 'node:test'
2
+ import assert from 'node:assert/strict'
1
3
  import './capture.js'
2
4
 
3
- describe('Capture', () => {
4
- let container = null
5
+ let container = null
5
6
 
6
- beforeEach(() => {
7
- container = document.createElement('div')
8
- document.body.appendChild(container)
9
- })
7
+ const setup = () => {
8
+ document.body.innerHTML = ''
9
+ container = document.createElement('div')
10
+ document.body.appendChild(container)
11
+ }
10
12
 
11
- afterEach(() => {
12
- container.remove()
13
- container = null
14
- })
13
+ it('can be instantiated', () => {
14
+ setup()
15
+ container.innerHTML = `
16
+ <ark-capture></ark-capture>
17
+ `
15
18
 
16
- it('can be instantiated', () => {
17
- container.innerHTML = `
18
- <ark-capture></ark-capture>
19
- `
19
+ const capture = container.querySelector('ark-capture')
20
+ assert.deepStrictEqual(capture, capture.init())
21
+ })
20
22
 
21
- const capture = container.querySelector('ark-capture')
22
- expect(capture).toEqual(capture.init())
23
- })
23
+ it('renders the given data detail', () => {
24
+ setup()
25
+ container.innerHTML = `
26
+ <ark-capture>
27
+ <data>
28
+ {
29
+ "name": "John Doe",
30
+ "job": "Programmer"
31
+ }
32
+ </data>
33
+ <output></output>
34
+ </ark-capture>
35
+ `
36
+
37
+ const capture = container.querySelector('ark-capture')
38
+ capture.init().render()
39
+ const output = capture.querySelector('output')
40
+
41
+ assert.deepStrictEqual(capture.children.length, 1)
42
+ assert.ok(output.innerHTML.includes('John Doe'))
43
+ assert.ok(output.innerHTML.includes('Programmer'))
44
+ })
24
45
 
25
- it('renders the given data detail', () => {
26
- container.innerHTML = `
27
- <ark-capture>
28
- <data>
29
- {
30
- "name": "John Doe",
31
- "job": "Programmer"
32
- }
33
- </data>
34
- <output></output>
35
- </ark-capture>
36
- `
37
-
38
- const capture = container.querySelector('ark-capture')
39
- const output = capture.querySelector('output')
40
-
41
- expect(capture.children.length).toEqual(1)
42
- expect(output.innerHTML).toContain('John Doe')
43
- expect(output.innerHTML).toContain('Programmer')
44
- })
46
+ it('renders json data on the given template on the given output', () => {
47
+ setup()
48
+ container.innerHTML = `
49
+ <ark-capture>
50
+ <data>
51
+ {
52
+ "name": "John Doe",
53
+ "job": "Programmer"
54
+ }
55
+ </data>
56
+ <template>
57
+ <div id="output">
58
+ <strong>\${this.name}</strong>
59
+ <strong>\${this.job}</strong>
60
+ </div>
61
+ </template>
62
+ <output></output>
63
+ </ark-capture>
64
+ `
65
+
66
+ const capture = container.querySelector('ark-capture')
67
+ capture.init().render()
68
+ const output = capture.querySelector('output')
69
+
70
+ assert.deepStrictEqual(capture.children.length, 1)
71
+ assert.ok(output.children[0].innerHTML.includes('John Doe'))
72
+ assert.ok(output.children[0].innerHTML.includes('Programmer'))
73
+ })
74
+
75
+ it('captures specific custom events and renders its details', () => {
76
+ setup()
77
+ container.innerHTML = `
78
+ <ark-capture receive="custom">
79
+ <template>
80
+ <div id="output">
81
+ <strong>\${this.name}</strong>
82
+ <strong>\${this.job}</strong>
83
+ </div>
84
+ </template>
85
+ <output></output>
86
+ <p>Adjoint Element</p>
87
+ </ark-capture>
88
+ `
89
+
90
+ const capture = container.querySelector('ark-capture')
91
+ capture.init().render()
92
+ capture.addEventListener('custom', capture.handle.bind(capture))
93
+ const output = capture.querySelector('output')
94
+
95
+ capture.dispatchEvent(new CustomEvent('custom', {
96
+ bubbles: true,
97
+ detail: {
98
+ name: 'Richard Roe', job: 'Analyst'
99
+ }
100
+ }))
101
+
102
+ assert.deepStrictEqual(capture.children.length, 2)
103
+ assert.ok(output.children[0].innerHTML.includes('Richard Roe'))
104
+ assert.ok(output.children[0].innerHTML.includes('Analyst'))
105
+
106
+ capture.dispatchEvent(new CustomEvent('custom', {
107
+ bubbles: true,
108
+ detail: {
109
+ name: 'Megan More', job: 'Manager'
110
+ }
111
+ }))
112
+
113
+ assert.deepStrictEqual(capture.children.length, 2)
114
+ assert.ok(output.children[0].innerHTML.includes('Megan More'))
115
+ assert.ok(output.children[0].innerHTML.includes('Manager'))
116
+ })
45
117
 
46
- it('renders json data on the given template on the given output', () => {
47
- container.innerHTML = `
48
- <ark-capture>
49
- <data>
50
- {
51
- "name": "John Doe",
52
- "job": "Programmer"
53
- }
54
- </data>
55
- <template>
56
- <div id="output">
57
- <strong>\${this.name}</strong>
58
- <strong>\${this.job}</strong>
59
- </div>
60
- </template>
61
- <output></output>
62
- </ark-capture>
63
- `
64
-
65
- const capture = container.querySelector('ark-capture')
66
- const output = capture.querySelector('output')
67
-
68
- expect(capture.children.length).toEqual(1)
69
- expect(output.children[0].innerHTML).toContain('John Doe')
70
- expect(output.children[0].innerHTML).toContain('Programmer')
118
+ it('emits error when data JSON is invalid', () => {
119
+ setup()
120
+ const holder = document.createElement('div')
121
+ holder.innerHTML = `
122
+ <ark-capture>
123
+ <data>{ invalid json }</data>
124
+ <output></output>
125
+ </ark-capture>
126
+ `
127
+ const capture = holder.querySelector('ark-capture')
128
+ let errorEvent = null
129
+ capture.addEventListener('error', (event) => {
130
+ errorEvent = event
71
131
  })
72
132
 
73
- it('captures specific custom events and renders its details', () => {
74
- container.innerHTML = `
75
- <ark-capture receive="custom">
76
- <template>
77
- <div id="output">
78
- <strong>\${this.name}</strong>
79
- <strong>\${this.job}</strong>
80
- </div>
81
- </template>
82
- <output></output>
83
- <p>Adjoint Element</p>
84
- </ark-capture>
85
- `
86
-
87
- const capture = container.querySelector('ark-capture')
88
- const output = capture.querySelector('output')
89
-
90
- capture.dispatchEvent(new CustomEvent('custom', {
91
- bubbles: true,
92
- detail: {
93
- name: 'Richard Roe', job: 'Analyst'
94
- }
95
- }))
133
+ capture.init().render()
96
134
 
97
- expect(capture.children.length).toEqual(2)
98
- expect(output.children[0].innerHTML).toContain('Richard Roe')
99
- expect(output.children[0].innerHTML).toContain('Analyst')
135
+ assert.ok(errorEvent)
136
+ })
100
137
 
101
- capture.dispatchEvent(new CustomEvent('custom', {
102
- bubbles: true,
103
- detail: {
104
- name: 'Megan More', job: 'Manager'
105
- }
106
- }))
138
+ it('emits error when template cannot be compiled', () => {
139
+ setup()
140
+ const holder = document.createElement('div')
141
+ holder.innerHTML = `
142
+ <ark-capture>
143
+ <data>{"name":"John"}</data>
144
+ <template>\${this.name</template>
145
+ <output></output>
146
+ </ark-capture>
147
+ `
148
+ const capture = holder.querySelector('ark-capture')
149
+ let errorEvent = null
150
+ capture.addEventListener('error', (event) => {
151
+ errorEvent = event
152
+ })
107
153
 
108
- expect(capture.children.length).toEqual(2)
109
- expect(output.children[0].innerHTML).toContain('Megan More')
110
- expect(output.children[0].innerHTML).toContain('Manager')
154
+ capture.init().render()
155
+
156
+ assert.ok(errorEvent)
157
+ assert.deepStrictEqual(capture.querySelector('output').innerHTML, '')
158
+ })
159
+
160
+ it('emits error when template evaluation fails at runtime', () => {
161
+ setup()
162
+ const holder = document.createElement('div')
163
+ holder.innerHTML = `
164
+ <ark-capture>
165
+ <data>{"name":"John"}</data>
166
+ <template>\${this.profile.name}</template>
167
+ <output></output>
168
+ </ark-capture>
169
+ `
170
+ const capture = holder.querySelector('ark-capture')
171
+ let errorEvent = null
172
+ capture.addEventListener('error', (event) => {
173
+ errorEvent = event
111
174
  })
175
+
176
+ capture.init().render()
177
+
178
+ assert.ok(errorEvent)
179
+ assert.deepStrictEqual(capture.querySelector('output').innerHTML, '')
112
180
  })
@@ -6,10 +6,21 @@ import './droparea.js'
6
6
  const tag = 'ark-droparea-preview'
7
7
 
8
8
  export class DropareaPreview extends Component {
9
+ constructor () {
10
+ super()
11
+ this._objectUrls = new Map()
12
+ this._onDragEnd = this.handleDrop.bind(this)
13
+ }
14
+
9
15
  init (_context = {}) {
10
16
  return super.init()
11
17
  }
12
18
 
19
+ disconnectedCallback () {
20
+ this.revokeAllFiles()
21
+ super.disconnectedCallback()
22
+ }
23
+
13
24
  render () {
14
25
  this.content = /* html */ `
15
26
  <ul data-preview-list class="ark-droparea-preview__list drag-sort-enable"></ul>
@@ -18,7 +29,7 @@ export class DropareaPreview extends Component {
18
29
  }
19
30
 
20
31
  previewFile (file) {
21
- const blobUrl = URL.createObjectURL(file)
32
+ const blobUrl = this.getObjectURL(file)
22
33
  const fileType = file.type.split('/')[0]
23
34
  const previewZone = this.select('[data-preview-list]')
24
35
  const picture = document.createElement('li')
@@ -28,7 +39,9 @@ export class DropareaPreview extends Component {
28
39
  removeButton.className = 'ark-droparea__remove'
29
40
 
30
41
  if (fileType != 'image') {
31
- picture.innerHTML = `<p>${file.name}</p>`
42
+ const text = document.createElement('p')
43
+ text.textContent = file.name
44
+ picture.appendChild(text)
32
45
  picture.setAttribute('data', `${blobUrl}`)
33
46
  } else {
34
47
  picture.style.backgroundImage = `url('${blobUrl}')`
@@ -68,9 +81,12 @@ export class DropareaPreview extends Component {
68
81
  }
69
82
 
70
83
  enableDragItem (item) {
84
+ if (item.hasAttribute('data-drag-enabled')) return
85
+
86
+ item.setAttribute('data-drag-enabled', '')
71
87
  item.setAttribute('draggable', true)
72
88
  item.addEventListener('drag', this.handleDrag.bind(this, item))
73
- item.addEventListener('dragend', this.handleDrop, false)
89
+ item.addEventListener('dragend', this._onDragEnd, false)
74
90
  }
75
91
 
76
92
  /* istanbul ignore next */
@@ -94,10 +110,12 @@ export class DropareaPreview extends Component {
94
110
  }
95
111
  }
96
112
 
97
- handleDrop (item) {
98
- const droparea = item.target.closest('ark-droparea')
113
+ handleDrop (event) {
114
+ const droparea = event.target.closest('ark-droparea')
115
+ if (!droparea) return
116
+
99
117
  droparea.preview.createNewFileList()
100
- item.target.classList.remove('drag-sort-active')
118
+ event.target.classList.remove('drag-sort-active')
101
119
  droparea.preview.dispatchAlterEvent()
102
120
  }
103
121
  /* ---------------------------------------------------- */
@@ -120,9 +138,41 @@ export class DropareaPreview extends Component {
120
138
  return this.files.some((item) => item.name === file.name)
121
139
  }
122
140
 
141
+ getObjectURL (file) {
142
+ if (this._objectUrls.has(file)) return this._objectUrls.get(file)
143
+
144
+ const url = URL.createObjectURL(file)
145
+ this._objectUrls.set(file, url)
146
+ return url
147
+ }
148
+
149
+ revokeFile (file) {
150
+ const url = this._objectUrls.get(file)
151
+ if (!url) return
152
+
153
+ this._objectUrls.delete(file)
154
+ URL.revokeObjectURL?.(url)
155
+ }
156
+
157
+ revokeAllFiles () {
158
+ for (const url of this._objectUrls.values()) {
159
+ URL.revokeObjectURL?.(url)
160
+ }
161
+ this._objectUrls.clear()
162
+ }
163
+
164
+ clearPreview () {
165
+ const previewZone = this.select('[data-preview-list]')
166
+ previewZone && (previewZone.textContent = '')
167
+ this.toggleVisibility()
168
+ }
169
+
123
170
  removeFile (file, event) {
124
171
  const element = event.target
125
172
  const fileIndex = this.droparea.fileList.indexOf(file)
173
+ if (fileIndex < 0) return
174
+
175
+ this.revokeFile(file)
126
176
  this.droparea.fileList.splice(fileIndex, 1)
127
177
  element.parentNode.remove()
128
178
  this.selectAll('li').forEach((item, index) =>
@@ -137,7 +187,7 @@ export class DropareaPreview extends Component {
137
187
  }
138
188
 
139
189
  get droparea () {
140
- return /** @type {Droparea} */ (this.closest('.ark-droparea'))
190
+ return /** @type {Droparea} */ (this.closest('ark-droparea'))
141
191
  }
142
192
 
143
193
  get mediaList () {
@@ -147,7 +197,7 @@ export class DropareaPreview extends Component {
147
197
  name: file.name,
148
198
  type: file.type,
149
199
  size: file.size,
150
- url: URL.createObjectURL(file)
200
+ url: this.getObjectURL(file)
151
201
  })
152
202
  })
153
203
  return mediaList