@operato/input 10.29.43 → 10.29.49

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 (29) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/coverage/lcov-report/base.css +224 -0
  3. package/coverage/lcov-report/block-navigation.js +87 -0
  4. package/coverage/lcov-report/favicon.png +0 -0
  5. package/coverage/lcov-report/index.html +776 -0
  6. package/coverage/lcov-report/prettify.css +1 -0
  7. package/coverage/lcov-report/prettify.js +2 -0
  8. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  9. package/coverage/lcov-report/sorter.js +210 -0
  10. package/coverage/lcov-report/webpack/bootstrap.html +118 -0
  11. package/coverage/lcov-report/webpack/index.html +116 -0
  12. package/coverage/lcov-report/webpack/runtime/define property getters.html +118 -0
  13. package/coverage/lcov-report/webpack/runtime/global.html +118 -0
  14. package/coverage/lcov-report/webpack/runtime/hasOwnProperty shorthand.html +118 -0
  15. package/coverage/lcov-report/webpack/runtime/index.html +161 -0
  16. package/coverage/lcov-report/webpack/runtime/make namespace object.html +118 -0
  17. package/coverage/lcov.info +47330 -0
  18. package/dist/src/ox-input-barcode.d.ts +2 -0
  19. package/dist/src/ox-input-barcode.js +5 -0
  20. package/dist/src/ox-input-barcode.js.map +1 -1
  21. package/dist/src/ox-input-file.d.ts +5 -0
  22. package/dist/src/ox-input-file.js +5 -1
  23. package/dist/src/ox-input-file.js.map +1 -1
  24. package/dist/test/ox-input-barcode.test.d.ts +1 -0
  25. package/dist/test/ox-input-barcode.test.js +53 -0
  26. package/dist/test/ox-input-barcode.test.js.map +1 -0
  27. package/dist/tsconfig.tsbuildinfo +1 -1
  28. package/package.json +4 -4
  29. package/test/ox-input-barcode.test.ts +59 -0
