@design.estate/dees-catalog 9.6.0 → 9.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/dist_bundle/bundle.js +2957 -2685
  2. package/dist_bundle/bundle.js.map +1 -1
  3. package/dist_ts_web/00_commitinfo_data.js +1 -1
  4. package/dist_ts_web/demos/inputs.d.ts +1 -1
  5. package/dist_ts_web/demos/inputs.js +5 -4
  6. package/dist_ts_web/elements/00group-dataview/dees-table/dees-table.js +5 -4
  7. package/dist_ts_web/elements/00group-form/dees-form/dees-form.d.ts +5 -1
  8. package/dist_ts_web/elements/00group-form/dees-form/dees-form.js +27 -3
  9. package/dist_ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.d.ts +1 -0
  10. package/dist_ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.js +11 -2
  11. package/dist_ts_web/elements/00group-harness/dees-harness-composer/dees-harness-composer.d.ts +3 -0
  12. package/dist_ts_web/elements/00group-harness/dees-harness-composer/dees-harness-composer.demo.js +4 -1
  13. package/dist_ts_web/elements/00group-harness/dees-harness-composer/dees-harness-composer.js +51 -13
  14. package/dist_ts_web/elements/00group-input/dees-input-base/dees-input-base.d.ts +1 -8
  15. package/dist_ts_web/elements/00group-input/dees-input-base/dees-input-base.js +7 -2
  16. package/dist_ts_web/elements/00group-input/dees-input-datepicker/component.d.ts +18 -3
  17. package/dist_ts_web/elements/00group-input/dees-input-datepicker/component.js +181 -144
  18. package/dist_ts_web/elements/00group-input/dees-input-datepicker/date-validation.d.ts +13 -0
  19. package/dist_ts_web/elements/00group-input/dees-input-datepicker/date-validation.js +65 -0
  20. package/dist_ts_web/elements/00group-input/dees-input-datepicker/datepicker-popup.js +44 -56
  21. package/dist_ts_web/elements/00group-input/dees-input-datepicker/styles.js +6 -1
  22. package/dist_ts_web/elements/00group-input/dees-input-datepicker/template.js +11 -5
  23. package/dist_ts_web/elements/00group-input/dees-input-text/dees-input-text.d.ts +8 -8
  24. package/dist_ts_web/elements/00group-input/dees-input-text/dees-input-text.js +6 -53
  25. package/dist_ts_web/elements/00group-input/dees-input-textarea/dees-input-textarea.d.ts +62 -0
  26. package/dist_ts_web/elements/00group-input/dees-input-textarea/dees-input-textarea.demo.d.ts +1 -0
  27. package/dist_ts_web/elements/00group-input/dees-input-textarea/dees-input-textarea.demo.js +3 -0
  28. package/dist_ts_web/elements/00group-input/dees-input-textarea/dees-input-textarea.js +224 -0
  29. package/dist_ts_web/elements/00group-input/dees-input-textarea/index.d.ts +1 -0
  30. package/dist_ts_web/elements/00group-input/dees-input-textarea/index.js +2 -0
  31. package/dist_ts_web/elements/00group-input/index.d.ts +1 -0
  32. package/dist_ts_web/elements/00group-input/index.js +2 -1
  33. package/dist_ts_web/elements/00group-input/text-input-contextmenu.d.ts +29 -0
  34. package/dist_ts_web/elements/00group-input/text-input-contextmenu.js +29 -0
  35. package/dist_ts_web/elements/00group-overlay/dees-contextmenu/dees-contextmenu.d.ts +1 -1
  36. package/dist_ts_web/elements/00group-overlay/dees-contextmenu/dees-contextmenu.js +5 -4
  37. package/package.json +2 -2
  38. package/readme.md +78 -4
  39. package/scripts/check-bdtheme-ratchet.cjs +2 -2
  40. package/scripts/check-packed-consumer.mjs +48 -0
  41. package/ts_web/00_commitinfo_data.ts +1 -1
  42. package/ts_web/demos/inputs.ts +5 -4
  43. package/ts_web/elements/00group-dataview/dees-table/dees-table.ts +3 -2
  44. package/ts_web/elements/00group-form/dees-form/dees-form.ts +25 -2
  45. package/ts_web/elements/00group-harness/dees-harness-chat/dees-harness-chat.ts +4 -0
  46. package/ts_web/elements/00group-harness/dees-harness-composer/dees-harness-composer.demo.ts +3 -0
  47. package/ts_web/elements/00group-harness/dees-harness-composer/dees-harness-composer.ts +37 -11
  48. package/ts_web/elements/00group-input/dees-input-base/dees-input-base.ts +6 -1
  49. package/ts_web/elements/00group-input/dees-input-datepicker/component.ts +149 -148
  50. package/ts_web/elements/00group-input/dees-input-datepicker/date-validation.ts +64 -0
  51. package/ts_web/elements/00group-input/dees-input-datepicker/datepicker-popup.ts +40 -40
  52. package/ts_web/elements/00group-input/dees-input-datepicker/styles.ts +5 -0
  53. package/ts_web/elements/00group-input/dees-input-datepicker/template.ts +10 -4
  54. package/ts_web/elements/00group-input/dees-input-text/dees-input-text.ts +5 -52
  55. package/ts_web/elements/00group-input/dees-input-textarea/dees-input-textarea.demo.ts +2 -0
  56. package/ts_web/elements/00group-input/dees-input-textarea/dees-input-textarea.ts +128 -0
  57. package/ts_web/elements/00group-input/dees-input-textarea/index.ts +1 -0
  58. package/ts_web/elements/00group-input/index.ts +1 -0
  59. package/ts_web/elements/00group-input/text-input-contextmenu.ts +31 -0
  60. package/ts_web/elements/00group-overlay/dees-contextmenu/dees-contextmenu.ts +3 -3
  61. package/readme.hints.md +0 -1233
  62. package/readme.icons.md +0 -1849
  63. package/readme.info.md +0 -80
  64. package/readme.plan.md +0 -671
  65. package/readme.playbook.md +0 -820
  66. package/readme.theme-migration.md +0 -168
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * bdTheme ratchet: cssManager.bdTheme() is legacy (see readme.theme-migration.md).
3
+ * bdTheme ratchet: cssManager.bdTheme() is legacy (see docs/development/readme.theme-migration.md).
4
4
  * The only sanctioned usage is the token bridge in ts_web/elements/00theme.ts.
