@operato/input 2.0.0-alpha.3 → 2.0.0-alpha.8
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.
- package/CHANGELOG.md +19 -0
- package/dist/src/ox-input-barcode.d.ts +46 -2
- package/dist/src/ox-input-barcode.js +69 -28
- package/dist/src/ox-input-barcode.js.map +1 -1
- package/dist/src/ox-input-mass-fraction.d.ts +1 -0
- package/dist/src/ox-input-mass-fraction.js +48 -31
- package/dist/src/ox-input-mass-fraction.js.map +1 -1
- package/dist/stories/ox-input-barcode.stories.js +4 -0
- package/dist/stories/ox-input-barcode.stories.js.map +1 -1
- package/dist/stories/ox-input-mass-fraction.stories.d.ts +4 -0
- package/dist/stories/ox-input-mass-fraction.stories.js +9 -2
- package/dist/stories/ox-input-mass-fraction.stories.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/src/ox-input-barcode.ts +100 -26
- package/src/ox-input-mass-fraction.ts +46 -32
- package/stories/ox-input-barcode.stories.ts +4 -0
- package/stories/ox-input-mass-fraction.stories.ts +10 -1
package/src/ox-input-barcode.ts
CHANGED
@@ -6,14 +6,40 @@ import '@operato/popup/ox-popup.js'
|
|
6
6
|
|
7
7
|
import { css, html } from 'lit'
|
8
8
|
import { customElement, property, query, state } from 'lit/decorators.js'
|
9
|
+
import { scanImageData } from '@undecaf/zbar-wasm'
|
9
10
|
|
10
11
|
import { OxPopup } from '@operato/popup'
|
11
|
-
import { BrowserMultiFormatReader } from '@zxing/library'
|
12
12
|
|
13
13
|
import { OxFormField } from './ox-form-field.js'
|
14
14
|
|
15
15
|
const barcodeIcon = `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAYBAMAAAAfR1CMAAADKGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDUgNzkuMTYzNDk5LCAyMDE4LzA4LzEzLTE2OjQwOjIyICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpFNjM4RURDQkQ1OUExMUU5QkExMkQ4NUY3NkMxNzBFOSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpFNjM4RURDQ0Q1OUExMUU5QkExMkQ4NUY3NkMxNzBFOSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkU2MzhFREM5RDU5QTExRTlCQTEyRDg1Rjc2QzE3MEU5IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkU2MzhFRENBRDU5QTExRTlCQTEyRDg1Rjc2QzE3MEU5Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+55pr/QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAAkUExURQAAAEdwTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEus/7UCWQwAAAALdFJOU9YAg3wKBFBDSz9PnvQNDgAAAE9JREFUGNNjEEQFDKLJSnCOklkgg9QUJFn3RgZhRyS+iCGDEIp2RSBfQICRkRGIgTSQL4jCF6ScvxsZYOFT2T50/6D7Fz080MMLPTzRwhsAHVspfelur08AAAAASUVORK5CYII=`
|
16
16
|
|
17
|
+
/**
|
18
|
+
* Custom input component for barcode scanning.
|
19
|
+
*
|
20
|
+
* This component provides a text input field and a barcode scanning button. Users can input text
|
21
|
+
* manually or scan barcodes using the device camera. Supported barcode formats include:
|
22
|
+
*
|
23
|
+
* - Code-39
|
24
|
+
* - Code-93
|
25
|
+
* - Code-128
|
26
|
+
* - Codabar
|
27
|
+
* - Databar/Expanded
|
28
|
+
* - EAN/GTIN-5/8/13
|
29
|
+
* - ISBN-10/13
|
30
|
+
* - ISBN-13+2
|
31
|
+
* - ISBN-13+5
|
32
|
+
* - ITF (Interleaved 2 of 5)
|
33
|
+
* - QR Code
|
34
|
+
* - UPC-A/E
|
35
|
+
*
|
36
|
+
* @fires CustomEvent#change - Dispatched when the input value changes.
|
37
|
+
* @fires KeyboardEvent#keydown - Dispatched when the Enter key is pressed (if not withoutEnter).
|
38
|
+
*
|
39
|
+
* @cssprop {String} --barcodescan-input-button-icon - Icon for the barcode scanning button.
|
40
|
+
*
|
41
|
+
* @customElement
|
42
|
+
*/
|
17
43
|
@customElement('ox-input-barcode')
|
18
44
|
export class OxInputBarcode extends OxFormField {
|
19
45
|
static styles = [
|
@@ -87,14 +113,37 @@ export class OxInputBarcode extends OxFormField {
|
|
87
113
|
`
|
88
114
|
]
|
89
115
|
|
116
|
+
/**
|
117
|
+
* Indicates whether barcode scanning is enabled.
|
118
|
+
* @property {Boolean} scannable
|
119
|
+
*/
|
90
120
|
@property({ type: Boolean }) scannable?: boolean
|
121
|
+
|
122
|
+
/**
|
123
|
+
* If true, the "Enter" key press event is not fired after scanning a barcode.
|
124
|
+
* @property {Boolean} withoutEnter
|
125
|
+
*/
|
91
126
|
@property({ attribute: 'without-enter', type: Boolean }) withoutEnter?: boolean
|
127
|
+
|
128
|
+
/**
|
129
|
+
* The value of the input field.
|
130
|
+
* @property {String} declare value
|
131
|
+
*/
|
92
132
|
@property({ type: String }) declare value?: string
|
133
|
+
|
134
|
+
/**
|
135
|
+
* If true, only English characters are allowed in the input field.
|
136
|
+
* @property {Boolean} englishOnly
|
137
|
+
*/
|
93
138
|
@property({ attribute: 'english-only', type: Boolean }) englishOnly?: boolean
|
139
|
+
|
140
|
+
/**
|
141
|
+
* If true, the input field is automatically selected after a change event.
|
142
|
+
* @property {Boolean} selectAfterChange
|
143
|
+
*/
|
94
144
|
@property({ attribute: 'select-after-change', type: Boolean }) selectAfterChange?: boolean
|
95
145
|
|
96
146
|
@state() stream?: MediaStream
|
97
|
-
@state() reader?: BrowserMultiFormatReader
|
98
147
|
|
99
148
|
@query('input') input!: HTMLInputElement
|
100
149
|
@query('ox-popup') popup!: OxPopup
|
@@ -108,7 +157,7 @@ export class OxInputBarcode extends OxFormField {
|
|
108
157
|
if (navigator.mediaDevices) {
|
109
158
|
;(async () => {
|
110
159
|
try {
|
111
|
-
var stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } })
|
160
|
+
var stream = await navigator.mediaDevices.getUserMedia({ audio: false, video: { facingMode: 'environment' } })
|
112
161
|
if (stream) {
|
113
162
|
stream.getTracks().forEach(track => track.stop())
|
114
163
|
this.scannable = true
|
@@ -206,22 +255,52 @@ export class OxInputBarcode extends OxFormField {
|
|
206
255
|
/* template.video가 생성된 후에 접근하기 위해서, 한 프레임을 강제로 건너뛴다. */
|
207
256
|
await this.updateComplete
|
208
257
|
|
209
|
-
var constraints = { video: { facingMode: 'environment' } } /* backside camera first */
|
258
|
+
var constraints = { audio: false, video: { facingMode: 'environment' } } /* backside camera first */
|
210
259
|
this.stream = await navigator.mediaDevices.getUserMedia(constraints)
|
211
|
-
|
212
|
-
this.
|
213
|
-
|
214
|
-
|
215
|
-
var
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
260
|
+
this.video.srcObject = this.stream
|
261
|
+
this.video.play()
|
262
|
+
|
263
|
+
this.video.onloadedmetadata = async e => {
|
264
|
+
var canvas = new OffscreenCanvas(
|
265
|
+
this.video.videoWidth || this.video.width,
|
266
|
+
this.video.videoHeight || this.video.height
|
267
|
+
)
|
268
|
+
|
269
|
+
var context = canvas.getContext('2d', {
|
270
|
+
willReadFrequently: true
|
271
|
+
})
|
272
|
+
|
273
|
+
const detect = async () => {
|
274
|
+
try {
|
275
|
+
if (!this.stream?.active) {
|
276
|
+
return
|
277
|
+
}
|
278
|
+
|
279
|
+
context!.drawImage(this.video, 0, 0, canvas.width, canvas.height)
|
280
|
+
const imageData = context!.getImageData(0, 0, canvas.width, canvas.height)
|
281
|
+
const symbols = await scanImageData(imageData)
|
282
|
+
const result = symbols[0]?.decode()
|
283
|
+
|
284
|
+
if (result) {
|
285
|
+
this.stopScan()
|
286
|
+
|
287
|
+
var input = this.input
|
288
|
+
input.focus()
|
289
|
+
this.value = input.value = String(result)
|
290
|
+
|
291
|
+
if (!this.withoutEnter) {
|
292
|
+
input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }))
|
293
|
+
}
|
294
|
+
} else {
|
295
|
+
requestAnimationFrame(async () => await detect())
|
296
|
+
}
|
297
|
+
} catch (e) {
|
298
|
+
console.warn(e)
|
299
|
+
this.stopScan()
|
300
|
+
}
|
221
301
|
}
|
222
|
-
|
223
|
-
|
224
|
-
this.stopScan()
|
302
|
+
|
303
|
+
await detect()
|
225
304
|
}
|
226
305
|
} catch (err) {
|
227
306
|
/*
|
@@ -229,20 +308,15 @@ export class OxInputBarcode extends OxFormField {
|
|
229
308
|
* 2. 뒤로가기 등으로 popup이 종료된 경우에도 NotFoundException: Video stream has ended before any code could be detected. 이 발생한다.
|
230
309
|
*/
|
231
310
|
console.warn(err)
|
232
|
-
} finally {
|
233
|
-
this.popup.close()
|
234
|
-
|
235
|
-
this.stopScan()
|
236
311
|
}
|
237
312
|
}
|
238
313
|
|
239
314
|
stopScan() {
|
240
|
-
this.video
|
315
|
+
if (this.video) {
|
316
|
+
this.video.pause()
|
317
|
+
this.video.srcObject = null
|
318
|
+
}
|
241
319
|
|
242
320
|
this.stream?.getTracks().forEach(track => track.stop())
|
243
|
-
this.reader?.reset()
|
244
|
-
|
245
|
-
delete this.stream
|
246
|
-
delete this.reader
|
247
321
|
}
|
248
322
|
}
|
@@ -131,11 +131,16 @@ export class OxInputMassFraction extends OxFormField {
|
|
131
131
|
display: block;
|
132
132
|
text-align: center;
|
133
133
|
}
|
134
|
+
|
135
|
+
[right-end] {
|
136
|
+
margin-left: auto;
|
137
|
+
}
|
134
138
|
`
|
135
139
|
]
|
136
140
|
|
137
141
|
@property({ type: Object }) defaultValue: MassFraction = {}
|
138
142
|
@property({ type: Object }) value: MassFraction = {}
|
143
|
+
@property({ type: Boolean, attribute: true }) composable: boolean = false
|
139
144
|
|
140
145
|
@queryAll('[data-record]') records!: NodeListOf<HTMLElement>
|
141
146
|
|
@@ -168,14 +173,18 @@ export class OxInputMassFraction extends OxFormField {
|
|
168
173
|
list="value-template"
|
169
174
|
?disabled=${this.disabled}
|
170
175
|
/>
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
176
|
+
${this.composable
|
177
|
+
? html`
|
178
|
+
<button
|
179
|
+
class="record-action"
|
180
|
+
@click=${(e: MouseEvent) => this._delete(e)}
|
181
|
+
tabindex="-1"
|
182
|
+
?disabled=${this.disabled}
|
183
|
+
>
|
184
|
+
<mwc-icon>remove</mwc-icon>
|
185
|
+
</button>
|
186
|
+
`
|
187
|
+
: nothing}
|
179
188
|
<button
|
180
189
|
class="record-action"
|
181
190
|
@click=${(e: MouseEvent) => this._up(e)}
|
@@ -202,36 +211,41 @@ export class OxInputMassFraction extends OxFormField {
|
|
202
211
|
? nothing
|
203
212
|
: html`
|
204
213
|
<div data-record-new>
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
214
|
+
${this.composable
|
215
|
+
? html`
|
216
|
+
<ox-select
|
217
|
+
data-key
|
218
|
+
placeholder="Fluid"
|
219
|
+
.value=${live('')}
|
220
|
+
@change=${(e: Event) => {
|
221
|
+
e.stopPropagation()
|
222
|
+
}}
|
223
|
+
>
|
224
|
+
<ox-popup-list with-search> ${this.options} </ox-popup-list>
|
225
|
+
</ox-select>
|
226
|
+
|
227
|
+
<input
|
228
|
+
type="number"
|
229
|
+
data-value
|
230
|
+
placeholder="proportion"
|
231
|
+
min="0"
|
232
|
+
max="1"
|
233
|
+
step="0.01"
|
234
|
+
value=""
|
235
|
+
list="value-template"
|
236
|
+
/>
|
237
|
+
<button class="record-action" @click=${(e: MouseEvent) => this._add()} tabindex="-1">
|
238
|
+
<mwc-icon>add</mwc-icon>
|
239
|
+
</button>
|
240
|
+
`
|
241
|
+
: nothing}
|
229
242
|
<button
|
230
243
|
title="fill with the values suggested"
|
231
244
|
@click=${() => {
|
232
245
|
this.value = { ...this.defaultValue }
|
233
246
|
this.dispatchChangeEvent()
|
234
247
|
}}
|
248
|
+
right-end
|
235
249
|
>
|
236
250
|
<mwc-icon>settings_suggest</mwc-icon>
|
237
251
|
</button>
|
@@ -43,6 +43,10 @@ const Template: Story<ArgTypes> = ({
|
|
43
43
|
<link href="/themes/app-theme.css" rel="stylesheet" />
|
44
44
|
<link href="https://fonts.googleapis.com/css?family=Material+Icons&display=block" rel="stylesheet" />
|
45
45
|
<style>
|
46
|
+
#root {
|
47
|
+
height: 500px;
|
48
|
+
}
|
49
|
+
|
46
50
|
ox-input-barcode {
|
47
51
|
font-size: 80px;
|
48
52
|
--input-font: initial;
|
@@ -9,6 +9,7 @@ export default {
|
|
9
9
|
name: { control: 'text' },
|
10
10
|
value: { control: 'object' },
|
11
11
|
defaultValue: { control: 'object' },
|
12
|
+
composable: { control: 'boolean' },
|
12
13
|
disabled: { control: 'boolean' }
|
13
14
|
}
|
14
15
|
}
|
@@ -23,6 +24,7 @@ interface ArgTypes {
|
|
23
24
|
name?: string
|
24
25
|
value?: object
|
25
26
|
defaultValue?: object
|
27
|
+
composable?: boolean
|
26
28
|
disabled?: boolean
|
27
29
|
}
|
28
30
|
|
@@ -30,6 +32,7 @@ const Template: Story<ArgTypes> = ({
|
|
30
32
|
name = 'mass-fraction',
|
31
33
|
value = {},
|
32
34
|
defaultValue = {},
|
35
|
+
composable = true,
|
33
36
|
disabled
|
34
37
|
}: ArgTypes) => html`
|
35
38
|
<link href="/themes/app-theme.css" rel="stylesheet" />
|
@@ -46,6 +49,7 @@ const Template: Story<ArgTypes> = ({
|
|
46
49
|
name=${name}
|
47
50
|
.value=${value}
|
48
51
|
.defaultValue=${defaultValue}
|
52
|
+
?composable=${composable}
|
49
53
|
?disabled=${disabled}
|
50
54
|
>
|
51
55
|
</ox-input-mass-fraction>
|
@@ -54,7 +58,12 @@ const Template: Story<ArgTypes> = ({
|
|
54
58
|
export const Regular = Template.bind({})
|
55
59
|
Regular.args = {
|
56
60
|
name: 'mass-fraction',
|
57
|
-
value: {
|
61
|
+
value: {
|
62
|
+
H2O: 0.8,
|
63
|
+
N2O: 0.1,
|
64
|
+
CO2: 0.1
|
65
|
+
},
|
66
|
+
composable: true,
|
58
67
|
defaultValue: {
|
59
68
|
H2O: 0.8,
|
60
69
|
N2O: 0.1,
|