@@ -66,6 +66,8 @@ export declare class OxInputBarcode extends OxFormField {
66
66
  connectedCallback(): void;
67
67
  disconnectedCallback(): void;
68
68
  render(): import("lit-html").TemplateResult<1>;
69
+ /** The value an Enter said last - the browser's own change on leaving the field would say it a second time. */
70
+ private saidOnEnter?;
69
71
  onInputChange(e: Event): void;
70
72
  onInputKeyDown(e: KeyboardEvent): void;
71
73
  scan(e: MouseEvent): Promise<void>;
@@ -157,6 +157,11 @@ let OxInputBarcode = class OxInputBarcode extends OxFormField {
157
157
  }
158
158
  onInputChange(e) {
159
159
  e.stopPropagation();
160
+ /* The change Enter dispatches is a CustomEvent; the browser's, on leaving the field, is not. */
161
+ const byEnter = e instanceof CustomEvent;
162
+ if (!byEnter && this.input.value === this.saidOnEnter)
163
+ return;
164
+ this.saidOnEnter = byEnter ? this.input.value : undefined;
160
165
  if (this.englishOnly) {
161
166
  /* englishOnly 인 경우에는 멀티바이트 문자들을 모두 제거한다. */
162
167
  this.value = this.input.value = this.input.value?.replace(/[^\x00-\x7F]/g, '');
@@ -1 +1 @@
1
- {"version":3,"file":"ox-input-barcode.js","sourceRoot":"","sources":["../../src/ox-input-barcode.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @license Copyright © HatioLab Inc. All rights reserved.\n */\n\nimport '@operato/popup/ox-popup.js'\n\nimport { css, html } from 'lit'\nimport { customElement, property, query, state } from 'lit/decorators.js'\nimport { scanImageData } from '@undecaf/zbar-wasm'\n\nimport { i18next } from '@operato/i18n'\nimport { OxPopup } from '@operato/popup'\nimport { formElementHeight } from '@operato/styles/form-element-heights.js'\n\nimport { OxFormField } from './ox-form-field.js'\nimport { InputStyles } from './input-styles.js'\n\nconst barcodeIcon = `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAYBAMAAAAfR1CMAAADKGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDUgNzkuMTYzNDk5LCAyMDE4LzA4LzEzLTE2OjQwOjIyICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpFNjM4RURDQkQ1OUExMUU5QkExMkQ4NUY3NkMxNzBFOSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpFNjM4RURDQ0Q1OUExMUU5QkExMkQ4NUY3NkMxNzBFOSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkU2MzhFREM5RDU5QTExRTlCQTEyRDg1Rjc2QzE3MEU5IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkU2MzhFRENBRDU5QTExRTlCQTEyRDg1Rjc2QzE3MEU5Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+55pr/QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAAkUExURQAAAEdwTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEus/7UCWQwAAAALdFJOU9YAg3wKBFBDSz9PnvQNDgAAAE9JREFUGNNjEEQFDKLJSnCOklkgg9QUJFn3RgZhRyS+iCGDEIp2RSBfQICRkRGIgTSQL4jCF6ScvxsZYOFT2T50/6D7Fz080MMLPTzRwhsAHVspfelur08AAAAASUVORK5CYII=`\n\n/**\n * Custom input component for barcode scanning.\n *\n * This component provides a text input field and a barcode scanning button. Users can input text\n * manually or scan barcodes using the device camera. Supported barcode formats include:\n *\n * - Code-39\n * - Code-93\n * - Code-128\n * - Codabar\n * - Databar/Expanded\n * - EAN/GTIN-5/8/13\n * - ISBN-10/13\n * - ISBN-13+2\n * - ISBN-13+5\n * - ITF (Interleaved 2 of 5)\n * - QR Code\n * - UPC-A/E\n *\n * @fires CustomEvent#change - Dispatched when the input value changes.\n * @fires KeyboardEvent#keydown - Dispatched when the Enter key is pressed (if not withoutEnter).\n *\n * @cssprop {String} --barcodescan-input-button-icon - Icon for the barcode scanning button.\n * @cssprop {String} --barcodescan-input-button-icon-size - The icon's background-size (default: auto, its own 30x24).\n * @cssprop {String} --barcodescan-input-button-width - The button's width, and the room the input keeps for it (default: 35px).\n *\n * @customElement\n */\n@customElement('ox-input-barcode')\nexport class OxInputBarcode extends OxFormField {\n static styles = [\n InputStyles,\n css`\n :host {\n display: flex;\n align-items: center;\n border: none;\n background-color: transparent;\n height: ${formElementHeight('--form-element-height-medium')};\n }\n\n * {\n align-self: stretch;\n }\n\n *:focus {\n outline: none;\n }\n\n input {\n flex: 1;\n width: 10px; /* intentionally width set */\n border: var(--ox-input-border, 1px solid var(--md-sys-color-outline));\n border-radius: var(--ox-input-radius, var(--md-sys-shape-corner-small));\n background-color: var(--ox-input-background, var(--md-sys-color-on-primary));\n color: var(--ox-input-color, var(--md-sys-color-on-primary-container));\n font-size: var(--md-sys-typescale-label-large-size, 0.875rem);\n\n box-sizing: border-box;\n padding-right: var(--barcodescan-input-button-width, 35px);\n }\n\n input:focus {\n outline: none;\n border-color: var(--md-sys-color-secondary-fixed-dim);\n }\n\n #scan-button {\n display: block;\n position: relative;\n margin-left: calc(-1 * var(--barcodescan-input-button-width, 35px));\n width: var(--barcodescan-input-button-width, 35px);\n border: none;\n background: var(--barcodescan-input-button-icon) no-repeat center center;\n background-size: var(--barcodescan-input-button-icon-size, auto);\n }\n\n #scan-button[hidden] {\n display: none;\n }\n `\n ]\n\n /**\n * Whether this device has a camera to scan with - the camera button shows only then. Reflected, so a host can\n * style itself by it (`ox-input-barcode[scannable]`) without asking the device again.\n * @property {Boolean} scannable\n */\n @property({ type: Boolean, attribute: true, reflect: true }) scannable?: boolean\n\n /**\n * If true, the \"Enter\" key press event is not fired after scanning a barcode.\n * @property {Boolean} withoutEnter\n */\n @property({ attribute: 'without-enter', type: Boolean }) withoutEnter?: boolean\n\n /**\n * The value of the input field.\n * @property {String} declare value\n */\n @property({ type: String }) declare value?: string\n\n /**\n * If true, only English characters are allowed in the input field.\n * @property {Boolean} englishOnly\n */\n @property({ attribute: 'english-only', type: Boolean }) englishOnly?: boolean\n\n /**\n * If true, the input field is automatically selected after a change event.\n * @property {Boolean} selectAfterChange\n */\n @property({ attribute: 'select-after-change', type: Boolean }) selectAfterChange?: boolean\n\n @state() stream?: MediaStream\n\n @query('input') input!: HTMLInputElement\n\n private popup: OxPopup | null = null\n private video: HTMLVideoElement | null = null\n\n connectedCallback() {\n super.connectedCallback()\n\n /*\n * Whether to offer the camera button: ask which kinds of device there are, never open one. Opening the camera\n * here asked for camera permission - and switched the camera on - wherever the field was merely drawn, so a\n * field in an app's header asked on every page. Permission is asked when the person presses the button.\n */\n if (navigator.mediaDevices?.enumerateDevices) {\n ;(async () => {\n try {\n const devices = await navigator.mediaDevices.enumerateDevices()\n this.scannable = devices.some(device => device.kind === 'videoinput')\n } catch (e) {\n this.scannable = false\n console.warn('this device not support camera for barcode scan', e)\n }\n })()\n } else {\n this.scannable = false\n }\n }\n\n disconnectedCallback() {\n this.stopScan()\n }\n\n render() {\n this.style.setProperty('--barcodescan-input-button-icon', `url(${barcodeIcon})`)\n\n /*\n * Name both controls. The input had no name and no placeholder - `placeholder` set by the caller never reached\n * it - and the camera button was an empty button. Same rule as ox-input-search: the caller's placeholder is the\n * name, \"barcode\" when there is none.\n */\n const scanLabel = i18next.t('button.scan-with-camera', { defaultValue: 'scan with camera' })\n\n return html`\n <input\n type=\"text\"\n aria-label=${this.placeholder || i18next.t('label.barcode', { defaultValue: 'barcode' })}\n .placeholder=${this.placeholder || ''}\n .value=${this.value || ''}\n @change=${(e: Event) => this.onInputChange(e)}\n @keydown=${(e: KeyboardEvent) => this.onInputKeyDown(e)}\n ?disabled=${this.disabled}\n />\n <button\n ?hidden=${!this.scannable}\n id=\"scan-button\"\n aria-label=${scanLabel}\n title=${scanLabel}\n @click=${(e: MouseEvent) => {\n this.scan(e)\n }}\n ?disabled=${this.disabled}\n ></button>\n `\n }\n\n onInputChange(e: Event) {\n e.stopPropagation()\n\n if (this.englishOnly) {\n /* englishOnly 인 경우에는 멀티바이트 문자들을 모두 제거한다. */\n this.value = this.input.value = this.input.value?.replace(/[^\\x00-\\x7F]/g, '')\n } else {\n this.value = this.input.value\n }\n\n if (this.selectAfterChange) {\n requestAnimationFrame(() => {\n this.input.select()\n })\n }\n\n this.dispatchEvent(\n new CustomEvent('change', {\n detail: this.value\n })\n )\n }\n\n onInputKeyDown(e: KeyboardEvent) {\n if (e.key === 'Enter' && !e.isComposing) {\n /* Even if the value has not changed, the enter key triggers a change event. */\n e.preventDefault() /* Prevent change event from occurring twice. */\n\n this.input.dispatchEvent(new CustomEvent('change'))\n } else if (this.englishOnly && !e.metaKey && !e.ctrlKey && !e.altKey && /^Key/.test(e.code)) {\n e.stopPropagation()\n e.preventDefault()\n\n /* englishOnly 인 경우에 문자들은 여기에서 처리한다. 멀티바이트 문자들이 대부분 알파벳의 자모음을 조합하므로, ... */\n const key = e.shiftKey ? e.code.charAt(3) : e.code.charAt(3).toLowerCase()\n const value = this.input.value\n\n const start = this.input.selectionStart || 0\n const end = this.input.selectionEnd || start\n\n this.input.value = [value.substring(0, start), key, value.substring(end)].join('')\n this.input.setSelectionRange(start + 1, start + 1)\n }\n }\n\n async scan(e: MouseEvent) {\n try {\n if (this.popup) {\n this.stopScan()\n }\n\n this.popup = OxPopup.open({\n template: html`\n <video></video>\n <md-icon\n style=\"position: fixed; right: 0; top: 0; color: red; tabindex: 0\"\n @click=${() => {\n this.stopScan()\n }}\n >close</md-icon\n >\n `,\n width: '100vw',\n height: '100dvh'\n })\n\n this.video! = this.popup.querySelector('video') as HTMLVideoElement\n\n var constraints = { audio: false, video: { facingMode: 'environment' } } /* backside camera first */\n this.stream = await navigator.mediaDevices.getUserMedia(constraints)\n\n this.video.srcObject = this.stream\n this.video.play()\n\n this.video.onloadedmetadata = async e => {\n var canvas = new OffscreenCanvas(\n this.video!.videoWidth || this.video!.width,\n this.video!.videoHeight || this.video!.height\n )\n\n var context = canvas.getContext('2d', {\n willReadFrequently: true\n })\n\n const detect = async () => {\n try {\n if (!this.stream?.active) {\n return\n }\n\n context!.drawImage(this.video!, 0, 0, canvas.width, canvas.height)\n const imageData = context!.getImageData(0, 0, canvas.width, canvas.height)\n const symbols = await scanImageData(imageData)\n const result = symbols[0]?.decode()\n\n if (result) {\n this.stopScan()\n\n var input = this.input\n input.focus()\n this.value = input.value = String(result)\n\n if (!this.withoutEnter) {\n input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }))\n }\n } else {\n requestAnimationFrame(async () => await detect())\n }\n } catch (e) {\n console.warn(e)\n this.stopScan()\n }\n }\n\n await detect()\n }\n } catch (err) {\n /*\n * 1. stream device 문제로 예외 발생한 경우.\n * 2. 뒤로가기 등으로 popup이 종료된 경우에도 NotFoundException: Video stream has ended before any code could be detected. 이 발생한다.\n */\n console.warn(err)\n }\n }\n\n stopScan() {\n if (this.video) {\n this.video.pause()\n this.video.srcObject = null\n }\n\n if (this.popup) {\n this.popup.close()\n this.popup = null\n }\n\n this.stream?.getTracks().forEach(track => track.stop())\n }\n}\n"]}
1
+ {"version":3,"file":"ox-input-barcode.js","sourceRoot":"","sources":["../../src/ox-input-barcode.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @license Copyright © HatioLab Inc. All rights reserved.\n */\n\nimport '@operato/popup/ox-popup.js'\n\nimport { css, html } from 'lit'\nimport { customElement, property, query, state } from 'lit/decorators.js'\nimport { scanImageData } from '@undecaf/zbar-wasm'\n\nimport { i18next } from '@operato/i18n'\nimport { OxPopup } from '@operato/popup'\nimport { formElementHeight } from '@operato/styles/form-element-heights.js'\n\nimport { OxFormField } from './ox-form-field.js'\nimport { InputStyles } from './input-styles.js'\n\nconst barcodeIcon = `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAYBAMAAAAfR1CMAAADKGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDUgNzkuMTYzNDk5LCAyMDE4LzA4LzEzLTE2OjQwOjIyICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpFNjM4RURDQkQ1OUExMUU5QkExMkQ4NUY3NkMxNzBFOSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpFNjM4RURDQ0Q1OUExMUU5QkExMkQ4NUY3NkMxNzBFOSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkU2MzhFREM5RDU5QTExRTlCQTEyRDg1Rjc2QzE3MEU5IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkU2MzhFRENBRDU5QTExRTlCQTEyRDg1Rjc2QzE3MEU5Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+55pr/QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAAkUExURQAAAEdwTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEus/7UCWQwAAAALdFJOU9YAg3wKBFBDSz9PnvQNDgAAAE9JREFUGNNjEEQFDKLJSnCOklkgg9QUJFn3RgZhRyS+iCGDEIp2RSBfQICRkRGIgTSQL4jCF6ScvxsZYOFT2T50/6D7Fz080MMLPTzRwhsAHVspfelur08AAAAASUVORK5CYII=`\n\n/**\n * Custom input component for barcode scanning.\n *\n * This component provides a text input field and a barcode scanning button. Users can input text\n * manually or scan barcodes using the device camera. Supported barcode formats include:\n *\n * - Code-39\n * - Code-93\n * - Code-128\n * - Codabar\n * - Databar/Expanded\n * - EAN/GTIN-5/8/13\n * - ISBN-10/13\n * - ISBN-13+2\n * - ISBN-13+5\n * - ITF (Interleaved 2 of 5)\n * - QR Code\n * - UPC-A/E\n *\n * @fires CustomEvent#change - Dispatched when the input value changes.\n * @fires KeyboardEvent#keydown - Dispatched when the Enter key is pressed (if not withoutEnter).\n *\n * @cssprop {String} --barcodescan-input-button-icon - Icon for the barcode scanning button.\n * @cssprop {String} --barcodescan-input-button-icon-size - The icon's background-size (default: auto, its own 30x24).\n * @cssprop {String} --barcodescan-input-button-width - The button's width, and the room the input keeps for it (default: 35px).\n *\n * @customElement\n */\n@customElement('ox-input-barcode')\nexport class OxInputBarcode extends OxFormField {\n static styles = [\n InputStyles,\n css`\n :host {\n display: flex;\n align-items: center;\n border: none;\n background-color: transparent;\n height: ${formElementHeight('--form-element-height-medium')};\n }\n\n * {\n align-self: stretch;\n }\n\n *:focus {\n outline: none;\n }\n\n input {\n flex: 1;\n width: 10px; /* intentionally width set */\n border: var(--ox-input-border, 1px solid var(--md-sys-color-outline));\n border-radius: var(--ox-input-radius, var(--md-sys-shape-corner-small));\n background-color: var(--ox-input-background, var(--md-sys-color-on-primary));\n color: var(--ox-input-color, var(--md-sys-color-on-primary-container));\n font-size: var(--md-sys-typescale-label-large-size, 0.875rem);\n\n box-sizing: border-box;\n padding-right: var(--barcodescan-input-button-width, 35px);\n }\n\n input:focus {\n outline: none;\n border-color: var(--md-sys-color-secondary-fixed-dim);\n }\n\n #scan-button {\n display: block;\n position: relative;\n margin-left: calc(-1 * var(--barcodescan-input-button-width, 35px));\n width: var(--barcodescan-input-button-width, 35px);\n border: none;\n background: var(--barcodescan-input-button-icon) no-repeat center center;\n background-size: var(--barcodescan-input-button-icon-size, auto);\n }\n\n #scan-button[hidden] {\n display: none;\n }\n `\n ]\n\n /**\n * Whether this device has a camera to scan with - the camera button shows only then. Reflected, so a host can\n * style itself by it (`ox-input-barcode[scannable]`) without asking the device again.\n * @property {Boolean} scannable\n */\n @property({ type: Boolean, attribute: true, reflect: true }) scannable?: boolean\n\n /**\n * If true, the \"Enter\" key press event is not fired after scanning a barcode.\n * @property {Boolean} withoutEnter\n */\n @property({ attribute: 'without-enter', type: Boolean }) withoutEnter?: boolean\n\n /**\n * The value of the input field.\n * @property {String} declare value\n */\n @property({ type: String }) declare value?: string\n\n /**\n * If true, only English characters are allowed in the input field.\n * @property {Boolean} englishOnly\n */\n @property({ attribute: 'english-only', type: Boolean }) englishOnly?: boolean\n\n /**\n * If true, the input field is automatically selected after a change event.\n * @property {Boolean} selectAfterChange\n */\n @property({ attribute: 'select-after-change', type: Boolean }) selectAfterChange?: boolean\n\n @state() stream?: MediaStream\n\n @query('input') input!: HTMLInputElement\n\n private popup: OxPopup | null = null\n private video: HTMLVideoElement | null = null\n\n connectedCallback() {\n super.connectedCallback()\n\n /*\n * Whether to offer the camera button: ask which kinds of device there are, never open one. Opening the camera\n * here asked for camera permission - and switched the camera on - wherever the field was merely drawn, so a\n * field in an app's header asked on every page. Permission is asked when the person presses the button.\n */\n if (navigator.mediaDevices?.enumerateDevices) {\n ;(async () => {\n try {\n const devices = await navigator.mediaDevices.enumerateDevices()\n this.scannable = devices.some(device => device.kind === 'videoinput')\n } catch (e) {\n this.scannable = false\n console.warn('this device not support camera for barcode scan', e)\n }\n })()\n } else {\n this.scannable = false\n }\n }\n\n disconnectedCallback() {\n this.stopScan()\n }\n\n render() {\n this.style.setProperty('--barcodescan-input-button-icon', `url(${barcodeIcon})`)\n\n /*\n * Name both controls. The input had no name and no placeholder - `placeholder` set by the caller never reached\n * it - and the camera button was an empty button. Same rule as ox-input-search: the caller's placeholder is the\n * name, \"barcode\" when there is none.\n */\n const scanLabel = i18next.t('button.scan-with-camera', { defaultValue: 'scan with camera' })\n\n return html`\n <input\n type=\"text\"\n aria-label=${this.placeholder || i18next.t('label.barcode', { defaultValue: 'barcode' })}\n .placeholder=${this.placeholder || ''}\n .value=${this.value || ''}\n @change=${(e: Event) => this.onInputChange(e)}\n @keydown=${(e: KeyboardEvent) => this.onInputKeyDown(e)}\n ?disabled=${this.disabled}\n />\n <button\n ?hidden=${!this.scannable}\n id=\"scan-button\"\n aria-label=${scanLabel}\n title=${scanLabel}\n @click=${(e: MouseEvent) => {\n this.scan(e)\n }}\n ?disabled=${this.disabled}\n ></button>\n `\n }\n\n /** The value an Enter said last - the browser's own change on leaving the field would say it a second time. */\n private saidOnEnter?: string\n\n onInputChange(e: Event) {\n e.stopPropagation()\n\n /* The change Enter dispatches is a CustomEvent; the browser's, on leaving the field, is not. */\n const byEnter = e instanceof CustomEvent\n if (!byEnter && this.input.value === this.saidOnEnter) return\n this.saidOnEnter = byEnter ? this.input.value : undefined\n\n if (this.englishOnly) {\n /* englishOnly 인 경우에는 멀티바이트 문자들을 모두 제거한다. */\n this.value = this.input.value = this.input.value?.replace(/[^\\x00-\\x7F]/g, '')\n } else {\n this.value = this.input.value\n }\n\n if (this.selectAfterChange) {\n requestAnimationFrame(() => {\n this.input.select()\n })\n }\n\n this.dispatchEvent(\n new CustomEvent('change', {\n detail: this.value\n })\n )\n }\n\n onInputKeyDown(e: KeyboardEvent) {\n if (e.key === 'Enter' && !e.isComposing) {\n /* Even if the value has not changed, the enter key triggers a change event. */\n e.preventDefault() /* Prevent change event from occurring twice. */\n\n this.input.dispatchEvent(new CustomEvent('change'))\n } else if (this.englishOnly && !e.metaKey && !e.ctrlKey && !e.altKey && /^Key/.test(e.code)) {\n e.stopPropagation()\n e.preventDefault()\n\n /* englishOnly 인 경우에 문자들은 여기에서 처리한다. 멀티바이트 문자들이 대부분 알파벳의 자모음을 조합하므로, ... */\n const key = e.shiftKey ? e.code.charAt(3) : e.code.charAt(3).toLowerCase()\n const value = this.input.value\n\n const start = this.input.selectionStart || 0\n const end = this.input.selectionEnd || start\n\n this.input.value = [value.substring(0, start), key, value.substring(end)].join('')\n this.input.setSelectionRange(start + 1, start + 1)\n }\n }\n\n async scan(e: MouseEvent) {\n try {\n if (this.popup) {\n this.stopScan()\n }\n\n this.popup = OxPopup.open({\n template: html`\n <video></video>\n <md-icon\n style=\"position: fixed; right: 0; top: 0; color: red; tabindex: 0\"\n @click=${() => {\n this.stopScan()\n }}\n >close</md-icon\n >\n `,\n width: '100vw',\n height: '100dvh'\n })\n\n this.video! = this.popup.querySelector('video') as HTMLVideoElement\n\n var constraints = { audio: false, video: { facingMode: 'environment' } } /* backside camera first */\n this.stream = await navigator.mediaDevices.getUserMedia(constraints)\n\n this.video.srcObject = this.stream\n this.video.play()\n\n this.video.onloadedmetadata = async e => {\n var canvas = new OffscreenCanvas(\n this.video!.videoWidth || this.video!.width,\n this.video!.videoHeight || this.video!.height\n )\n\n var context = canvas.getContext('2d', {\n willReadFrequently: true\n })\n\n const detect = async () => {\n try {\n if (!this.stream?.active) {\n return\n }\n\n context!.drawImage(this.video!, 0, 0, canvas.width, canvas.height)\n const imageData = context!.getImageData(0, 0, canvas.width, canvas.height)\n const symbols = await scanImageData(imageData)\n const result = symbols[0]?.decode()\n\n if (result) {\n this.stopScan()\n\n var input = this.input\n input.focus()\n this.value = input.value = String(result)\n\n if (!this.withoutEnter) {\n input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter' }))\n }\n } else {\n requestAnimationFrame(async () => await detect())\n }\n } catch (e) {\n console.warn(e)\n this.stopScan()\n }\n }\n\n await detect()\n }\n } catch (err) {\n /*\n * 1. stream device 문제로 예외 발생한 경우.\n * 2. 뒤로가기 등으로 popup이 종료된 경우에도 NotFoundException: Video stream has ended before any code could be detected. 이 발생한다.\n */\n console.warn(err)\n }\n }\n\n stopScan() {\n if (this.video) {\n this.video.pause()\n this.video.srcObject = null\n }\n\n if (this.popup) {\n this.popup.close()\n this.popup = null\n }\n\n this.stream?.getTracks().forEach(track => track.stop())\n }\n}\n"]}
@@ -27,6 +27,11 @@ export declare class OxInputFile extends OxFormField {
27
27
  static styles: import("lit").CSSResult[];
28
28
  multiple?: boolean;
29
29
  accept?: string;
30
+ /**
31
+ * The phone's camera opens at once - 'environment' for the back camera, 'user' for the front. Without it the phone
32
+ * offers its own choice: take a photo, the photo library, or a file.
33
+ */
34
+ capture?: 'environment' | 'user';
30
35
  /** The drop box's icon (upload when not given); in the quiet look, shown before the attach word only when given. */
31
36
  icon?: string;
32
37
  label?: string;
@@ -5,6 +5,7 @@ import { __decorate } from "tslib";
5
5
  import '@material/web/icon/icon.js';
6
6
  import { css, html, nothing } from 'lit';
7
7
  import { customElement, property, query, state } from 'lit/decorators.js';
8
+ import { ifDefined } from 'lit/directives/if-defined.js';
8
9
  import { i18next } from '@operato/i18n';
9
10
  import { actionHeight, ButtonStyles, ScrollbarStyles } from '@operato/styles';
10
11
  import { FileDropHelper, fileUploadLimits, narrowLimits, sortPickedFiles } from '@operato/utils';
@@ -218,7 +219,7 @@ let OxInputFile = class OxInputFile extends OxFormField {
218
219
  accept=${this.accept || ''}
219
220
  ?multiple=${this.multiple}
220
221
  hidden
221
- capture="environment"
222
+ capture=${ifDefined(this.capture)}
222
223
  @change=${(e) => this._onChangeValue(e)}
223
224
  ?disabled=${this.disabled || this.readonly}
224
225
  />`;
@@ -433,6 +434,9 @@ __decorate([
433
434
  __decorate([
434
435
  property({ type: String })
435
436
  ], OxInputFile.prototype, "accept", void 0);
437
+ __decorate([
438
+ property({ type: String })
439
+ ], OxInputFile.prototype, "capture", void 0);
436
440
  __decorate([
437
441
  property({ type: String })
438
442
  ], OxInputFile.prototype, "icon", void 0);
@@ -1 +1 @@
1
- {"version":3,"file":"ox-input-file.js","sourceRoot":"","sources":["../../src/ox-input-file.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @license Copyright © HatioLab Inc. All rights reserved.\n */\n\nimport '@material/web/icon/icon.js'\n\nimport { css, html, nothing, PropertyValues } from 'lit'\nimport { customElement, property, query, state } from 'lit/decorators.js'\n\nimport { i18next } from '@operato/i18n'\nimport { actionHeight, ButtonStyles, ScrollbarStyles } from '@operato/styles'\nimport { FileDropHelper, FileRefusal, FileUploadLimits, fileUploadLimits, narrowLimits, sortPickedFiles } from '@operato/utils'\n\nimport { OxFormField } from './ox-form-field.js'\nimport { InputStyles } from './input-styles.js'\n\nconst megabytes = (bytes: number) =>\n bytes >= 1048576 ? `${Math.round((bytes / 1048576) * 10) / 10}MB` : `${Math.max(1, Math.round(bytes / 1024))}KB`\n\n/**\n * Picks files — the one picker for every screen.\n *\n * Two looks:\n * (default) a drop box, for a screen where the file is the subject (an import, an attachment gallery)\n * quiet text buttons only — 「첨부」, then the picked name with 「바꾸기」「빼기」 — for a screen that is\n * read first (a 360 section, a form row). The drop area is `dropTarget` (the section) and\n * shows only while a file is dragged over it.\n *\n * Both refuse on the spot what the server would refuse after the upload: a type outside `accept`\n * (a drop skips the native filter), a file over the server's size limit, more files than it takes.\n * The limits are the server's own numbers (`fileUploadLimits` in @operato/utils), never written here.\n *\n * The owner uploads; while it does, it sets `progress` (0‒1) and the picker shows it.\n *\n * A panel with its own look styles the quiet buttons through `::part(attach | replace | remove)` and puts its\n * own icon in the attach button through `slot=\"icon\"`. File names are shown as written.\n */\n@customElement('ox-input-file')\nexport class OxInputFile extends OxFormField {\n static styles = [\n InputStyles,\n ScrollbarStyles,\n ButtonStyles,\n css`\n :host(:not([quiet])) {\n display: flex;\n flex-direction: column;\n border-radius: var(--md-sys-shape-corner-small);\n align-items: center;\n justify-content: center;\n padding: calc(var(--ox-input-gap) * 2);\n min-height: 100px;\n\n border: 1px dashed var(--md-sys-color-outline-variant);\n background-color: var(--md-sys-color-surface);\n font-size: var(--md-sys-typescale-label-large-size, 0.875rem);\n }\n :host(:not([quiet])) > span {\n color: var(--md-sys-color-primary);\n font-weight: var(--md-sys-typescale-label-medium-weight, var(--md-ref-typeface-weight-medium, 500));\n }\n\n :host(:not([quiet])) > md-icon {\n color: var(--md-sys-color-primary);\n --md-icon-size: var(--file-uploader-icon-size, 36px);\n }\n\n :host(.candrop:not([quiet])) {\n background-color: var(--md-sys-color-primary-fixed);\n border: 1px dashed var(--md-sys-color-primary-fixed-dim);\n }\n\n #input-file {\n display: none;\n }\n\n label {\n position: relative;\n width: auto;\n border: none;\n\n padding: var(--file-uploader-label-padding);\n border-radius: var(--file-uploader-label-border-radius);\n font-size: var(--md-sys-typescale-label-large-size, 0.875rem);\n color: var(--md-sys-color-on-surface-variant);\n }\n\n ul {\n max-width: 500px;\n width: 100%;\n list-style: none;\n margin: 5px 0 0 0;\n padding: 0;\n max-height: 46px;\n overflow: auto;\n background-color: var(--md-sys-color-on-primary);\n }\n li {\n text-align: left;\n\n padding: 3px 5px 2px 5px;\n border-bottom: var(--file-uploader-li-border-bottom);\n font-size: var(--md-sys-typescale-label-large-size, 0.875rem);\n color: var(--md-sys-color-on-primary-container);\n display: flex;\n align-items: center;\n }\n li md-icon {\n cursor: pointer;\n font-size: var(--icon-size-small);\n margin-left: auto;\n }\n li md-icon:hover,\n li md-icon:active {\n color: var(--md-sys-color-error);\n }\n\n /* ── quiet ── no box: the words are the control. */\n /* --ox-actions-align: a section heading's actions slot says end, and the refusal line follows it. */\n :host([quiet]) {\n display: inline-flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: var(--ox-actions-align, start);\n gap: var(--spacing-small, 4px);\n /* The name reads at the size of what is around it (a form row, a heading); the buttons keep theirs. */\n font-size: inherit;\n color: var(--md-sys-color-on-surface);\n border-radius: var(--md-sys-shape-corner-small, 4px);\n }\n\n /*\n * A page action starts the pick: the picker has no box of its own, so an empty one takes no room (no gap in\n * its parent's column) and only its refusal or progress line appears, in the parent's flow.\n */\n :host([quiet][outside-trigger]) {\n display: contents;\n }\n\n :host([quiet].candrop) {\n outline: 1px dashed var(--md-sys-color-primary);\n outline-offset: 2px;\n }\n\n .picked {\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-small, 4px);\n min-width: 0;\n }\n\n .picked .name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n max-width: var(--ox-input-file-name-width, 24em);\n }\n\n .picked .size,\n .progress {\n color: var(--md-sys-color-on-surface-variant);\n font-variant-numeric: tabular-nums;\n }\n\n /* A place that is tapped with a thumb (a phone form) raises it; a heading or a row keeps the cell height. */\n :host([quiet]) button {\n height: var(--ox-input-file-action-height, ${actionHeight('--cell-action-height')});\n }\n\n .picked md-icon {\n --md-icon-size: 16px;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n .picked img {\n width: 20px;\n height: 20px;\n object-fit: cover;\n border-radius: 2px;\n }\n\n /* What it refused reads as an error sentence does everywhere (page-standard §7: 11.5, --ui-crit), whatever the place's size. */\n .refusal {\n flex-basis: 100%;\n font-size: var(--ui-t-small, 11.5px);\n text-align: var(--ox-actions-align, start);\n color: var(--ui-crit, var(--md-sys-color-error));\n }\n `\n ]\n\n @property({ type: Boolean }) multiple?: boolean\n @property({ type: String }) accept?: string\n /** The drop box's icon (upload when not given); in the quiet look, shown before the attach word only when given. */\n @property({ type: String }) icon?: string\n @property({ type: String }) label?: string\n @property({ type: String }) description?: string\n @property({ type: Boolean, reflect: true, attribute: 'hide-filelist' })\n hideFileList: boolean = false\n @property({ type: Boolean, reflect: true, attribute: 'attach-filelist' })\n attachFileList: boolean = false\n\n /** Text buttons instead of the drop box — for a screen that is read first. */\n @property({ type: Boolean, reflect: true }) quiet: boolean = false\n\n /** Where a file may be dropped. The picker itself when not given; a 360 passes its section. */\n @property({ attribute: false }) dropTarget?: HTMLElement\n\n /**\n * The owner's own button (a page action) calls `pick()`: the picker draws no button of its own, only what it\n * refused and the upload progress, where the owner placed it.\n */\n @property({ type: Boolean, reflect: true, attribute: 'outside-trigger' }) outsideTrigger: boolean = false\n\n /**\n * A screen's own limits, handed down by the server (an AI mode's attachment policy) — never written on the screen.\n * The picker uses the tighter of these and the server's upload limits.\n */\n @property({ attribute: false }) limits?: FileUploadLimits\n\n /** The file is read in the browser and never uploaded: the server's upload limits do not apply (accept still does). */\n @property({ type: Boolean, reflect: true }) local: boolean = false\n\n /** Upload progress 0‒1, set by the owner while it uploads; the buttons wait meanwhile. */\n @property({ type: Number }) progress?: number\n\n @query('#input-file') fileInput!: HTMLInputElement\n\n @state() private refusals: FileRefusal[] = []\n @state() private refusalLimits: { maxFileSize?: number; maxFiles?: number } = {}\n\n private dropArea?: HTMLElement\n private undoDrop?: () => void\n private previews = new Map<File, string>()\n\n private onFileDrop = (e: Event) => {\n if (this.disabled || this.readonly || this.uploading) {\n return\n }\n\n const files = (e as CustomEvent).detail as File[]\n this.take(this.multiple ? files : files.slice(0, 1))\n }\n\n private get uploading() {\n return typeof this.progress === 'number'\n }\n\n render() {\n return this.quiet ? this.renderQuiet() : this.renderBox()\n }\n\n private renderInput() {\n return html`<input\n id=\"input-file\"\n type=\"file\"\n accept=${this.accept || ''}\n ?multiple=${this.multiple}\n hidden\n capture=\"environment\"\n @change=${(e: Event) => this._onChangeValue(e)}\n ?disabled=${this.disabled || this.readonly}\n />`\n }\n\n private renderBox() {\n var files: File[] = this.value || []\n\n return html`\n <md-icon>${this.icon || 'upload'}</md-icon>\n\n <span>${this.description || i18next.t('text.drop files here', { defaultValue: 'drop files here!' })}</span>\n\n ${this.renderInput()}\n\n <label for=\"input-file\">${this.label || i18next.t('label.select files', { defaultValue: 'select files' })}</label>\n\n ${!this.hideFileList && files?.length\n ? html` <ul>\n ${files.map(\n file => html`\n <li>\n - ${file.name}\n <md-icon\n @click=${(e: Event) => {\n if (this.disabled) {\n return\n }\n this.removeFile(file)\n }}\n >delete_outline</md-icon\n >\n </li>\n `\n )}\n </ul>`\n : html``}\n ${this.renderRefusals()} ${this.renderProgress()}\n `\n }\n\n private renderQuiet() {\n const files: File[] = this.value || []\n const busy = this.disabled || this.readonly || this.uploading\n const shown = this.hideFileList ? [] : files\n\n if (this.outsideTrigger) {\n return html`${this.renderInput()} ${this.renderProgress()} ${this.renderRefusals()}`\n }\n\n return html`\n ${this.renderInput()}\n ${shown.map(\n file => html`<span class=\"picked\">\n ${this.renderKind(file)}\n <span class=\"name\" title=${file.name}>${file.name}</span>\n <span class=\"size\">${megabytes(file.size)}</span>\n ${!this.multiple\n ? html`<button type=\"button\" part=\"replace\" quiet small ?disabled=${busy} @click=${() => this.pick()}>\n ${i18next.t('label.replace file', { defaultValue: 'replace' })}\n </button>`\n : nothing}\n <button type=\"button\" part=\"remove\" quiet small danger ?disabled=${busy} @click=${() => this.removeFile(file)}>\n ${i18next.t('label.remove file', { defaultValue: 'remove' })}\n </button>\n </span>`\n )}\n ${this.multiple || !shown.length\n ? html`<button type=\"button\" part=\"attach\" quiet small ?disabled=${busy} @click=${() => this.pick()}>\n <slot name=\"icon\">${this.icon ? html`<md-icon>${this.icon}</md-icon>` : nothing}</slot>${this.label || i18next.t('label.attach file', { defaultValue: 'attach' })}\n </button>`\n : nothing}\n ${this.renderProgress()} ${this.renderRefusals()}\n `\n }\n\n /* A picture shows itself; anything else is told by its kind. */\n private renderKind(file: File) {\n const preview = this.previews.get(file)\n if (preview) return html`<img src=${preview} alt=\"\" />`\n\n const icon = file.type === 'application/pdf' ? 'picture_as_pdf' : 'draft'\n return html`<md-icon>${icon}</md-icon>`\n }\n\n private renderProgress() {\n if (!this.uploading) return nothing\n\n const percent = Math.round(Math.min(1, Math.max(0, this.progress!)) * 100)\n return html`<span class=\"progress\" role=\"status\">\n ${i18next.t('text.uploading file', { percent, defaultValue: `uploading ${percent}%` })}\n </span>`\n }\n\n private renderRefusals() {\n if (!this.refusals.length) return nothing\n\n const { maxFileSize, maxFiles } = this.refusalLimits\n return html`<div class=\"refusal\" role=\"alert\">\n ${this.refusals.map(({ file, reason }) =>\n reason === 'type'\n ? i18next.t('text.file refused for type', { name: file.name, defaultValue: `${file.name}: not a type taken here` })\n : reason === 'size'\n ? i18next.t('text.file refused for size', {\n name: file.name,\n limit: megabytes(maxFileSize!),\n defaultValue: `${file.name}: over ${megabytes(maxFileSize!)}`\n })\n : i18next.t('text.file refused for count', {\n name: file.name,\n limit: maxFiles,\n defaultValue: `${file.name}: ${maxFiles} files at most`\n })\n ).join(' · ')}\n </div>`\n }\n\n updated(changes: PropertyValues<this>) {\n if (changes.has('value') && !(this.value instanceof Array)) {\n this.value = !this.value ? [] : [this.value]\n }\n\n if (changes.has('value')) {\n this.syncPreviews()\n }\n\n if (changes.has('dropTarget')) {\n this.setDropArea()\n }\n }\n\n connectedCallback() {\n super.connectedCallback()\n if (this.hasUpdated) this.setDropArea()\n }\n\n firstUpdated() {\n this.setDropArea()\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n\n this.undoDrop?.()\n this.dropArea?.removeEventListener('file-drop', this.onFileDrop)\n this.undoDrop = this.dropArea = undefined\n\n this.previews.forEach(url => URL.revokeObjectURL(url))\n this.previews.clear()\n }\n\n private setDropArea() {\n const area: HTMLElement = this.dropTarget || (this as HTMLElement)\n if (area === this.dropArea) return\n\n this.undoDrop?.()\n this.dropArea?.removeEventListener('file-drop', this.onFileDrop)\n\n this.dropArea = area\n this.undoDrop = FileDropHelper.set(area)\n area.addEventListener('file-drop', this.onFileDrop)\n }\n\n private syncPreviews() {\n const files: File[] = this.value || []\n\n this.previews.forEach((url, file) => {\n if (!files.includes(file)) {\n URL.revokeObjectURL(url)\n this.previews.delete(file)\n }\n })\n\n if (!this.quiet) return\n\n files.forEach(file => {\n if (!this.previews.has(file) && file.type?.startsWith('image/')) {\n this.previews.set(file, URL.createObjectURL(file))\n }\n })\n }\n\n get files() {\n return this.value\n }\n\n /** Opens the file dialog — for an owner whose own button starts the pick. */\n pick() {\n if (this.disabled || this.readonly || this.uploading) return\n this.fileInput?.click()\n }\n\n reset() {\n if (this.fileInput) this.fileInput.value = ''\n this.value = []\n this.refusals = []\n\n this._notifyChange()\n }\n\n private removeFile(file: File) {\n const files: File[] = this.value || []\n\n this.value = files.filter(one => one !== file)\n this.refusals = []\n this._notifyChange()\n }\n\n private async take(picked: File[]) {\n const files: File[] = this.value || []\n const keep = this.attachFileList\n const limits = this.local ? narrowLimits(this.limits) : narrowLimits(this.limits, await fileUploadLimits())\n\n const { taken, refused } = sortPickedFiles(picked, {\n accept: this.accept,\n limits,\n already: keep ? files.length : 0\n })\n\n this.refusals = refused\n this.refusalLimits = limits\n\n if (!taken.length) return\n\n this.value = keep ? [...files, ...taken] : taken\n this._notifyChange()\n }\n\n _onChangeValue(e: Event) {\n const fileInput = e.currentTarget as HTMLInputElement\n const picked = Array.from(fileInput.files as FileList)\n\n /* The same file picked again must still raise change. */\n fileInput.value = ''\n\n this.take(picked)\n }\n\n _notifyChange() {\n this.dispatchEvent(\n new CustomEvent('change', {\n bubbles: true,\n composed: true,\n detail: this.value\n })\n )\n }\n}\n"]}
1
+ {"version":3,"file":"ox-input-file.js","sourceRoot":"","sources":["../../src/ox-input-file.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @license Copyright © HatioLab Inc. All rights reserved.\n */\n\nimport '@material/web/icon/icon.js'\n\nimport { css, html, nothing, PropertyValues } from 'lit'\nimport { customElement, property, query, state } from 'lit/decorators.js'\nimport { ifDefined } from 'lit/directives/if-defined.js'\n\nimport { i18next } from '@operato/i18n'\nimport { actionHeight, ButtonStyles, ScrollbarStyles } from '@operato/styles'\nimport { FileDropHelper, FileRefusal, FileUploadLimits, fileUploadLimits, narrowLimits, sortPickedFiles } from '@operato/utils'\n\nimport { OxFormField } from './ox-form-field.js'\nimport { InputStyles } from './input-styles.js'\n\nconst megabytes = (bytes: number) =>\n bytes >= 1048576 ? `${Math.round((bytes / 1048576) * 10) / 10}MB` : `${Math.max(1, Math.round(bytes / 1024))}KB`\n\n/**\n * Picks files — the one picker for every screen.\n *\n * Two looks:\n * (default) a drop box, for a screen where the file is the subject (an import, an attachment gallery)\n * quiet text buttons only — 「첨부」, then the picked name with 「바꾸기」「빼기」 — for a screen that is\n * read first (a 360 section, a form row). The drop area is `dropTarget` (the section) and\n * shows only while a file is dragged over it.\n *\n * Both refuse on the spot what the server would refuse after the upload: a type outside `accept`\n * (a drop skips the native filter), a file over the server's size limit, more files than it takes.\n * The limits are the server's own numbers (`fileUploadLimits` in @operato/utils), never written here.\n *\n * The owner uploads; while it does, it sets `progress` (0‒1) and the picker shows it.\n *\n * A panel with its own look styles the quiet buttons through `::part(attach | replace | remove)` and puts its\n * own icon in the attach button through `slot=\"icon\"`. File names are shown as written.\n */\n@customElement('ox-input-file')\nexport class OxInputFile extends OxFormField {\n static styles = [\n InputStyles,\n ScrollbarStyles,\n ButtonStyles,\n css`\n :host(:not([quiet])) {\n display: flex;\n flex-direction: column;\n border-radius: var(--md-sys-shape-corner-small);\n align-items: center;\n justify-content: center;\n padding: calc(var(--ox-input-gap) * 2);\n min-height: 100px;\n\n border: 1px dashed var(--md-sys-color-outline-variant);\n background-color: var(--md-sys-color-surface);\n font-size: var(--md-sys-typescale-label-large-size, 0.875rem);\n }\n :host(:not([quiet])) > span {\n color: var(--md-sys-color-primary);\n font-weight: var(--md-sys-typescale-label-medium-weight, var(--md-ref-typeface-weight-medium, 500));\n }\n\n :host(:not([quiet])) > md-icon {\n color: var(--md-sys-color-primary);\n --md-icon-size: var(--file-uploader-icon-size, 36px);\n }\n\n :host(.candrop:not([quiet])) {\n background-color: var(--md-sys-color-primary-fixed);\n border: 1px dashed var(--md-sys-color-primary-fixed-dim);\n }\n\n #input-file {\n display: none;\n }\n\n label {\n position: relative;\n width: auto;\n border: none;\n\n padding: var(--file-uploader-label-padding);\n border-radius: var(--file-uploader-label-border-radius);\n font-size: var(--md-sys-typescale-label-large-size, 0.875rem);\n color: var(--md-sys-color-on-surface-variant);\n }\n\n ul {\n max-width: 500px;\n width: 100%;\n list-style: none;\n margin: 5px 0 0 0;\n padding: 0;\n max-height: 46px;\n overflow: auto;\n background-color: var(--md-sys-color-on-primary);\n }\n li {\n text-align: left;\n\n padding: 3px 5px 2px 5px;\n border-bottom: var(--file-uploader-li-border-bottom);\n font-size: var(--md-sys-typescale-label-large-size, 0.875rem);\n color: var(--md-sys-color-on-primary-container);\n display: flex;\n align-items: center;\n }\n li md-icon {\n cursor: pointer;\n font-size: var(--icon-size-small);\n margin-left: auto;\n }\n li md-icon:hover,\n li md-icon:active {\n color: var(--md-sys-color-error);\n }\n\n /* ── quiet ── no box: the words are the control. */\n /* --ox-actions-align: a section heading's actions slot says end, and the refusal line follows it. */\n :host([quiet]) {\n display: inline-flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: var(--ox-actions-align, start);\n gap: var(--spacing-small, 4px);\n /* The name reads at the size of what is around it (a form row, a heading); the buttons keep theirs. */\n font-size: inherit;\n color: var(--md-sys-color-on-surface);\n border-radius: var(--md-sys-shape-corner-small, 4px);\n }\n\n /*\n * A page action starts the pick: the picker has no box of its own, so an empty one takes no room (no gap in\n * its parent's column) and only its refusal or progress line appears, in the parent's flow.\n */\n :host([quiet][outside-trigger]) {\n display: contents;\n }\n\n :host([quiet].candrop) {\n outline: 1px dashed var(--md-sys-color-primary);\n outline-offset: 2px;\n }\n\n .picked {\n display: inline-flex;\n align-items: center;\n gap: var(--spacing-small, 4px);\n min-width: 0;\n }\n\n .picked .name {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n max-width: var(--ox-input-file-name-width, 24em);\n }\n\n .picked .size,\n .progress {\n color: var(--md-sys-color-on-surface-variant);\n font-variant-numeric: tabular-nums;\n }\n\n /* A place that is tapped with a thumb (a phone form) raises it; a heading or a row keeps the cell height. */\n :host([quiet]) button {\n height: var(--ox-input-file-action-height, ${actionHeight('--cell-action-height')});\n }\n\n .picked md-icon {\n --md-icon-size: 16px;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n .picked img {\n width: 20px;\n height: 20px;\n object-fit: cover;\n border-radius: 2px;\n }\n\n /* What it refused reads as an error sentence does everywhere (page-standard §7: 11.5, --ui-crit), whatever the place's size. */\n .refusal {\n flex-basis: 100%;\n font-size: var(--ui-t-small, 11.5px);\n text-align: var(--ox-actions-align, start);\n color: var(--ui-crit, var(--md-sys-color-error));\n }\n `\n ]\n\n @property({ type: Boolean }) multiple?: boolean\n @property({ type: String }) accept?: string\n /**\n * The phone's camera opens at once - 'environment' for the back camera, 'user' for the front. Without it the phone\n * offers its own choice: take a photo, the photo library, or a file.\n */\n @property({ type: String }) capture?: 'environment' | 'user'\n /** The drop box's icon (upload when not given); in the quiet look, shown before the attach word only when given. */\n @property({ type: String }) icon?: string\n @property({ type: String }) label?: string\n @property({ type: String }) description?: string\n @property({ type: Boolean, reflect: true, attribute: 'hide-filelist' })\n hideFileList: boolean = false\n @property({ type: Boolean, reflect: true, attribute: 'attach-filelist' })\n attachFileList: boolean = false\n\n /** Text buttons instead of the drop box — for a screen that is read first. */\n @property({ type: Boolean, reflect: true }) quiet: boolean = false\n\n /** Where a file may be dropped. The picker itself when not given; a 360 passes its section. */\n @property({ attribute: false }) dropTarget?: HTMLElement\n\n /**\n * The owner's own button (a page action) calls `pick()`: the picker draws no button of its own, only what it\n * refused and the upload progress, where the owner placed it.\n */\n @property({ type: Boolean, reflect: true, attribute: 'outside-trigger' }) outsideTrigger: boolean = false\n\n /**\n * A screen's own limits, handed down by the server (an AI mode's attachment policy) — never written on the screen.\n * The picker uses the tighter of these and the server's upload limits.\n */\n @property({ attribute: false }) limits?: FileUploadLimits\n\n /** The file is read in the browser and never uploaded: the server's upload limits do not apply (accept still does). */\n @property({ type: Boolean, reflect: true }) local: boolean = false\n\n /** Upload progress 0‒1, set by the owner while it uploads; the buttons wait meanwhile. */\n @property({ type: Number }) progress?: number\n\n @query('#input-file') fileInput!: HTMLInputElement\n\n @state() private refusals: FileRefusal[] = []\n @state() private refusalLimits: { maxFileSize?: number; maxFiles?: number } = {}\n\n private dropArea?: HTMLElement\n private undoDrop?: () => void\n private previews = new Map<File, string>()\n\n private onFileDrop = (e: Event) => {\n if (this.disabled || this.readonly || this.uploading) {\n return\n }\n\n const files = (e as CustomEvent).detail as File[]\n this.take(this.multiple ? files : files.slice(0, 1))\n }\n\n private get uploading() {\n return typeof this.progress === 'number'\n }\n\n render() {\n return this.quiet ? this.renderQuiet() : this.renderBox()\n }\n\n private renderInput() {\n return html`<input\n id=\"input-file\"\n type=\"file\"\n accept=${this.accept || ''}\n ?multiple=${this.multiple}\n hidden\n capture=${ifDefined(this.capture)}\n @change=${(e: Event) => this._onChangeValue(e)}\n ?disabled=${this.disabled || this.readonly}\n />`\n }\n\n private renderBox() {\n var files: File[] = this.value || []\n\n return html`\n <md-icon>${this.icon || 'upload'}</md-icon>\n\n <span>${this.description || i18next.t('text.drop files here', { defaultValue: 'drop files here!' })}</span>\n\n ${this.renderInput()}\n\n <label for=\"input-file\">${this.label || i18next.t('label.select files', { defaultValue: 'select files' })}</label>\n\n ${!this.hideFileList && files?.length\n ? html` <ul>\n ${files.map(\n file => html`\n <li>\n - ${file.name}\n <md-icon\n @click=${(e: Event) => {\n if (this.disabled) {\n return\n }\n this.removeFile(file)\n }}\n >delete_outline</md-icon\n >\n </li>\n `\n )}\n </ul>`\n : html``}\n ${this.renderRefusals()} ${this.renderProgress()}\n `\n }\n\n private renderQuiet() {\n const files: File[] = this.value || []\n const busy = this.disabled || this.readonly || this.uploading\n const shown = this.hideFileList ? [] : files\n\n if (this.outsideTrigger) {\n return html`${this.renderInput()} ${this.renderProgress()} ${this.renderRefusals()}`\n }\n\n return html`\n ${this.renderInput()}\n ${shown.map(\n file => html`<span class=\"picked\">\n ${this.renderKind(file)}\n <span class=\"name\" title=${file.name}>${file.name}</span>\n <span class=\"size\">${megabytes(file.size)}</span>\n ${!this.multiple\n ? html`<button type=\"button\" part=\"replace\" quiet small ?disabled=${busy} @click=${() => this.pick()}>\n ${i18next.t('label.replace file', { defaultValue: 'replace' })}\n </button>`\n : nothing}\n <button type=\"button\" part=\"remove\" quiet small danger ?disabled=${busy} @click=${() => this.removeFile(file)}>\n ${i18next.t('label.remove file', { defaultValue: 'remove' })}\n </button>\n </span>`\n )}\n ${this.multiple || !shown.length\n ? html`<button type=\"button\" part=\"attach\" quiet small ?disabled=${busy} @click=${() => this.pick()}>\n <slot name=\"icon\">${this.icon ? html`<md-icon>${this.icon}</md-icon>` : nothing}</slot>${this.label || i18next.t('label.attach file', { defaultValue: 'attach' })}\n </button>`\n : nothing}\n ${this.renderProgress()} ${this.renderRefusals()}\n `\n }\n\n /* A picture shows itself; anything else is told by its kind. */\n private renderKind(file: File) {\n const preview = this.previews.get(file)\n if (preview) return html`<img src=${preview} alt=\"\" />`\n\n const icon = file.type === 'application/pdf' ? 'picture_as_pdf' : 'draft'\n return html`<md-icon>${icon}</md-icon>`\n }\n\n private renderProgress() {\n if (!this.uploading) return nothing\n\n const percent = Math.round(Math.min(1, Math.max(0, this.progress!)) * 100)\n return html`<span class=\"progress\" role=\"status\">\n ${i18next.t('text.uploading file', { percent, defaultValue: `uploading ${percent}%` })}\n </span>`\n }\n\n private renderRefusals() {\n if (!this.refusals.length) return nothing\n\n const { maxFileSize, maxFiles } = this.refusalLimits\n return html`<div class=\"refusal\" role=\"alert\">\n ${this.refusals.map(({ file, reason }) =>\n reason === 'type'\n ? i18next.t('text.file refused for type', { name: file.name, defaultValue: `${file.name}: not a type taken here` })\n : reason === 'size'\n ? i18next.t('text.file refused for size', {\n name: file.name,\n limit: megabytes(maxFileSize!),\n defaultValue: `${file.name}: over ${megabytes(maxFileSize!)}`\n })\n : i18next.t('text.file refused for count', {\n name: file.name,\n limit: maxFiles,\n defaultValue: `${file.name}: ${maxFiles} files at most`\n })\n ).join(' · ')}\n </div>`\n }\n\n updated(changes: PropertyValues<this>) {\n if (changes.has('value') && !(this.value instanceof Array)) {\n this.value = !this.value ? [] : [this.value]\n }\n\n if (changes.has('value')) {\n this.syncPreviews()\n }\n\n if (changes.has('dropTarget')) {\n this.setDropArea()\n }\n }\n\n connectedCallback() {\n super.connectedCallback()\n if (this.hasUpdated) this.setDropArea()\n }\n\n firstUpdated() {\n this.setDropArea()\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n\n this.undoDrop?.()\n this.dropArea?.removeEventListener('file-drop', this.onFileDrop)\n this.undoDrop = this.dropArea = undefined\n\n this.previews.forEach(url => URL.revokeObjectURL(url))\n this.previews.clear()\n }\n\n private setDropArea() {\n const area: HTMLElement = this.dropTarget || (this as HTMLElement)\n if (area === this.dropArea) return\n\n this.undoDrop?.()\n this.dropArea?.removeEventListener('file-drop', this.onFileDrop)\n\n this.dropArea = area\n this.undoDrop = FileDropHelper.set(area)\n area.addEventListener('file-drop', this.onFileDrop)\n }\n\n private syncPreviews() {\n const files: File[] = this.value || []\n\n this.previews.forEach((url, file) => {\n if (!files.includes(file)) {\n URL.revokeObjectURL(url)\n this.previews.delete(file)\n }\n })\n\n if (!this.quiet) return\n\n files.forEach(file => {\n if (!this.previews.has(file) && file.type?.startsWith('image/')) {\n this.previews.set(file, URL.createObjectURL(file))\n }\n })\n }\n\n get files() {\n return this.value\n }\n\n /** Opens the file dialog — for an owner whose own button starts the pick. */\n pick() {\n if (this.disabled || this.readonly || this.uploading) return\n this.fileInput?.click()\n }\n\n reset() {\n if (this.fileInput) this.fileInput.value = ''\n this.value = []\n this.refusals = []\n\n this._notifyChange()\n }\n\n private removeFile(file: File) {\n const files: File[] = this.value || []\n\n this.value = files.filter(one => one !== file)\n this.refusals = []\n this._notifyChange()\n }\n\n private async take(picked: File[]) {\n const files: File[] = this.value || []\n const keep = this.attachFileList\n const limits = this.local ? narrowLimits(this.limits) : narrowLimits(this.limits, await fileUploadLimits())\n\n const { taken, refused } = sortPickedFiles(picked, {\n accept: this.accept,\n limits,\n already: keep ? files.length : 0\n })\n\n this.refusals = refused\n this.refusalLimits = limits\n\n if (!taken.length) return\n\n this.value = keep ? [...files, ...taken] : taken\n this._notifyChange()\n }\n\n _onChangeValue(e: Event) {\n const fileInput = e.currentTarget as HTMLInputElement\n const picked = Array.from(fileInput.files as FileList)\n\n /* The same file picked again must still raise change. */\n fileInput.value = ''\n\n this.take(picked)\n }\n\n _notifyChange() {\n this.dispatchEvent(\n new CustomEvent('change', {\n bubbles: true,\n composed: true,\n detail: this.value\n })\n )\n }\n}\n"]}
@@ -0,0 +1 @@
1
+ import '../src/ox-input-barcode.js';
@@ -0,0 +1,53 @@
1
+ import { expect, fixture } from '@open-wc/testing';
2
+ import { html } from 'lit';
3
+ import '../src/ox-input-barcode.js';
4
+ /**
5
+ * One Enter is one `change`. Enter says the value at once; the browser would say the same value again when the
6
+ * field loses focus, since Enter kept it from saying it then. A host that looks something up on `change` (the band's
7
+ * ID Lens, ADR-0133) looked it up twice - the second time when another part of the page gave the field its focus back.
8
+ */
9
+ describe('ox-input-barcode change', () => {
10
+ async function field() {
11
+ const element = await fixture(html `<ox-input-barcode></ox-input-barcode>`);
12
+ const input = element.shadowRoot.querySelector('input');
13
+ const said = [];
14
+ element.addEventListener('change', e => said.push(e.detail));
15
+ return { element, input, said };
16
+ }
17
+ const type = (input, text) => {
18
+ input.value = text;
19
+ input.dispatchEvent(new Event('input'));
20
+ };
21
+ const enter = (input) => input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true, cancelable: true }));
22
+ /* What the browser does on blur when the value differs from the one at focus. */
23
+ const leave = (input) => input.dispatchEvent(new Event('change', { bubbles: true }));
24
+ it('Enter says the value once; leaving the field afterwards says nothing more', async () => {
25
+ const { input, said } = await field();
26
+ type(input, 'X-1');
27
+ enter(input);
28
+ leave(input);
29
+ expect(said).to.deep.equal(['X-1']);
30
+ });
31
+ it('Enter again on the same value is asked again - it is a request, not a change', async () => {
32
+ const { input, said } = await field();
33
+ type(input, 'X-1');
34
+ enter(input);
35
+ enter(input);
36
+ expect(said).to.deep.equal(['X-1', 'X-1']);
37
+ });
38
+ it('text changed after Enter and then left is still said on leaving', async () => {
39
+ const { input, said } = await field();
40
+ type(input, 'X-1');
41
+ enter(input);
42
+ type(input, 'X-2');
43
+ leave(input);
44
+ expect(said).to.deep.equal(['X-1', 'X-2']);
45
+ });
46
+ it('text typed and left without Enter is said on leaving, as before', async () => {
47
+ const { input, said } = await field();
48
+ type(input, 'X-3');
49
+ leave(input);
50
+ expect(said).to.deep.equal(['X-3']);
51
+ });
52
+ });
53
+ //# sourceMappingURL=ox-input-barcode.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ox-input-barcode.test.js","sourceRoot":"","sources":["../../test/ox-input-barcode.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { expect, fixture } from '@open-wc/testing'\nimport { html } from 'lit'\nimport '../src/ox-input-barcode.js'\nimport type { OxInputBarcode } from '../src/ox-input-barcode.js'\n\n/**\n * One Enter is one `change`. Enter says the value at once; the browser would say the same value again when the\n * field loses focus, since Enter kept it from saying it then. A host that looks something up on `change` (the band's\n * ID Lens, ADR-0133) looked it up twice - the second time when another part of the page gave the field its focus back.\n */\ndescribe('ox-input-barcode change', () => {\n async function field() {\n const element = await fixture<OxInputBarcode>(html`<ox-input-barcode></ox-input-barcode>`)\n const input = element.shadowRoot!.querySelector('input') as HTMLInputElement\n const said: string[] = []\n element.addEventListener('change', e => said.push((e as CustomEvent).detail))\n return { element, input, said }\n }\n\n const type = (input: HTMLInputElement, text: string) => {\n input.value = text\n input.dispatchEvent(new Event('input'))\n }\n const enter = (input: HTMLInputElement) => input.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true, cancelable: true }))\n /* What the browser does on blur when the value differs from the one at focus. */\n const leave = (input: HTMLInputElement) => input.dispatchEvent(new Event('change', { bubbles: true }))\n\n it('Enter says the value once; leaving the field afterwards says nothing more', async () => {\n const { input, said } = await field()\n type(input, 'X-1')\n enter(input)\n leave(input)\n expect(said).to.deep.equal(['X-1'])\n })\n\n it('Enter again on the same value is asked again - it is a request, not a change', async () => {\n const { input, said } = await field()\n type(input, 'X-1')\n enter(input)\n enter(input)\n expect(said).to.deep.equal(['X-1', 'X-1'])\n })\n\n it('text changed after Enter and then left is still said on leaving', async () => {\n const { input, said } = await field()\n type(input, 'X-1')\n enter(input)\n type(input, 'X-2')\n leave(input)\n expect(said).to.deep.equal(['X-1', 'X-2'])\n })\n\n it('text typed and left without Enter is said on leaving, as before', async () => {\n const { input, said } = await field()\n type(input, 'X-3')\n leave(input)\n expect(said).to.deep.equal(['X-3'])\n })\n})\n"]}