5
5
  * Per-file call counts are frozen in scripts/bdtheme-baseline.json and may only shrink.
6
6
  *
@@ -72,7 +72,7 @@ for (const [file, count] of Object.entries(current)) {
72
72
 
73
73
  if (violations.length > 0) {
74
74
  console.error('[bdtheme-ratchet] FAIL — cssManager.bdTheme() is legacy; use --dees-* tokens from 00theme.ts');
75
- console.error('[bdtheme-ratchet] see readme.theme-migration.md and readme.playbook.md (Theming System)');
75
+ console.error('[bdtheme-ratchet] see docs/development/readme.theme-migration.md and docs/development/readme.playbook.md (Theming System)');
76
76
  for (const violation of violations) console.error(` ${violation}`);
77
77
  process.exit(1);
78
78
  }
@@ -0,0 +1,48 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { cpSync, mkdirSync, mkdtempSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
3
+ import { join, resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ // An actual isolated consumer: install the tarball, never source aliases or links.
7
+ // An optional argument checks a release journal's exact tarball without repacking.
8
+ const root = fileURLToPath(new URL('../', import.meta.url));
9
+ const debugRoot = join(root, '.nogit/debug');
10
+ mkdirSync(debugRoot, { recursive: true });
11
+ const consumer = mkdtempSync(join(debugRoot, 'packed-consumer-'));
12
+ const run = (args, cwd = consumer) => execFileSync('pnpm', args, { cwd, stdio: 'inherit' });
13
+ const tarball = process.argv[2] ? resolve(process.argv[2]) : join(consumer, 'catalog.tgz');
14
+ if (!process.argv[2]) run(['pack', '--out', tarball], root);
15
+ cpSync(join(root, 'test/fixtures/packed-consumer'), consumer, { recursive: true });
16
+ renameSync(join(consumer, 'entrypoints.ts.fixture'), join(consumer, 'entrypoints.ts'));
17
+ writeFileSync(join(consumer, 'package.json'), JSON.stringify({
18
+ name: 'catalog-packed-consumer', private: true, type: 'module',
19
+ packageManager: 'pnpm@12.3.1',
20
+ dependencies: {
21
+ '@design.estate/dees-catalog': tarball,
22
+ '@design.estate/dees-element': '3.1.1',
23
+ },
24
+ devDependencies: {
25
+ typescript: '5.9.3',
26
+ '@types/node': '26.4.1',
27
+ '@git.zone/tstest': '6.0.0',
28
+ '@push.rocks/smartbrowser': '2.0.11',
29
+ },
30
+ }, null, 2));
31
+ writeFileSync(join(consumer, 'pnpm-workspace.yaml'), 'packages:\n - "."\n');
32
+ // Run the same regressions against installed JavaScript, not sources.
33
+ const browserTests = ['table-construction', 'datepicker-validation', 'textarea', 'composer-attachments'];
34
+ for (const name of browserTests) {
35
+ const browserTest = readFileSync(join(root, `test/test.${name}.browser.ts`), 'utf8')
36
+ .replaceAll('../ts_web/', '@design.estate/dees-catalog/dist_ts_web/');
37
+ writeFileSync(join(consumer, `test.${name}.browser.ts`), browserTest);
38
+ }
39
+ console.log(`Packed consumer: ${consumer}`);
40
+ run(['install', '--ignore-scripts']);
41
+ run(['exec', 'tsc', '--version']);
42
+ run(['exec', 'tsc', '--project', 'tsconfig.json']);
43
+ // Verify the current producer compiler too; neither run skips dependency checks.
44
+ run(['add', '--save-dev', '--save-exact', '--ignore-scripts', 'typescript@6.0.3']);
45
+ run(['exec', 'tsc', '--version']);
46
+ run(['exec', 'tsc', '--project', 'tsconfig.json']);
47
+ run(['exec', 'tstest', 'test.*.browser.ts', '--verbose', '--logfile']);
48
+ run(['exec', 'node', 'browser.mjs']);
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@design.estate/dees-catalog',
6
- version: '9.6.0',
6
+ version: '9.7.0',
7
7
  description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.'
8
8
  }
@@ -4,20 +4,21 @@ import { demoPage } from './templates.js';
4
4
  import type { TFormData } from '../elements/00group-form/dees-form/dees-form.js';
5
5
 
6
6
  /** One fixture vocabulary for the showcase and each input's focused demo. */
7
- export type TInputExample = 'text' | 'dropdown' | 'datepicker' | 'checkbox' | 'toggle' | 'radiogroup' | 'multitoggle' | 'quantityselector' | 'phone' | 'iban' | 'tags' | 'typelist' | 'list' | 'fileupload' | 'profilepicture' | 'richtext' | 'wysiwyg' | 'code';
7
+ export type TInputExample = 'text' | 'textarea' | 'dropdown' | 'datepicker' | 'checkbox' | 'toggle' | 'radiogroup' | 'multitoggle' | 'quantityselector' | 'phone' | 'iban' | 'tags' | 'typelist' | 'list' | 'fileupload' | 'profilepicture' | 'richtext' | 'wysiwyg' | 'code';
8
8
 
9
9
  const titles: Record<TInputExample, string> = {
10
- text: 'Text fields', dropdown: 'Dropdowns', datepicker: 'Dates & times', checkbox: 'Checkboxes', toggle: 'Switches',
10
+ text: 'Text fields', textarea: 'Multiline text', dropdown: 'Dropdowns', datepicker: 'Dates & times', checkbox: 'Checkboxes', toggle: 'Switches',
11
11
  radiogroup: 'Radio choices', multitoggle: 'Segmented controls', quantityselector: 'Quantities', phone: 'Phone numbers',
12
12
  iban: 'IBAN', tags: 'Tags', typelist: 'Type lists', list: 'Editable lists', fileupload: 'File uploads',
13
13
  profilepicture: 'Profile pictures', richtext: 'Rich text', wysiwyg: 'Block editor', code: 'Code editor',
14
14
  };
15
- const compound: TInputExample[] = ['tags', 'typelist', 'list', 'fileupload', 'profilepicture', 'richtext', 'wysiwyg', 'code'];
15
+ const compound: TInputExample[] = ['textarea', 'tags', 'typelist', 'list', 'fileupload', 'profilepicture', 'richtext', 'wysiwyg', 'code'];
16
16
  const visibility = [{ key: 'team', option: 'Team members' }, { key: 'private', option: 'Only me' }, { key: 'public', option: 'Anyone with the link' }];
17
17
 
18
18
  export const inputExample = (kind: TInputExample, disabled = false): TemplateResult => {
19
19
  switch (kind) {
20
20
  case 'text': return html`<dees-input-text key="name" label="Workspace name" value="Design studio" required ?disabled=${disabled} description="The name your teammates see."></dees-input-text>`;
21
+ case 'textarea': return html`<dees-input-textarea key="paymentNotes" label="Payment notes" .value=${'Invoice 2026-042\nReference: studio equipment'} .rows=${3} .maxLength=${1000} ?disabled=${disabled} description="Plain text. Line breaks are preserved."></dees-input-textarea>`;
21
22
  case 'dropdown': return html`<dees-input-dropdown key="visibility" label="Visible to" .options=${visibility} .selectedOption=${visibility[0]} ?disabled=${disabled}></dees-input-dropdown>`;
22
23
  case 'datepicker': return html`<dees-input-datepicker key="review" label="Next review" value="2026-09-14T09:00:00" .enableTime=${true} timeFormat="24h" .minuteIncrement=${15} ?disabled=${disabled}></dees-input-datepicker>`;
23
24
  case 'checkbox': return html`<dees-input-checkbox key="digest" label="Include a weekly summary" .value=${true} ?disabled=${disabled}></dees-input-checkbox>`;
@@ -96,7 +97,7 @@ export class DeesDemoInputWorkspace extends DeesElement {
96
97
  <dees-form @formData=${this.submit} style="gap:0">
97
98
  ${this.section('General', 'Make this space your own.', ['text', 'dropdown', 'datepicker'])}
98
99
  ${this.section('Your team', 'A setup that fits the way you work.', ['radiogroup', 'quantityselector', 'multitoggle', 'toggle', 'checkbox'])}
99
- ${this.section('Profile & billing', 'Your picture and contact details.', ['profilepicture', 'phone', 'iban'])}
100
+ ${this.section('Profile & billing', 'Your picture and contact details.', ['profilepicture', 'phone', 'iban', 'textarea'])}
100
101
  ${this.section('Organization', 'Small details that keep work moving.', ['tags', 'typelist', 'list'])}
101
102
  ${this.section('Documents', 'The context behind the work.', ['fileupload', 'richtext', 'wysiwyg'])}
102
103
  ${this.section('Automation', 'Fine-tune the workspace configuration.', ['code'])}
@@ -356,8 +356,6 @@ export class DeesTable<T> extends DeesElement {
356
356
 
357
357
  constructor() {
358
358
  super();
359
- // Make the host focusable so it can receive Ctrl/Cmd+C for copy.
360
- if (!this.hasAttribute('tabindex')) this.setAttribute('tabindex', '0');
361
359
  this.addEventListener('keydown', this.__handleHostKeydown);
362
360
  }
363
361
 
@@ -1280,6 +1278,9 @@ export class DeesTable<T> extends DeesElement {
1280
1278
  }
1281
1279
 
1282
1280
  public connectedCallback(): Promise<void> {
1281
+ // Custom-element constructors must not add host attributes. Initialize the
1282
+ // keyboard entry point here, after parser/caller attributes are available.
1283
+ if (!this.hasAttribute('tabindex')) this.setAttribute('tabindex', '0');
1283
1284
  const connected = super.connectedCallback();
1284
1285
  if (this.hasUpdated) this.requestUpdate();
1285
1286
  return connected;
@@ -13,6 +13,7 @@ import { DeesInputCheckbox } from '../../00group-input/dees-input-checkbox/dees-
13
13
  import { DeesInputCode } from '../../00group-input/dees-input-code/dees-input-code.js';
14
14
  import { DeesInputDatepicker } from '../../00group-input/dees-input-datepicker/index.js';
15
15
  import { DeesInputText } from '../../00group-input/dees-input-text/dees-input-text.js';
16
+ import { DeesInputTextarea } from '../../00group-input/dees-input-textarea/dees-input-textarea.js';
16
17
  import { DeesInputQuantitySelector } from '../../00group-input/dees-input-quantityselector/dees-input-quantityselector.js';
17
18
  import { DeesInputRadiogroup } from '../../00group-input/dees-input-radiogroup/dees-input-radiogroup.js';
18
19
  import { DeesInputDropdown } from '../../00group-input/dees-input-dropdown/dees-input-dropdown.js';
@@ -44,6 +45,7 @@ const FORM_INPUT_TYPES = [
44
45
  DeesInputQuantitySelector,
45
46
  DeesInputRadiogroup,
46
47
  DeesInputText,
48
+ DeesInputTextarea,
47
49
  DeesInputToggle,
48
50
  DeesInputTypelist,
49
51
  DeesInputTags,
@@ -65,6 +67,9 @@ export interface IFormCompatibleElement extends HTMLElement {
65
67
  };
66
68
  getValue?: () => unknown;
67
69
  setValue?: (value: unknown) => void;
70
+ checkValidity?: () => boolean;
71
+ reportValidity?: () => boolean;
72
+ reset?: () => void;
68
73
  }
69
74
 
70
75
  export type TFormDataValue =
@@ -96,6 +101,7 @@ export type TFormInputElement =
96
101
  | DeesInputQuantitySelector
97
102
  | DeesInputRadiogroup
98
103
  | DeesInputText
104
+ | DeesInputTextarea
99
105
  | DeesInputToggle
100
106
  | DeesInputTypelist
101
107
  | DeesInputTags
@@ -281,7 +287,9 @@ export class DeesForm extends DeesElement {
281
287
  public async updateRequiredStatus() {
282
288
  let requiredOK = true;
283
289
  for (const childArg of this.getFormElements()) {
284
- if (childArg.required && !getFormElementValue(childArg)) {
290
+ if (childArg.disabled) continue;
291
+ if ((childArg.required && !getFormElementValue(childArg))
292
+ || (childArg as IFormCompatibleElement).checkValidity?.() === false) {
285
293
  requiredOK = false;
286
294
  }
287
295
  }
@@ -312,6 +320,15 @@ export class DeesForm extends DeesElement {
312
320
  }
313
321
 
314
322
  public async gatherAndDispatch() {
323
+ for (const child of this.getFormElements()) {
324
+ if (child.disabled) continue;
325
+ const input = child as IFormCompatibleElement;
326
+ if (input.checkValidity?.() === false) {
327
+ input.reportValidity?.();
328
+ child.focus();
329
+ return;
330
+ }
331
+ }
315
332
  const valueObject = await this.collectFormData();
316
333
  const formDataEvent = new CustomEvent('formData', {
317
334
  detail: {
@@ -371,12 +388,18 @@ export class DeesForm extends DeesElement {
371
388
  const inputChildren = this.getFormElements();
372
389
 
373
390
  for (const inputChild of inputChildren) {
374
- setFormElementValue(inputChild, null);
391
+ const input = inputChild as IFormCompatibleElement;
392
+ if (input.reset) input.reset();
393
+ else setFormElementValue(inputChild, null);
375
394
  }
376
395
  this.setStatus('normal', 'Submit');
396
+ void this.updateRequiredStatus();
377
397
  }
378
398
 
379
399
  public async addBehaviours() {
400
+ this.addEventListener('validation-change', event => {
401
+ if (this.getFormElements().includes(event.target as TFormInputElement)) void this.updateRequiredStatus();
402
+ });
380
403
  // Use event delegation
381
404
  this.addEventListener('keydown', (event: KeyboardEvent) => {
382
405
  const target = event.target as DeesElement;
@@ -203,6 +203,9 @@ export class DeesHarnessChat extends DeesElement {
203
203
  @property({ attribute: false })
204
204
  accessor attachments: IHarnessAttachment[] = [];
205
205
 
206
+ @property({ attribute: false })
207
+ accessor attachmentsEnabled: boolean = true;
208
+
206
209
  @property({ attribute: false })
207
210
  accessor suggestions: IHarnessComposerSuggestion[] = [];
208
211
 
@@ -763,6 +766,7 @@ export class DeesHarnessChat extends DeesElement {
763
766
  .contextDescription=${this.heading ? `Replying to ${this.heading}` : ''}
764
767
  .value=${this.composerValue}
765
768
  .attachments=${this.attachments}
769
+ .attachmentsEnabled=${this.attachmentsEnabled}
766
770
  .suggestions=${this.suggestions}
767
771
  .maxAttachmentCount=${this.maxAttachmentCount}
768
772
  .maxAttachmentBytes=${this.maxAttachmentBytes}
@@ -44,6 +44,9 @@ export const demoFunc = () => demoPage('Composer', 'Write, attach files, choose
44
44
  <dees-harness-composer busy queuedCount="2"></dees-harness-composer>
45
45
  <div class="dees-demo-label">Waiting for a connection</div>
46
46
  <dees-harness-composer disabled placeholder="Connect to a project to chat."></dees-harness-composer>
47
+ <div class="dees-demo-label">Text-only conversation</div>
48
+ <dees-harness-composer .attachmentsEnabled=${false} .showModelControls=${false}
49
+ value="Summarize the outstanding invoices."></dees-harness-composer>
47
50
  <div class="dees-demo-label">Files ready to send</div>
48
51
  <dees-harness-composer
49
52
  .showModelControls=${false}
@@ -112,6 +112,10 @@ export class DeesHarnessComposer extends DeesElement {
112
112
  @property({ attribute: false })
113
113
  accessor attachments: IHarnessAttachment[] = [];
114
114
 
115
+ /** Host capability: false disables every attachment entry point and sends text only. */
116
+ @property({ attribute: false })
117
+ accessor attachmentsEnabled: boolean = true;
118
+
115
119
  @property({ attribute: false })
116
120
  accessor suggestions: IHarnessComposerSuggestion[] = [];
117
121
 
@@ -473,6 +477,10 @@ export class DeesHarnessComposer extends DeesElement {
473
477
 
474
478
  public updated(changedProperties: Map<PropertyKey, unknown>): void {
475
479
  super.updated(changedProperties);
480
+ if (['attachmentsEnabled', 'disabled'].some(key => changedProperties.has(key) && changedProperties.get(key) !== undefined)) {
481
+ this.attachmentGeneration++;
482
+ if (!this.attachmentsEnabled || this.disabled) this.handleDragLeave();
483
+ }
476
484
  this.syncTextareaSuggestionAria();
477
485
  }
478
486
 
@@ -486,13 +494,13 @@ export class DeesHarnessComposer extends DeesElement {
486
494
  @dragleave=${this.handleDragLeave}
487
495
  @drop=${this.handleDrop}
488
496
  >
489
- <input
497
+ ${this.attachmentsEnabled ? html`<input
490
498
  class="fileInput"
491
499
  type="file"
492
500
  multiple
493
501
  @change=${this.handleFileInputChange}
494
- />
495
- ${this.attachments.length
502
+ />` : ''}
503
+ ${this.attachmentsEnabled && this.attachments.length
496
504
  ? html`
497
505
  <div class="attachmentRow">
498
506
  ${this.attachments.map((attachment) => this.renderChip(attachment))}
@@ -505,7 +513,7 @@ export class DeesHarnessComposer extends DeesElement {
505
513
  .value=${this.value}
506
514
  aria-label="Message"
507
515
  aria-description=${this.contextDescription}
508
- placeholder=${this.placeholder}
516
+ placeholder=${!this.attachmentsEnabled && this.placeholder === 'Message the agent — drop or paste files to attach…' ? 'Message the agent…' : this.placeholder}
509
517
  ?disabled=${this.disabled}
510
518
  rows="1"
511
519
  @input=${this.handleTextInput}
@@ -518,14 +526,16 @@ export class DeesHarnessComposer extends DeesElement {
518
526
  ></textarea>
519
527
  </div>
520
528
  <div class="controlsRow">
521
- <dees-button
529
+ ${this.attachmentsEnabled ? html`<dees-button
530
+ class="attachButton"
522
531
  size="sm"
523
532
  type="ghost"
533
+ title="Attach files"
524
534
  ?disabled=${this.disabled}
525
535
  @clicked=${() => this.shadowRoot?.querySelector<HTMLInputElement>('.fileInput')?.click()}
526
536
  >
527
537
  <dees-icon icon="lucide:Paperclip" iconSize="14"></dees-icon>
528
- </dees-button>
538
+ </dees-button>` : ''}
529
539
  ${this.modeOptions.length
530
540
  ? html`
531
541
  <dees-input-multitoggle class="modeSwitch" label="Agent mode" .labelPosition=${'none'}
@@ -869,6 +879,7 @@ export class DeesHarnessComposer extends DeesElement {
869
879
  };
870
880
 
871
881
  private handlePaste = (event: ClipboardEvent): void => {
882
+ if (!this.attachmentsEnabled || this.disabled) return;
872
883
  const files = Array.from(event.clipboardData?.files ?? []);
873
884
  if (!files.length) return;
874
885
  event.preventDefault();
@@ -878,6 +889,10 @@ export class DeesHarnessComposer extends DeesElement {
878
889
  private handleDragOver = (event: DragEvent): void => {
879
890
  if (!event.dataTransfer?.types.includes('Files')) return;
880
891
  event.preventDefault();
892
+ if (!this.attachmentsEnabled || this.disabled) {
893
+ event.dataTransfer.dropEffect = 'none';
894
+ return;
895
+ }
881
896
  this.shadowRoot?.querySelector('.composer')?.classList.add('dragOver');
882
897
  };
883
898
 
@@ -890,23 +905,32 @@ export class DeesHarnessComposer extends DeesElement {
890
905
  const files = Array.from(event.dataTransfer?.files ?? []);
891
906
  if (!files.length) return;
892
907
  event.preventDefault();
908
+ if (!this.attachmentsEnabled || this.disabled) {
909
+ if (event.dataTransfer) event.dataTransfer.dropEffect = 'none';
910
+ return;
911
+ }
893
912
  void this.addFiles(files);
894
913
  };
895
914
 
896
915
  private attachmentAdditionTail = Promise.resolve();
916
+ private attachmentGeneration = 0;
897
917
 
898
918
  public addFiles(fileList: FileList | File[] | null): Promise<void> {
899
919
  const files = Array.from(fileList ?? []);
900
- if (!files.length) return Promise.resolve();
901
- const addition = this.attachmentAdditionTail.then(() => this.addFilesSequential(files));
920
+ if (!files.length || !this.attachmentsEnabled || this.disabled) return Promise.resolve();
921
+ const generation = this.attachmentGeneration;
922
+ const addition = this.attachmentAdditionTail.then(() => this.addFilesSequential(files, generation));
902
923
  this.attachmentAdditionTail = addition.catch(() => undefined);
903
924
  return addition;
904
925
  }
905
926
 
906
- private async addFilesSequential(files: File[]): Promise<void> {
927
+ private async addFilesSequential(files: File[], generation: number): Promise<void> {
928
+ const allowed = () => this.attachmentsEnabled && !this.disabled && generation === this.attachmentGeneration;
929
+ if (!allowed()) return;
907
930
  const added: IHarnessAttachment[] = [];
908
931
  let totalBytes = this.attachments.reduce((sum, attachment) => sum + attachment.size, 0);
909
932
  for (const file of files) {
933
+ if (!allowed()) return;
910
934
  if (this.attachments.length + added.length >= this.maxAttachmentCount) {
911
935
  this.emit('harness-attachment-error', {
912
936
  message: `A maximum of ${this.maxAttachmentCount} attachments is allowed.`,
@@ -928,6 +952,7 @@ export class DeesHarnessComposer extends DeesElement {
928
952
  }
929
953
  try {
930
954
  const dataBase64 = await harnessFileToBase64(file);
955
+ if (!allowed()) return;
931
956
  const kind = harnessAttachmentKindFor(mediaType);
932
957
  added.push({
933
958
  id: globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(16).slice(2)}`,
@@ -939,12 +964,13 @@ export class DeesHarnessComposer extends DeesElement {
939
964
  });
940
965
  totalBytes += file.size;
941
966
  } catch (error) {
967
+ if (!allowed()) return;
942
968
  this.emit('harness-attachment-error', {
943
969
  message: error instanceof Error ? error.message : String(error),
944
970
  });
945
971
  }
946
972
  }
947
- if (added.length) {
973
+ if (added.length && allowed()) {
948
974
  this.emitAttachments([...this.attachments, ...added]);
949
975
  }
950
976
  }
@@ -957,7 +983,7 @@ export class DeesHarnessComposer extends DeesElement {
957
983
  && (!this.busy || !this.inputSteeringEnabled || !this.value.trim())) return;
958
984
  const detail: IHarnessComposerSendDetail = {
959
985
  text: this.value,
960
- attachments: this.attachments,
986
+ attachments: this.attachmentsEnabled ? this.attachments : [],
961
987
  account: this.account || undefined,
962
988
  model: this.model || undefined,
963
989
  reasoningEffort: this.reasoningEffort || undefined,
@@ -15,7 +15,12 @@ import '../../00group-layout/dees-label/dees-label.js';
15
15
  * Provides unified margin system and layout mode support
16
16
  */
17
17
  export abstract class DeesInputBase<T = any> extends DeesElement {
18
- public static shadowRootOptions = { ...DeesElement.shadowRootOptions, delegatesFocus: true };
18
+ // Keep declarations tied to the superclass's DOM contract instead of emitting
19
+ // the build compiler's inferred (and potentially newer) ShadowRootInit shape.
20
+ public static override shadowRootOptions: typeof DeesElement.shadowRootOptions = {
21
+ ...DeesElement.shadowRootOptions,
22
+ delegatesFocus: true,
23
+ };
19
24
  public static inputLayout: 'field' | 'choice' | 'compound' = 'field';
20
25
  /**
21
26
  * Layout mode for the input component