@operato/input 10.29.36 → 10.29.37

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.
@@ -88,6 +88,11 @@ export class OxFormField extends LitElement {
88
88
  values = [this.value];
89
89
  }
90
90
  values.forEach(value => {
91
+ /* A file goes as itself, name and all; turned to JSON it was "{}". */
92
+ if (value instanceof Blob) {
93
+ formData.append(this.name, value);
94
+ return;
95
+ }
91
96
  formData.append(this.name, typeof value === 'string' ? value : value === undefined || value === null ? '' : JSON.stringify(value));
92
97
  });
93
98
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ox-form-field.js","sourceRoot":"","sources":["../../src/ox-form-field.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement } from 'lit'\nimport { property } from 'lit/decorators.js'\n\n/**\n * 모든 입력 컴포넌트의 기반 계약.\n *\n * ## 상태 계약\n * `disabled` / `readonly` / `required` / `invalid` 는 **attribute 로 reflect** 된다.\n * 그래야 `InputStyles` 의 `:host([disabled])` · `:host([readonly])` · `:host([invalid])` 가\n * **마크업으로 설정했을 때뿐 아니라 프로퍼티로 설정했을 때도** 시각에 반영된다.\n * (reflect 가 없으면 `el.disabled = true` 는 화면에 나타나지 않는다.)\n *\n * ## disabled 와 readonly 의 구분\n * - `disabled`: 상호작용 불가 · 흐리게 · **폼 데이터에 포함되지 않음**(HTML 표준 거동)\n * - `readonly`: 값을 **보고 선택·복사할 수 있으나** 수정 불가 · **폼 데이터에는 포함됨**\n * → 조회 화면을 `disabled` 로 잠그면 흐려지고 값도 전송되지 않으므로 `readonly` 를 쓴다.\n *\n * ## 검증\n * `checkValidity()` 가 `invalid` 를 갱신하고 결과를 반환한다.\n * 컴포넌트별 규칙(형식·범위 등)이 있으면 `validateValue()` 를 오버라이드한다(기본은 `required` 만 검사).\n */\nexport abstract class OxFormField<T = any> extends LitElement {\n @property({ type: String, attribute: true }) name?: string\n @property({ type: Boolean, reflect: true }) disabled?: boolean\n\n /** 값 표시·선택은 되지만 수정 불가. disabled 와 달리 폼 데이터에 포함된다. */\n @property({ type: Boolean, reflect: true }) readonly?: boolean\n\n /** 값이 반드시 있어야 함. checkValidity() 의 기준. */\n @property({ type: Boolean, reflect: true }) required?: boolean\n\n /** 검증 실패 상태. InputStyles 가 error 색으로 표시한다. */\n @property({ type: Boolean, reflect: true }) invalid?: boolean\n\n /** 비어 있을 때 보여줄 안내 문구(컴포넌트가 지원하는 경우). */\n @property({ type: String }) placeholder?: string\n\n @property() value?: T\n\n protected _form: HTMLFormElement | null = null\n protected _formdataEventHandler: EventListener | null = null\n\n override connectedCallback(): void {\n super.connectedCallback()\n\n if (this.name) {\n this._form = this.closest('form')\n if (this._form) {\n this._formdataEventHandler = this.appendFormData.bind(this) as EventListener\n this._form.addEventListener('formdata', this._formdataEventHandler)\n }\n }\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback()\n\n if (this._form) {\n this._form.removeEventListener('formdata', this._formdataEventHandler!)\n this._form = null\n this._formdataEventHandler = null\n }\n }\n\n /**\n * 값이 비었는지 판단. 문자열·배열·nullish 를 다룬다.\n * 숫자 `0` 과 `false` 는 **유효한 값**이므로 비었다고 보지 않는다.\n */\n protected isEmpty(): boolean {\n const value = this.value as unknown\n\n if (value === undefined || value === null) return true\n if (typeof value === 'string') return value.trim().length === 0\n if (Array.isArray(value)) return value.length === 0\n\n return false\n }\n\n /**\n * 자체 검증 규칙. 기본은 `required` 만 검사한다.\n * 컴포넌트별 규칙이 있으면 오버라이드한다.\n */\n protected validateValue(): boolean {\n return !(this.required && this.isEmpty())\n }\n\n /**\n * 검증을 실행해 `invalid` 를 갱신하고 결과를 반환한다.\n * 실패 시 `invalid` 이벤트를 올려 폼이 반응할 수 있게 한다.\n */\n checkValidity(): boolean {\n const valid = this.validateValue()\n\n this.invalid = !valid\n if (!valid) {\n this.dispatchEvent(new CustomEvent('invalid', { bubbles: true, composed: true, detail: { field: this.name } }))\n }\n\n return valid\n }\n\n protected appendFormData({ formData }: FormDataEvent): void {\n if (!this.name) return\n /* disabled 컨트롤은 폼 데이터에 포함하지 않는다(HTML 표준 거동).\n readonly 는 수정만 막는 상태이므로 포함한다. */\n if (this.disabled) return\n\n let values: T | T[] = this.value!\n if (!(values instanceof Array)) {\n values = [this.value] as T[]\n }\n\n values.forEach(value => {\n formData.append(\n this.name!,\n typeof value === 'string' ? value : value === undefined || value === null ? '' : JSON.stringify(value)\n )\n })\n }\n}\n"]}
1
+ {"version":3,"file":"ox-form-field.js","sourceRoot":"","sources":["../../src/ox-form-field.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement } from 'lit'\nimport { property } from 'lit/decorators.js'\n\n/**\n * 모든 입력 컴포넌트의 기반 계약.\n *\n * ## 상태 계약\n * `disabled` / `readonly` / `required` / `invalid` 는 **attribute 로 reflect** 된다.\n * 그래야 `InputStyles` 의 `:host([disabled])` · `:host([readonly])` · `:host([invalid])` 가\n * **마크업으로 설정했을 때뿐 아니라 프로퍼티로 설정했을 때도** 시각에 반영된다.\n * (reflect 가 없으면 `el.disabled = true` 는 화면에 나타나지 않는다.)\n *\n * ## disabled 와 readonly 의 구분\n * - `disabled`: 상호작용 불가 · 흐리게 · **폼 데이터에 포함되지 않음**(HTML 표준 거동)\n * - `readonly`: 값을 **보고 선택·복사할 수 있으나** 수정 불가 · **폼 데이터에는 포함됨**\n * → 조회 화면을 `disabled` 로 잠그면 흐려지고 값도 전송되지 않으므로 `readonly` 를 쓴다.\n *\n * ## 검증\n * `checkValidity()` 가 `invalid` 를 갱신하고 결과를 반환한다.\n * 컴포넌트별 규칙(형식·범위 등)이 있으면 `validateValue()` 를 오버라이드한다(기본은 `required` 만 검사).\n */\nexport abstract class OxFormField<T = any> extends LitElement {\n @property({ type: String, attribute: true }) name?: string\n @property({ type: Boolean, reflect: true }) disabled?: boolean\n\n /** 값 표시·선택은 되지만 수정 불가. disabled 와 달리 폼 데이터에 포함된다. */\n @property({ type: Boolean, reflect: true }) readonly?: boolean\n\n /** 값이 반드시 있어야 함. checkValidity() 의 기준. */\n @property({ type: Boolean, reflect: true }) required?: boolean\n\n /** 검증 실패 상태. InputStyles 가 error 색으로 표시한다. */\n @property({ type: Boolean, reflect: true }) invalid?: boolean\n\n /** 비어 있을 때 보여줄 안내 문구(컴포넌트가 지원하는 경우). */\n @property({ type: String }) placeholder?: string\n\n @property() value?: T\n\n protected _form: HTMLFormElement | null = null\n protected _formdataEventHandler: EventListener | null = null\n\n override connectedCallback(): void {\n super.connectedCallback()\n\n if (this.name) {\n this._form = this.closest('form')\n if (this._form) {\n this._formdataEventHandler = this.appendFormData.bind(this) as EventListener\n this._form.addEventListener('formdata', this._formdataEventHandler)\n }\n }\n }\n\n override disconnectedCallback(): void {\n super.disconnectedCallback()\n\n if (this._form) {\n this._form.removeEventListener('formdata', this._formdataEventHandler!)\n this._form = null\n this._formdataEventHandler = null\n }\n }\n\n /**\n * 값이 비었는지 판단. 문자열·배열·nullish 를 다룬다.\n * 숫자 `0` 과 `false` 는 **유효한 값**이므로 비었다고 보지 않는다.\n */\n protected isEmpty(): boolean {\n const value = this.value as unknown\n\n if (value === undefined || value === null) return true\n if (typeof value === 'string') return value.trim().length === 0\n if (Array.isArray(value)) return value.length === 0\n\n return false\n }\n\n /**\n * 자체 검증 규칙. 기본은 `required` 만 검사한다.\n * 컴포넌트별 규칙이 있으면 오버라이드한다.\n */\n protected validateValue(): boolean {\n return !(this.required && this.isEmpty())\n }\n\n /**\n * 검증을 실행해 `invalid` 를 갱신하고 결과를 반환한다.\n * 실패 시 `invalid` 이벤트를 올려 폼이 반응할 수 있게 한다.\n */\n checkValidity(): boolean {\n const valid = this.validateValue()\n\n this.invalid = !valid\n if (!valid) {\n this.dispatchEvent(new CustomEvent('invalid', { bubbles: true, composed: true, detail: { field: this.name } }))\n }\n\n return valid\n }\n\n protected appendFormData({ formData }: FormDataEvent): void {\n if (!this.name) return\n /* disabled 컨트롤은 폼 데이터에 포함하지 않는다(HTML 표준 거동).\n readonly 는 수정만 막는 상태이므로 포함한다. */\n if (this.disabled) return\n\n let values: T | T[] = this.value!\n if (!(values instanceof Array)) {\n values = [this.value] as T[]\n }\n\n values.forEach(value => {\n /* A file goes as itself, name and all; turned to JSON it was \"{}\". */\n if (value instanceof Blob) {\n formData.append(this.name!, value)\n return\n }\n\n formData.append(\n this.name!,\n typeof value === 'string' ? value : value === undefined || value === null ? '' : JSON.stringify(value)\n )\n })\n }\n}\n"]}
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import '@material/web/icon/icon.js';
5
5
  import { PropertyValues } from 'lit';
