@operato/input 10.29.44 → 10.29.52

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 CHANGED
@@ -3,6 +3,29 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ### [10.29.52](https://github.com/hatiolab/operato/compare/v10.29.51...v10.29.52) (2026-10-25)
7
+
8
+
9
+ ### :bug: Bug Fix
10
+
11
+ * **popup:** a popup never covers the select or point it opens from ([129bffe](https://github.com/hatiolab/operato/commit/129bffe96a3812e3dad0bdb7daae6db307d0afe9))
12
+
13
+
14
+
15
+ ### [10.29.49](https://github.com/hatiolab/operato/compare/v10.29.48...v10.29.49) (2026-10-23)
16
+
17
+
18
+ ### :bug: Bug Fix
19
+
20
+ * **input:** one Enter in the barcode field is one change ([c886102](https://github.com/hatiolab/operato/commit/c886102da6f498814fda3bec2d1c5d4889d6b580))
21
+
22
+
23
+ ### :rocket: New Features
24
+
25
+ * **input:** ox-input-file opens the camera only when asked ([3435011](https://github.com/hatiolab/operato/commit/343501151728012db5771fe9a014ea33a75dae74))
26
+
27
+
28
+
6
29
  ### [10.29.44](https://github.com/hatiolab/operato/compare/v10.29.43...v10.29.44) (2026-10-18)
7
30
 
8
31
  **Note:** Version bump only for package @operato/input
@@ -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"]}
@@ -167,7 +167,7 @@ let OxSelect = class OxSelect extends OxFormField {
167
167
  if (slotContent.length > 0) {
168
168
  const popupList = slotContent.find(content => content instanceof OxPopupList);
169
169
  if (popupList) {
170
- const { left, bottom } = this.getBoundingClientRect();
170
+ const { left, top, bottom } = this.getBoundingClientRect();
171
171
  popupList.value = this.value;
172
172
  popupList.style.width = `${this.offsetWidth}px`;
173
173
  popupList.style.textWrap = 'nowrap';
@@ -194,7 +194,8 @@ let OxSelect = class OxSelect extends OxFormField {
194
194
  document.body.appendChild(popupList);
195
195
  popupList.open({
196
196
  left,
197
- top: bottom
197
+ top: bottom,
198
+ anchorTop: top
198
199
  });
199
200
  }
200
201
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ox-select.js","sourceRoot":"","sources":["../../src/ox-select.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @license Copyright © HatioLab Inc. All rights reserved.\n */\n\nimport '@material/web/icon/icon.js'\nimport '@operato/popup/ox-popup-list.js'\nimport './ox-checkbox.js'\n\nimport { css, html, render, PropertyValues } from 'lit'\nimport { customElement, property, query, state } from 'lit/decorators.js'\n\nimport { OxPopupList } from '@operato/popup'\n\nimport { OxFormField } from './ox-form-field.js'\nimport { InputStyles } from './input-styles.js'\n\n@customElement('ox-select')\nexport class OxSelect extends OxFormField {\n static styles = [\n InputStyles,\n css`\n /* 컴포넌트 조정 지점(L3) — 모두 L2 계약(--ox-input-*)으로 폴백하므로\n 앱은 전역(L2) 또는 이 컴포넌트만(L3) 어느 쪽으로도 통제할 수 있다.\n 고정 리터럴을 남기지 않는다. */\n :host {\n display: block;\n position: relative;\n border-bottom: var(--ox-select-border-bottom, var(--ox-input-underline));\n\n --ox-select-padding: var(--ox-input-padding);\n --ox-select-font: var(--ox-input-font);\n --ox-select-color: var(--ox-input-color);\n --ox-select-icon-color: var(--ox-input-placeholder-color);\n --ox-select-icon-hover-color: var(--ox-input-focus-color);\n --ox-select-icon-size: var(--ox-input-font-size);\n --ox-select-icon-opacity: 0.7;\n }\n\n div {\n width: 100%;\n\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n padding: var(--ox-select-padding);\n font: var(--ox-select-font);\n color: var(--ox-select-color);\n transition: var(--ox-input-transition);\n }\n\n span {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n gap: var(--ox-input-gap);\n }\n\n md-icon {\n --md-icon-size: var(--ox-select-icon-size);\n display: block;\n text-align: right;\n color: var(--ox-select-icon-color);\n opacity: var(--ox-select-icon-opacity);\n transition: var(--ox-input-transition);\n }\n\n div:hover md-icon {\n color: var(--ox-select-icon-hover-color);\n opacity: 1;\n }\n `\n ]\n\n @property({ type: String }) name: string = ''\n @property({ type: String }) placeholder: string = ''\n\n @state() label: string | string[] = ''\n @state() popupContainer: HTMLElement | null = null\n @state() observer: MutationObserver | null = null\n @state() hasLabelSlot: boolean = false\n\n render() {\n // value/label 은 문자열이 아닐 수 있다 (예: select option value 가 숫자 0/1).\n // `(0)?.trim()` 은 0 이 nullish 가 아니라 단락되지 않아 TypeError 가 난다 →\n // 비-문자열 스칼라는 String() 으로 강제, nullish 만 '' 처리.\n const asText = (v: unknown): string =>\n v instanceof Array ? v.join(', ') : typeof v === 'string' ? v.trim() : v == null ? '' : String(v)\n\n const label = asText(this.label) || asText(this.value) || this.placeholder || ''\n\n return html`\n <div @click=${this.expand}>\n <span data-reactive-tooltip> <slot name=\"label\">${label}</slot> </span>\n <md-icon>expand_more</md-icon>\n </div>\n\n <slot></slot>\n `\n }\n\n connectedCallback() {\n super.connectedCallback()\n\n this.setAttribute('tabindex', '0')\n this.addEventListener('keydown', this.handleKeyDown)\n this.addEventListener('click', this.expand)\n this.addEventListener('options-change', this.optionsChanged)\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n\n this.removeEventListener('keydown', this.handleKeyDown)\n this.removeEventListener('click', this.expand)\n this.removeEventListener('options-change', this.optionsChanged)\n }\n\n handleKeyDown(e: KeyboardEvent) {\n e.preventDefault()\n if (e.key === ' ' || e.key === 'Spacebar' || e.key === 'ArrowDown') {\n this.expand()\n }\n }\n\n /** Its list's options arrived or changed: show the label of the value they now hold. */\n private optionsChanged = (e: Event) => {\n e.stopPropagation()\n const popupList = e.target as OxPopupList\n this.label = popupList.getSelectedLabels()\n }\n\n async updated(changes: PropertyValues<this>) {\n if (changes.has('value')) {\n const popupList = (this.popupContainer?.querySelector('ox-popup-list') ||\n this.querySelector('ox-popup-list')) as OxPopupList\n\n if (popupList) {\n popupList.value = this.value\n await this.requestUpdate()\n this.label = popupList.getSelectedLabels()\n }\n }\n }\n\n setOptions(\n options: string[] | { display: string; value: string }[],\n opt: { multiple?: boolean; withSearch?: boolean } = {}\n ) {\n const objOptions = options.map(option => {\n return typeof option == 'string' ? { display: option, value: option } : option\n })\n\n const { multiple, withSearch } = opt || {}\n\n const template = html`\n <ox-popup-list\n align-left\n nowrap\n ?multiple=${multiple}\n attr-selected=${multiple ? 'checked' : ''}\n ?with-search=${withSearch}\n >\n ${multiple\n ? html`<ox-checkbox\n @change=${(e: Event) => {\n const target = e.target as HTMLInputElement\n const options = Array.from(target.parentElement!.querySelectorAll('[option]')).filter(\n option => !option.hasAttribute('hidden')\n )\n options.forEach(option => ((option as HTMLInputElement).checked = target.checked))\n\n this.value = options\n .map(option =>\n (option as HTMLInputElement).checked ? (option as HTMLInputElement).value : undefined\n )\n .filter(Boolean)\n }}\n >set all</ox-checkbox\n >\n ${objOptions.map(\n option => html` <ox-checkbox option value=${option.value}>${option.display}</ox-checkbox> `\n )}`\n : html`${objOptions.map(option => html` <div option value=${option.value}>${option.display}</div> `)}`}\n </ox-popup-list>\n `\n\n render(template, this)\n }\n\n expand() {\n if (this.disabled) {\n return\n }\n\n const slotContent = (this.renderRoot.querySelector('slot:not([name])') as HTMLSlotElement)?.assignedNodes() || []\n\n if (slotContent.length > 0) {\n const popupList = slotContent.find(content => content instanceof OxPopupList) as OxPopupList\n\n if (popupList) {\n const { left, bottom } = this.getBoundingClientRect()\n\n popupList.value = this.value\n popupList.style.width = `${this.offsetWidth}px`\n popupList.style.textWrap = 'nowrap'\n\n const selectHandler = async (e: Event) => {\n this.value = (e as CustomEvent).detail\n\n if (popupList) {\n await this.requestUpdate()\n this.label = popupList.getSelectedLabels()\n }\n }\n popupList.addEventListener('select', selectHandler)\n\n const closeHandler = (e: Event) => {\n /* popup이 close될 때 change 이벤트를 발생시킨다. */\n this.dispatchEvent(\n new CustomEvent('change', {\n bubbles: true,\n composed: true,\n detail: this.value\n })\n )\n popupList.removeEventListener('select', selectHandler)\n popupList.removeEventListener('close', closeHandler)\n\n this.appendChild(popupList)\n }\n popupList.addEventListener('close', closeHandler, { once: true })\n\n document.body.appendChild(popupList)\n\n popupList.open({\n left,\n top: bottom\n })\n }\n }\n }\n}\n"]}
1
+ {"version":3,"file":"ox-select.js","sourceRoot":"","sources":["../../src/ox-select.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * @license Copyright © HatioLab Inc. All rights reserved.\n */\n\nimport '@material/web/icon/icon.js'\nimport '@operato/popup/ox-popup-list.js'\nimport './ox-checkbox.js'\n\nimport { css, html, render, PropertyValues } from 'lit'\nimport { customElement, property, query, state } from 'lit/decorators.js'\n\nimport { OxPopupList } from '@operato/popup'\n\nimport { OxFormField } from './ox-form-field.js'\nimport { InputStyles } from './input-styles.js'\n\n@customElement('ox-select')\nexport class OxSelect extends OxFormField {\n static styles = [\n InputStyles,\n css`\n /* 컴포넌트 조정 지점(L3) — 모두 L2 계약(--ox-input-*)으로 폴백하므로\n 앱은 전역(L2) 또는 이 컴포넌트만(L3) 어느 쪽으로도 통제할 수 있다.\n 고정 리터럴을 남기지 않는다. */\n :host {\n display: block;\n position: relative;\n border-bottom: var(--ox-select-border-bottom, var(--ox-input-underline));\n\n --ox-select-padding: var(--ox-input-padding);\n --ox-select-font: var(--ox-input-font);\n --ox-select-color: var(--ox-input-color);\n --ox-select-icon-color: var(--ox-input-placeholder-color);\n --ox-select-icon-hover-color: var(--ox-input-focus-color);\n --ox-select-icon-size: var(--ox-input-font-size);\n --ox-select-icon-opacity: 0.7;\n }\n\n div {\n width: 100%;\n\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n padding: var(--ox-select-padding);\n font: var(--ox-select-font);\n color: var(--ox-select-color);\n transition: var(--ox-input-transition);\n }\n\n span {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n gap: var(--ox-input-gap);\n }\n\n md-icon {\n --md-icon-size: var(--ox-select-icon-size);\n display: block;\n text-align: right;\n color: var(--ox-select-icon-color);\n opacity: var(--ox-select-icon-opacity);\n transition: var(--ox-input-transition);\n }\n\n div:hover md-icon {\n color: var(--ox-select-icon-hover-color);\n opacity: 1;\n }\n `\n ]\n\n @property({ type: String }) name: string = ''\n @property({ type: String }) placeholder: string = ''\n\n @state() label: string | string[] = ''\n @state() popupContainer: HTMLElement | null = null\n @state() observer: MutationObserver | null = null\n @state() hasLabelSlot: boolean = false\n\n render() {\n // value/label 은 문자열이 아닐 수 있다 (예: select option value 가 숫자 0/1).\n // `(0)?.trim()` 은 0 이 nullish 가 아니라 단락되지 않아 TypeError 가 난다 →\n // 비-문자열 스칼라는 String() 으로 강제, nullish 만 '' 처리.\n const asText = (v: unknown): string =>\n v instanceof Array ? v.join(', ') : typeof v === 'string' ? v.trim() : v == null ? '' : String(v)\n\n const label = asText(this.label) || asText(this.value) || this.placeholder || ''\n\n return html`\n <div @click=${this.expand}>\n <span data-reactive-tooltip> <slot name=\"label\">${label}</slot> </span>\n <md-icon>expand_more</md-icon>\n </div>\n\n <slot></slot>\n `\n }\n\n connectedCallback() {\n super.connectedCallback()\n\n this.setAttribute('tabindex', '0')\n this.addEventListener('keydown', this.handleKeyDown)\n this.addEventListener('click', this.expand)\n this.addEventListener('options-change', this.optionsChanged)\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n\n this.removeEventListener('keydown', this.handleKeyDown)\n this.removeEventListener('click', this.expand)\n this.removeEventListener('options-change', this.optionsChanged)\n }\n\n handleKeyDown(e: KeyboardEvent) {\n e.preventDefault()\n if (e.key === ' ' || e.key === 'Spacebar' || e.key === 'ArrowDown') {\n this.expand()\n }\n }\n\n /** Its list's options arrived or changed: show the label of the value they now hold. */\n private optionsChanged = (e: Event) => {\n e.stopPropagation()\n const popupList = e.target as OxPopupList\n this.label = popupList.getSelectedLabels()\n }\n\n async updated(changes: PropertyValues<this>) {\n if (changes.has('value')) {\n const popupList = (this.popupContainer?.querySelector('ox-popup-list') ||\n this.querySelector('ox-popup-list')) as OxPopupList\n\n if (popupList) {\n popupList.value = this.value\n await this.requestUpdate()\n this.label = popupList.getSelectedLabels()\n }\n }\n }\n\n setOptions(\n options: string[] | { display: string; value: string }[],\n opt: { multiple?: boolean; withSearch?: boolean } = {}\n ) {\n const objOptions = options.map(option => {\n return typeof option == 'string' ? { display: option, value: option } : option\n })\n\n const { multiple, withSearch } = opt || {}\n\n const template = html`\n <ox-popup-list\n align-left\n nowrap\n ?multiple=${multiple}\n attr-selected=${multiple ? 'checked' : ''}\n ?with-search=${withSearch}\n >\n ${multiple\n ? html`<ox-checkbox\n @change=${(e: Event) => {\n const target = e.target as HTMLInputElement\n const options = Array.from(target.parentElement!.querySelectorAll('[option]')).filter(\n option => !option.hasAttribute('hidden')\n )\n options.forEach(option => ((option as HTMLInputElement).checked = target.checked))\n\n this.value = options\n .map(option =>\n (option as HTMLInputElement).checked ? (option as HTMLInputElement).value : undefined\n )\n .filter(Boolean)\n }}\n >set all</ox-checkbox\n >\n ${objOptions.map(\n option => html` <ox-checkbox option value=${option.value}>${option.display}</ox-checkbox> `\n )}`\n : html`${objOptions.map(option => html` <div option value=${option.value}>${option.display}</div> `)}`}\n </ox-popup-list>\n `\n\n render(template, this)\n }\n\n expand() {\n if (this.disabled) {\n return\n }\n\n const slotContent = (this.renderRoot.querySelector('slot:not([name])') as HTMLSlotElement)?.assignedNodes() || []\n\n if (slotContent.length > 0) {\n const popupList = slotContent.find(content => content instanceof OxPopupList) as OxPopupList\n\n if (popupList) {\n const { left, top, bottom } = this.getBoundingClientRect()\n\n popupList.value = this.value\n popupList.style.width = `${this.offsetWidth}px`\n popupList.style.textWrap = 'nowrap'\n\n const selectHandler = async (e: Event) => {\n this.value = (e as CustomEvent).detail\n\n if (popupList) {\n await this.requestUpdate()\n this.label = popupList.getSelectedLabels()\n }\n }\n popupList.addEventListener('select', selectHandler)\n\n const closeHandler = (e: Event) => {\n /* popup이 close될 때 change 이벤트를 발생시킨다. */\n this.dispatchEvent(\n new CustomEvent('change', {\n bubbles: true,\n composed: true,\n detail: this.value\n })\n )\n popupList.removeEventListener('select', selectHandler)\n popupList.removeEventListener('close', closeHandler)\n\n this.appendChild(popupList)\n }\n popupList.addEventListener('close', closeHandler, { once: true })\n\n document.body.appendChild(popupList)\n\n popupList.open({\n left,\n top: bottom,\n anchorTop: top\n })\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"]}