6
+ import { FileUploadLimits } from '@operato/utils';
6
7
  import { OxFormField } from './ox-form-field.js';
7
8
  /**
8
9
  * Picks files — the one picker for every screen.
@@ -18,11 +19,15 @@ import { OxFormField } from './ox-form-field.js';
18
19
  * The limits are the server's own numbers (`fileUploadLimits` in @operato/utils), never written here.
19
20
  *
20
21
  * The owner uploads; while it does, it sets `progress` (0‒1) and the picker shows it.
22
+ *
23
+ * A panel with its own look styles the quiet buttons through `::part(attach | replace | remove)` and puts its
24
+ * own icon in the attach button through `slot="icon"`. File names are shown as written.
21
25
  */
22
26
  export declare class OxInputFile extends OxFormField {
23
27
  static styles: import("lit").CSSResult[];
24
28
  multiple?: boolean;
25
29
  accept?: string;
30
+ /** The drop box's icon (upload when not given); in the quiet look, shown before the attach word only when given. */
26
31
  icon?: string;
27
32
  label?: string;
28
33
  description?: string;
@@ -32,6 +37,18 @@ export declare class OxInputFile extends OxFormField {
32
37
  quiet: boolean;
33
38
  /** Where a file may be dropped. The picker itself when not given; a 360 passes its section. */
34
39
  dropTarget?: HTMLElement;
40
+ /**
41
+ * The owner's own button (a page action) calls `pick()`: the picker draws no button of its own, only what it
42
+ * refused and the upload progress, where the owner placed it.
43
+ */
44
+ outsideTrigger: boolean;
45
+ /**
46
+ * A screen's own limits, handed down by the server (an AI mode's attachment policy) — never written on the screen.
47
+ * The picker uses the tighter of these and the server's upload limits.
48
+ */
49
+ limits?: FileUploadLimits;
50
+ /** The file is read in the browser and never uploaded: the server's upload limits do not apply (accept still does). */
51
+ local: boolean;
35
52
  /** Upload progress 0‒1, set by the owner while it uploads; the buttons wait meanwhile. */
36
53
  progress?: number;
37
54
  fileInput: HTMLInputElement;
@@ -6,8 +6,8 @@ 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
8
  import { i18next } from '@operato/i18n';
9
- import { ButtonStyles, ScrollbarStyles } from '@operato/styles';
10
- import { FileDropHelper, fileUploadLimits, sortPickedFiles } from '@operato/utils';
9
+ import { actionHeight, ButtonStyles, ScrollbarStyles } from '@operato/styles';
10
+ import { FileDropHelper, fileUploadLimits, narrowLimits, sortPickedFiles } from '@operato/utils';
11
11
  import { OxFormField } from './ox-form-field.js';
12
12
  import { InputStyles } from './input-styles.js';
13
13
  const megabytes = (bytes) => bytes >= 1048576 ? `${Math.round((bytes / 1048576) * 10) / 10}MB` : `${Math.max(1, Math.round(bytes / 1024))}KB`;
@@ -25,15 +25,24 @@ const megabytes = (bytes) => bytes >= 1048576 ? `${Math.round((bytes / 1048576)
25
25
  * The limits are the server's own numbers (`fileUploadLimits` in @operato/utils), never written here.
26
26
  *
27
27
  * The owner uploads; while it does, it sets `progress` (0‒1) and the picker shows it.
28
+ *
29
+ * A panel with its own look styles the quiet buttons through `::part(attach | replace | remove)` and puts its
30
+ * own icon in the attach button through `slot="icon"`. File names are shown as written.
28
31
  */
29
32
  let OxInputFile = class OxInputFile extends OxFormField {
30
33
  constructor() {
31
34
  super(...arguments);
32
- this.icon = 'upload';
33
35
  this.hideFileList = false;
34
36
  this.attachFileList = false;
35
37
  /** Text buttons instead of the drop box — for a screen that is read first. */
36
38
  this.quiet = false;
39
+ /**
40
+ * The owner's own button (a page action) calls `pick()`: the picker draws no button of its own, only what it
41
+ * refused and the upload progress, where the owner placed it.
42
+ */
43
+ this.outsideTrigger = false;
44
+ /** The file is read in the browser and never uploaded: the server's upload limits do not apply (accept still does). */
45
+ this.local = false;
37
46
  this.refusals = [];
38
47
  this.refusalLimits = {};
39
48
  this.previews = new Map();
@@ -58,7 +67,6 @@ let OxInputFile = class OxInputFile extends OxFormField {
58
67
  justify-content: center;
59
68
  padding: calc(var(--ox-input-gap) * 2);
60
69
  min-height: 100px;
61
- text-transform: capitalize;
62
70
 
63
71
  border: 1px dashed var(--md-sys-color-outline-variant);
64
72
  background-color: var(--md-sys-color-surface);
@@ -87,7 +95,6 @@ let OxInputFile = class OxInputFile extends OxFormField {
87
95
  position: relative;
88
96
  width: auto;
89
97
  border: none;
90
- text-transform: capitalize;
91
98
 
92
99
  padding: var(--file-uploader-label-padding);
93
100
  border-radius: var(--file-uploader-label-border-radius);
@@ -126,12 +133,15 @@ let OxInputFile = class OxInputFile extends OxFormField {
126
133
  }
127
134
 
128
135
  /* ── quiet ── no box: the words are the control. */
136
+ /* --ox-actions-align: a section heading's actions slot says end, and the refusal line follows it. */
129
137
  :host([quiet]) {
130
138
  display: inline-flex;
131
139
  flex-wrap: wrap;
132
140
  align-items: center;
141
+ justify-content: var(--ox-actions-align, start);
133
142
  gap: var(--spacing-small, 4px);
134
- font-size: var(--ui-t-body, 13px);
143
+ /* The name reads at the size of what is around it (a form row, a heading); the buttons keep theirs. */
144
+ font-size: inherit;
135
145
  color: var(--md-sys-color-on-surface);
136
146
  border-radius: var(--md-sys-shape-corner-small, 4px);
137
147
  }
@@ -161,6 +171,11 @@ let OxInputFile = class OxInputFile extends OxFormField {
161
171
  font-variant-numeric: tabular-nums;
162
172
  }
163
173
 
174
+ /* A place that is tapped with a thumb (a phone form) raises it; a heading or a row keeps the cell height. */
175
+ :host([quiet]) button {
176
+ height: var(--ox-input-file-action-height, ${actionHeight('--cell-action-height')});
177
+ }
178
+
164
179
  .picked md-icon {
165
180
  --md-icon-size: 16px;
166
181
  color: var(--md-sys-color-on-surface-variant);
@@ -175,6 +190,7 @@ let OxInputFile = class OxInputFile extends OxFormField {
175
190
 
176
191
  .refusal {
177
192
  flex-basis: 100%;
193
+ text-align: var(--ox-actions-align, start);
178
194
  color: var(--ui-crit, var(--md-sys-color-error));
179
195
  }
180
196
  `
@@ -233,6 +249,9 @@ let OxInputFile = class OxInputFile extends OxFormField {
233
249
  const files = this.value || [];
234
250
  const busy = this.disabled || this.readonly || this.uploading;
235
251
  const shown = this.hideFileList ? [] : files;
252
+ if (this.outsideTrigger) {
253
+ return html `${this.renderInput()} ${this.renderProgress()} ${this.renderRefusals()}`;
254
+ }
236
255
  return html `
237
256
  ${this.renderInput()}
238
257
  ${shown.map(file => html `<span class="picked">
@@ -240,17 +259,17 @@ let OxInputFile = class OxInputFile extends OxFormField {
240
259
  <span class="name" title=${file.name}>${file.name}</span>
241
260
  <span class="size">${megabytes(file.size)}</span>
242
261
  ${!this.multiple
243
- ? html `<button type="button" quiet small ?disabled=${busy} @click=${() => this.pick()}>
262
+ ? html `<button type="button" part="replace" quiet small ?disabled=${busy} @click=${() => this.pick()}>
244
263
  ${i18next.t('label.replace file', { defaultValue: 'replace' })}
245
264
  </button>`
246
265
  : nothing}
247
- <button type="button" quiet small danger ?disabled=${busy} @click=${() => this.removeFile(file)}>
266
+ <button type="button" part="remove" quiet small danger ?disabled=${busy} @click=${() => this.removeFile(file)}>
248
267
  ${i18next.t('label.remove file', { defaultValue: 'remove' })}
249
268
  </button>
250
269
  </span>`)}
251
270
  ${this.multiple || !shown.length
252
- ? html `<button type="button" quiet small ?disabled=${busy} @click=${() => this.pick()}>
253
- ${this.label || i18next.t('label.attach file', { defaultValue: 'attach' })}
271
+ ? html `<button type="button" part="attach" quiet small ?disabled=${busy} @click=${() => this.pick()}>
272
+ <slot name="icon">${this.icon ? html `<md-icon>${this.icon}</md-icon>` : nothing}</slot>${this.label || i18next.t('label.attach file', { defaultValue: 'attach' })}
254
273
  </button>`
255
274
  : nothing}
256
275
  ${this.renderProgress()} ${this.renderRefusals()}
@@ -370,7 +389,7 @@ let OxInputFile = class OxInputFile extends OxFormField {
370
389
  async take(picked) {
371
390
  const files = this.value || [];
372
391
  const keep = this.attachFileList;
373
- const limits = await fileUploadLimits();
392
+ const limits = this.local ? narrowLimits(this.limits) : narrowLimits(this.limits, await fileUploadLimits());
374
393
  const { taken, refused } = sortPickedFiles(picked, {
375
394
  accept: this.accept,
376
395
  limits,
@@ -425,6 +444,15 @@ __decorate([
425
444
  __decorate([
426
445
  property({ attribute: false })
427
446
  ], OxInputFile.prototype, "dropTarget", void 0);
447
+ __decorate([
448
+ property({ type: Boolean, reflect: true, attribute: 'outside-trigger' })
449
+ ], OxInputFile.prototype, "outsideTrigger", void 0);
450
+ __decorate([
451
+ property({ attribute: false })
452
+ ], OxInputFile.prototype, "limits", void 0);
453
+ __decorate([
454
+ property({ type: Boolean, reflect: true })
455
+ ], OxInputFile.prototype, "local", void 0);
428
456
  __decorate([
429
457
  property({ type: Number })
430
458
  ], OxInputFile.prototype, "progress", 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 { ButtonStyles, ScrollbarStyles } from '@operato/styles'\nimport { FileDropHelper, FileRefusal, fileUploadLimits, 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@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 text-transform: capitalize;\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 text-transform: capitalize;\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 :host([quiet]) {\n display: inline-flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--spacing-small, 4px);\n font-size: var(--ui-t-body, 13px);\n color: var(--md-sys-color-on-surface);\n border-radius: var(--md-sys-shape-corner-small, 4px);\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 .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 .refusal {\n flex-basis: 100%;\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 @property({ type: String }) icon?: string = 'upload'\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 /** 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 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\" quiet small ?disabled=${busy} @click=${() => this.pick()}>\n ${i18next.t('label.replace file', { defaultValue: 'replace' })}\n </button>`\n : nothing}\n <button type=\"button\" 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\" quiet small ?disabled=${busy} @click=${() => this.pick()}>\n ${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 = 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'\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 :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 .refusal {\n flex-basis: 100%;\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"]}
@@ -91,6 +91,105 @@ describe('ox-input-file', () => {
91
91
  await settle(el);
92
92
  expect(el.value ?? [], 'a drop landed mid-upload').to.deep.equal([]);
93
93
  });
94
+ it('keeps its button at the end where its place says so, with the refusal under it', async () => {
95
+ const wrap = await fixture(html `<div style="--ox-actions-align: end; display: flex; justify-content: flex-end; width: 400px">
96
+ <ox-input-file quiet accept="image/*"></ox-input-file>
97
+ </div>`);
98
+ const el = wrap.querySelector('ox-input-file');
99
+ drop(el, file('a-rather-long-file-name-that-was-refused.txt', 'text/plain'));
100
+ await settle(el);
101
+ const host = el.getBoundingClientRect();
102
+ const button = buttons(el)[0].getBoundingClientRect();
103
+ const refusal = el.renderRoot.querySelector('[role="alert"]');
104
+ expect(Math.abs(host.right - button.right), 'the button left the end').to.be.below(1);
105
+ expect(getComputedStyle(refusal).textAlign).to.equal('end');
106
+ });
107
+ it('writes the picked name at the size of its surroundings', async () => {
108
+ const wrap = await fixture(html `<div style="font-size: 11px"><ox-input-file quiet></ox-input-file></div>`);
109
+ const el = wrap.querySelector('ox-input-file');
110
+ drop(el, file('a.pdf', 'application/pdf'));
111
+ await settle(el);
112
+ expect(getComputedStyle(el.renderRoot.querySelector('.picked .name')).fontSize).to.equal('11px');
113
+ });
114
+ it('starts at the start when nothing says otherwise', async () => {
115
+ const el = await fixture(html `<ox-input-file quiet accept="image/*"></ox-input-file>`);
116
+ drop(el, file('a-rather-long-file-name-that-was-refused.txt', 'text/plain'));
117
+ await settle(el);
118
+ expect(Math.abs(el.getBoundingClientRect().left - buttons(el)[0].getBoundingClientRect().left)).to.be.below(1);
119
+ });
120
+ it('shows an icon only when given one, and takes its button height from its place', async () => {
121
+ const plain = await fixture(html `<ox-input-file quiet></ox-input-file>`);
122
+ expect(!!buttons(plain)[0].querySelector('md-icon'), 'an icon nobody asked for').to.equal(false);
123
+ const wrap = await fixture(html `<div style="--ox-input-file-action-height: 40px">
124
+ <ox-input-file quiet icon="photo_camera" label="take"></ox-input-file>
125
+ </div>`);
126
+ const camera = wrap.querySelector('ox-input-file');
127
+ const button = buttons(camera)[0];
128
+ expect(button.querySelector('md-icon')?.textContent).to.equal('photo_camera');
129
+ expect(button.getBoundingClientRect().height).to.equal(40);
130
+ });
131
+ it('draws no button of its own when a page action starts the pick, and still refuses in place', async () => {
132
+ const el = await fixture(html `<ox-input-file quiet outside-trigger accept="application/pdf"></ox-input-file>`);
133
+ expect(buttons(el).length, 'a second trigger beside the page action').to.equal(0);
134
+ let opened = 0;
135
+ el.fileInput.click = () => opened++;
136
+ el.pick();
137
+ expect(opened, 'pick() did not open the dialog').to.equal(1);
138
+ drop(el, file('register.xlsx', 'application/vnd.ms-excel'));
139
+ await settle(el);
140
+ expect(!!el.renderRoot.querySelector('[role="alert"]'), 'the refusal is not shown').to.equal(true);
141
+ });
142
+ it('leaves the server limits out for a file read in the browser, and still checks the type', async () => {
143
+ registerFileUploadLimits(async () => ({ maxFileSize: 20 }));
144
+ const el = await fixture(html `<ox-input-file quiet local accept=".json"></ox-input-file>`);
145
+ drop(el, file('samples.json', 'application/json', 50));
146
+ await settle(el);
147
+ expect(el.value.map((one) => one.name), 'a local file was held to the upload limit').to.deep.equal(['samples.json']);
148
+ drop(el, file('notes.txt', 'text/plain'));
149
+ await settle(el);
150
+ expect(el.value.map((one) => one.name)).to.deep.equal(['samples.json']);
151
+ expect(!!el.renderRoot.querySelector('[role="alert"]')).to.equal(true);
152
+ });
153
+ it('takes a screen limit tighter than the server, and never a looser one', async () => {
154
+ registerFileUploadLimits(async () => ({ maxFileSize: 100 }));
155
+ const el = await fixture(html `<ox-input-file quiet multiple .limits=${{ maxFileSize: 20 }}></ox-input-file>`);
156
+ drop(el, file('small.png', 'image/png', 10), file('middle.png', 'image/png', 50));
157
+ await settle(el);
158
+ expect(el.value.map((one) => one.name), 'the screen limit was not applied').to.deep.equal(['small.png']);
159
+ el.limits = { maxFileSize: 1000 };
160
+ drop(el, file('large.png', 'image/png', 500));
161
+ await settle(el);
162
+ expect(el.value.map((one) => one.name), 'a screen loosened the server limit').to.deep.equal(['small.png']);
163
+ });
164
+ it('shows a file name as written, in the box and in a refusal', async () => {
165
+ const el = await fixture(html `<ox-input-file accept="image/*"></ox-input-file>`);
166
+ drop(el, file('notes.txt', 'text/plain'));
167
+ await settle(el);
168
+ expect(getComputedStyle(el.renderRoot.querySelector('[role="alert"]')).textTransform, 'the refusal recased the name').to.equal('none');
169
+ el.accept = '';
170
+ drop(el, file('model.figpkg', ''));
171
+ await settle(el);
172
+ expect(getComputedStyle(el.renderRoot.querySelector('li')).textTransform, 'the list recased the name').to.equal('none');
173
+ });
174
+ it('lets a panel style its quiet buttons by part and give the attach button its own icon', async () => {
175
+ const el = await fixture(html `<ox-input-file quiet><span slot="icon" id="tile">▣</span></ox-input-file>`);
176
+ const attach = el.renderRoot.querySelector('[part="attach"]');
177
+ expect(!!attach, 'no attach part').to.equal(true);
178
+ const slot = attach.querySelector('slot[name="icon"]');
179
+ expect(slot.assignedElements().map(one => one.id)).to.deep.equal(['tile']);
180
+ drop(el, file('a.pdf', 'application/pdf'));
181
+ await settle(el);
182
+ expect([...el.renderRoot.querySelectorAll('[part]')].map((one) => one.getAttribute('part'))).to.deep.equal(['replace', 'remove']);
183
+ });
184
+ it('hands its files to the form data as files', async () => {
185
+ const form = await fixture(html `<form><ox-input-file name="papers" multiple></ox-input-file></form>`);
186
+ const el = form.querySelector('ox-input-file');
187
+ drop(el, file('a.pdf', 'application/pdf'), file('b.png', 'image/png'));
188
+ await settle(el);
189
+ const sent = new FormData(form).getAll('papers');
190
+ expect(sent.every(one => one instanceof File), 'a file went as text').to.equal(true);
191
+ expect(sent.map(one => one.name)).to.deep.equal(['a.pdf', 'b.png']);
192
+ });
94
193
  it('keeps the drop box when not quiet', async () => {
95
194
  const el = await fixture(html `<ox-input-file></ox-input-file>`);
96
195
  expect(getComputedStyle(el).borderTopStyle).to.equal('dashed');
@@ -1 +1 @@
1
- {"version":3,"file":"ox-input-file.test.js","sourceRoot":"","sources":["../../test/ox-input-file.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { aTimeout, expect, fixture } from '@open-wc/testing'\nimport { html } from 'lit'\n\nimport { registerFileUploadLimits } from '@operato/utils'\n\nimport '../src/ox-input-file.js'\n\n/**\n * The one file picker.\n *\n * ## Why this is tested\n *\n * Every screen that takes a file goes through this element, and a 360 uses its quiet look. What\n * must hold:\n *\n * ```\n * quiet no box: an attach button; once picked, the name with replace and remove\n * refusal a type outside accept or a file over the server's limit never becomes the value,\n * and says so in place — a drop skips the native accept filter\n * drop target a drop lands on the element the owner gives (a 360 section), and only there\n * progress while the owner uploads, the buttons wait and the percent shows\n * ```\n */\ndescribe('ox-input-file', () => {\n const file = (name: string, type: string, size = 10) => new File([new Uint8Array(size)], name, { type })\n\n const drop = (area: HTMLElement, ...files: File[]) => {\n const dataTransfer = new DataTransfer()\n files.forEach(one => dataTransfer.items.add(one))\n area.dispatchEvent(new DragEvent('drop', { dataTransfer, bubbles: true, cancelable: true }))\n }\n\n const settle = async (el: any) => {\n await aTimeout(0)\n await el.updateComplete\n }\n\n const buttons = (el: any) => [...el.renderRoot.querySelectorAll('button')] as HTMLButtonElement[]\n\n /* The theme token the box border is drawn with; without it the border falls away and every look reads 'none'. */\n before(() => document.documentElement.style.setProperty('--md-sys-color-outline-variant', 'gray'))\n after(() => document.documentElement.style.removeProperty('--md-sys-color-outline-variant'))\n\n afterEach(() => registerFileUploadLimits(async () => ({})))\n\n it('draws no box when quiet, and shows the picked name with replace and remove', async () => {\n const el: any = await fixture(html`<ox-input-file quiet accept=\"image/*,application/pdf\"></ox-input-file>`)\n\n expect(getComputedStyle(el).borderTopStyle, 'quiet still draws a box').to.equal('none')\n expect(buttons(el).length, 'one attach button before a pick').to.equal(1)\n\n let changes = 0\n el.addEventListener('change', () => changes++)\n\n drop(el, file('paper.pdf', 'application/pdf'))\n await settle(el)\n\n expect(el.value.map((one: File) => one.name)).to.deep.equal(['paper.pdf'])\n expect(el.renderRoot.querySelector('.picked .name').textContent).to.equal('paper.pdf')\n expect(buttons(el).length, 'replace and remove').to.equal(2)\n\n const remove = buttons(el).find(one => one.hasAttribute('danger'))!\n remove.click()\n await el.updateComplete\n\n expect(el.value).to.deep.equal([])\n expect(changes).to.equal(2)\n })\n\n it('refuses a type outside accept and a file over the server limit, in place', async () => {\n registerFileUploadLimits(async () => ({ maxFileSize: 20 }))\n\n const el: any = await fixture(html`<ox-input-file quiet multiple accept=\"image/*\"></ox-input-file>`)\n\n drop(el, file('note.txt', 'text/plain'), file('big.png', 'image/png', 50), file('ok.png', 'image/png', 5))\n await settle(el)\n\n expect(el.value.map((one: File) => one.name)).to.deep.equal(['ok.png'])\n\n const refusal = el.renderRoot.querySelector('[role=\"alert\"]')\n expect(refusal, 'no refusal shown').to.exist\n expect(refusal.textContent).to.contain('note.txt')\n expect(refusal.textContent).to.contain('big.png')\n })\n\n it('takes a drop on the given target and lets go of the old one', async () => {\n const section = document.createElement('section')\n const other = document.createElement('section')\n document.body.append(section, other)\n\n const el: any = await fixture(html`<ox-input-file quiet .dropTarget=${section}></ox-input-file>`)\n\n drop(section, file('a.pdf', 'application/pdf'))\n await settle(el)\n expect(el.value.map((one: File) => one.name)).to.deep.equal(['a.pdf'])\n\n el.dropTarget = other\n await el.updateComplete\n\n drop(section, file('b.pdf', 'application/pdf'))\n await settle(el)\n expect(el.value.map((one: File) => one.name), 'the old target still takes drops').to.deep.equal(['a.pdf'])\n\n section.dispatchEvent(new DragEvent('dragenter', { bubbles: true }))\n expect(section.classList.contains('candrop'), 'the old target still lights up').to.equal(false)\n\n drop(other, file('c.pdf', 'application/pdf'))\n await settle(el)\n expect(el.value.map((one: File) => one.name)).to.deep.equal(['c.pdf'])\n\n section.remove()\n other.remove()\n })\n\n it('waits while the owner uploads and shows the percent', async () => {\n const el: any = await fixture(html`<ox-input-file quiet .progress=${0.4}></ox-input-file>`)\n\n expect(buttons(el).every(one => one.disabled), 'a button works mid-upload').to.equal(true)\n expect(el.renderRoot.querySelector('[role=\"status\"]').textContent).to.contain('40')\n\n drop(el, file('a.pdf', 'application/pdf'))\n await settle(el)\n expect(el.value ?? [], 'a drop landed mid-upload').to.deep.equal([])\n })\n\n it('keeps the drop box when not quiet', async () => {\n const el: any = await fixture(html`<ox-input-file></ox-input-file>`)\n\n expect(getComputedStyle(el).borderTopStyle).to.equal('dashed')\n expect(el.renderRoot.querySelector('label[for=\"input-file\"]')).to.exist\n\n drop(el, file('a.bin', ''))\n await settle(el)\n expect(el.renderRoot.querySelectorAll('li').length).to.equal(1)\n })\n})\n"]}
1
+ {"version":3,"file":"ox-input-file.test.js","sourceRoot":"","sources":["../../test/ox-input-file.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { aTimeout, expect, fixture } from '@open-wc/testing'\nimport { html } from 'lit'\n\nimport { registerFileUploadLimits } from '@operato/utils'\n\nimport '../src/ox-input-file.js'\n\n/**\n * The one file picker.\n *\n * ## Why this is tested\n *\n * Every screen that takes a file goes through this element, and a 360 uses its quiet look. What\n * must hold:\n *\n * ```\n * quiet no box: an attach button; once picked, the name with replace and remove\n * refusal a type outside accept or a file over the server's limit never becomes the value,\n * and says so in place — a drop skips the native accept filter\n * drop target a drop lands on the element the owner gives (a 360 section), and only there\n * progress while the owner uploads, the buttons wait and the percent shows\n * ```\n */\ndescribe('ox-input-file', () => {\n const file = (name: string, type: string, size = 10) => new File([new Uint8Array(size)], name, { type })\n\n const drop = (area: HTMLElement, ...files: File[]) => {\n const dataTransfer = new DataTransfer()\n files.forEach(one => dataTransfer.items.add(one))\n area.dispatchEvent(new DragEvent('drop', { dataTransfer, bubbles: true, cancelable: true }))\n }\n\n const settle = async (el: any) => {\n await aTimeout(0)\n await el.updateComplete\n }\n\n const buttons = (el: any) => [...el.renderRoot.querySelectorAll('button')] as HTMLButtonElement[]\n\n /* The theme token the box border is drawn with; without it the border falls away and every look reads 'none'. */\n before(() => document.documentElement.style.setProperty('--md-sys-color-outline-variant', 'gray'))\n after(() => document.documentElement.style.removeProperty('--md-sys-color-outline-variant'))\n\n afterEach(() => registerFileUploadLimits(async () => ({})))\n\n it('draws no box when quiet, and shows the picked name with replace and remove', async () => {\n const el: any = await fixture(html`<ox-input-file quiet accept=\"image/*,application/pdf\"></ox-input-file>`)\n\n expect(getComputedStyle(el).borderTopStyle, 'quiet still draws a box').to.equal('none')\n expect(buttons(el).length, 'one attach button before a pick').to.equal(1)\n\n let changes = 0\n el.addEventListener('change', () => changes++)\n\n drop(el, file('paper.pdf', 'application/pdf'))\n await settle(el)\n\n expect(el.value.map((one: File) => one.name)).to.deep.equal(['paper.pdf'])\n expect(el.renderRoot.querySelector('.picked .name').textContent).to.equal('paper.pdf')\n expect(buttons(el).length, 'replace and remove').to.equal(2)\n\n const remove = buttons(el).find(one => one.hasAttribute('danger'))!\n remove.click()\n await el.updateComplete\n\n expect(el.value).to.deep.equal([])\n expect(changes).to.equal(2)\n })\n\n it('refuses a type outside accept and a file over the server limit, in place', async () => {\n registerFileUploadLimits(async () => ({ maxFileSize: 20 }))\n\n const el: any = await fixture(html`<ox-input-file quiet multiple accept=\"image/*\"></ox-input-file>`)\n\n drop(el, file('note.txt', 'text/plain'), file('big.png', 'image/png', 50), file('ok.png', 'image/png', 5))\n await settle(el)\n\n expect(el.value.map((one: File) => one.name)).to.deep.equal(['ok.png'])\n\n const refusal = el.renderRoot.querySelector('[role=\"alert\"]')\n expect(refusal, 'no refusal shown').to.exist\n expect(refusal.textContent).to.contain('note.txt')\n expect(refusal.textContent).to.contain('big.png')\n })\n\n it('takes a drop on the given target and lets go of the old one', async () => {\n const section = document.createElement('section')\n const other = document.createElement('section')\n document.body.append(section, other)\n\n const el: any = await fixture(html`<ox-input-file quiet .dropTarget=${section}></ox-input-file>`)\n\n drop(section, file('a.pdf', 'application/pdf'))\n await settle(el)\n expect(el.value.map((one: File) => one.name)).to.deep.equal(['a.pdf'])\n\n el.dropTarget = other\n await el.updateComplete\n\n drop(section, file('b.pdf', 'application/pdf'))\n await settle(el)\n expect(el.value.map((one: File) => one.name), 'the old target still takes drops').to.deep.equal(['a.pdf'])\n\n section.dispatchEvent(new DragEvent('dragenter', { bubbles: true }))\n expect(section.classList.contains('candrop'), 'the old target still lights up').to.equal(false)\n\n drop(other, file('c.pdf', 'application/pdf'))\n await settle(el)\n expect(el.value.map((one: File) => one.name)).to.deep.equal(['c.pdf'])\n\n section.remove()\n other.remove()\n })\n\n it('waits while the owner uploads and shows the percent', async () => {\n const el: any = await fixture(html`<ox-input-file quiet .progress=${0.4}></ox-input-file>`)\n\n expect(buttons(el).every(one => one.disabled), 'a button works mid-upload').to.equal(true)\n expect(el.renderRoot.querySelector('[role=\"status\"]').textContent).to.contain('40')\n\n drop(el, file('a.pdf', 'application/pdf'))\n await settle(el)\n expect(el.value ?? [], 'a drop landed mid-upload').to.deep.equal([])\n })\n\n it('keeps its button at the end where its place says so, with the refusal under it', async () => {\n const wrap: any = await fixture(html`<div style=\"--ox-actions-align: end; display: flex; justify-content: flex-end; width: 400px\">\n <ox-input-file quiet accept=\"image/*\"></ox-input-file>\n </div>`)\n const el: any = wrap.querySelector('ox-input-file')\n\n drop(el, file('a-rather-long-file-name-that-was-refused.txt', 'text/plain'))\n await settle(el)\n\n const host = el.getBoundingClientRect()\n const button = buttons(el)[0].getBoundingClientRect()\n const refusal = el.renderRoot.querySelector('[role=\"alert\"]')\n\n expect(Math.abs(host.right - button.right), 'the button left the end').to.be.below(1)\n expect(getComputedStyle(refusal).textAlign).to.equal('end')\n })\n\n it('writes the picked name at the size of its surroundings', async () => {\n const wrap: any = await fixture(html`<div style=\"font-size: 11px\"><ox-input-file quiet></ox-input-file></div>`)\n const el: any = wrap.querySelector('ox-input-file')\n\n drop(el, file('a.pdf', 'application/pdf'))\n await settle(el)\n\n expect(getComputedStyle(el.renderRoot.querySelector('.picked .name')).fontSize).to.equal('11px')\n })\n\n it('starts at the start when nothing says otherwise', async () => {\n const el: any = await fixture(html`<ox-input-file quiet accept=\"image/*\"></ox-input-file>`)\n\n drop(el, file('a-rather-long-file-name-that-was-refused.txt', 'text/plain'))\n await settle(el)\n\n expect(Math.abs(el.getBoundingClientRect().left - buttons(el)[0].getBoundingClientRect().left)).to.be.below(1)\n })\n\n it('shows an icon only when given one, and takes its button height from its place', async () => {\n const plain: any = await fixture(html`<ox-input-file quiet></ox-input-file>`)\n expect(!!buttons(plain)[0].querySelector('md-icon'), 'an icon nobody asked for').to.equal(false)\n\n const wrap: any = await fixture(html`<div style=\"--ox-input-file-action-height: 40px\">\n <ox-input-file quiet icon=\"photo_camera\" label=\"take\"></ox-input-file>\n </div>`)\n const camera: any = wrap.querySelector('ox-input-file')\n const button = buttons(camera)[0]\n\n expect(button.querySelector('md-icon')?.textContent).to.equal('photo_camera')\n expect(button.getBoundingClientRect().height).to.equal(40)\n })\n\n it('draws no button of its own when a page action starts the pick, and still refuses in place', async () => {\n const el: any = await fixture(html`<ox-input-file quiet outside-trigger accept=\"application/pdf\"></ox-input-file>`)\n\n expect(buttons(el).length, 'a second trigger beside the page action').to.equal(0)\n\n let opened = 0\n el.fileInput.click = () => opened++\n el.pick()\n expect(opened, 'pick() did not open the dialog').to.equal(1)\n\n drop(el, file('register.xlsx', 'application/vnd.ms-excel'))\n await settle(el)\n expect(!!el.renderRoot.querySelector('[role=\"alert\"]'), 'the refusal is not shown').to.equal(true)\n })\n\n it('leaves the server limits out for a file read in the browser, and still checks the type', async () => {\n registerFileUploadLimits(async () => ({ maxFileSize: 20 }))\n const el: any = await fixture(html`<ox-input-file quiet local accept=\".json\"></ox-input-file>`)\n\n drop(el, file('samples.json', 'application/json', 50))\n await settle(el)\n expect(el.value.map((one: File) => one.name), 'a local file was held to the upload limit').to.deep.equal(['samples.json'])\n\n drop(el, file('notes.txt', 'text/plain'))\n await settle(el)\n expect(el.value.map((one: File) => one.name)).to.deep.equal(['samples.json'])\n expect(!!el.renderRoot.querySelector('[role=\"alert\"]')).to.equal(true)\n })\n\n it('takes a screen limit tighter than the server, and never a looser one', async () => {\n registerFileUploadLimits(async () => ({ maxFileSize: 100 }))\n const el: any = await fixture(html`<ox-input-file quiet multiple .limits=${{ maxFileSize: 20 }}></ox-input-file>`)\n\n drop(el, file('small.png', 'image/png', 10), file('middle.png', 'image/png', 50))\n await settle(el)\n expect(el.value.map((one: File) => one.name), 'the screen limit was not applied').to.deep.equal(['small.png'])\n\n el.limits = { maxFileSize: 1000 }\n drop(el, file('large.png', 'image/png', 500))\n await settle(el)\n expect(el.value.map((one: File) => one.name), 'a screen loosened the server limit').to.deep.equal(['small.png'])\n })\n\n it('shows a file name as written, in the box and in a refusal', async () => {\n const el: any = await fixture(html`<ox-input-file accept=\"image/*\"></ox-input-file>`)\n\n drop(el, file('notes.txt', 'text/plain'))\n await settle(el)\n expect(getComputedStyle(el.renderRoot.querySelector('[role=\"alert\"]')).textTransform, 'the refusal recased the name').to.equal('none')\n\n el.accept = ''\n drop(el, file('model.figpkg', ''))\n await settle(el)\n expect(getComputedStyle(el.renderRoot.querySelector('li')).textTransform, 'the list recased the name').to.equal('none')\n })\n\n it('lets a panel style its quiet buttons by part and give the attach button its own icon', async () => {\n const el: any = await fixture(html`<ox-input-file quiet><span slot=\"icon\" id=\"tile\">▣</span></ox-input-file>`)\n\n const attach = el.renderRoot.querySelector('[part=\"attach\"]')\n expect(!!attach, 'no attach part').to.equal(true)\n const slot = attach.querySelector('slot[name=\"icon\"]') as HTMLSlotElement\n expect(slot.assignedElements().map(one => one.id)).to.deep.equal(['tile'])\n\n drop(el, file('a.pdf', 'application/pdf'))\n await settle(el)\n expect([...el.renderRoot.querySelectorAll('[part]')].map((one: Element) => one.getAttribute('part'))).to.deep.equal(['replace', 'remove'])\n })\n\n it('hands its files to the form data as files', async () => {\n const form: any = await fixture(html`<form><ox-input-file name=\"papers\" multiple></ox-input-file></form>`)\n const el: any = form.querySelector('ox-input-file')\n\n drop(el, file('a.pdf', 'application/pdf'), file('b.png', 'image/png'))\n await settle(el)\n\n const sent = new FormData(form).getAll('papers') as File[]\n expect(sent.every(one => one instanceof File), 'a file went as text').to.equal(true)\n expect(sent.map(one => one.name)).to.deep.equal(['a.pdf', 'b.png'])\n })\n\n it('keeps the drop box when not quiet', async () => {\n const el: any = await fixture(html`<ox-input-file></ox-input-file>`)\n\n expect(getComputedStyle(el).borderTopStyle).to.equal('dashed')\n expect(el.renderRoot.querySelector('label[for=\"input-file\"]')).to.exist\n\n drop(el, file('a.bin', ''))\n await settle(el)\n expect(el.renderRoot.querySelectorAll('li').length).to.equal(1)\n })\n})\n"]}