@design.estate/dees-catalog 9.8.0 → 10.0.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 (53) hide show
  1. package/dist_bundle/bundle.js +3971 -3756
  2. package/dist_bundle/bundle.js.map +1 -1
  3. package/dist_bundle/third-party-notices.md +783 -10
  4. package/dist_ts_web/00_commitinfo_data.js +2 -2
  5. package/dist_ts_web/elements/00actionerror.d.ts +32 -0
  6. package/dist_ts_web/elements/00actionerror.js +45 -0
  7. package/dist_ts_web/elements/00group-appui/dees-appui-tabs/dees-appui-tabs.d.ts +16 -0
  8. package/dist_ts_web/elements/00group-appui/dees-appui-tabs/dees-appui-tabs.js +40 -3
  9. package/dist_ts_web/elements/00group-dataview/dees-table/dees-table.d.ts +7 -0
  10. package/dist_ts_web/elements/00group-dataview/dees-table/dees-table.js +17 -17
  11. package/dist_ts_web/elements/00group-feedback/dees-hint/dees-hint.d.ts +14 -3
  12. package/dist_ts_web/elements/00group-feedback/dees-hint/dees-hint.demo.js +23 -4
  13. package/dist_ts_web/elements/00group-feedback/dees-hint/dees-hint.js +77 -5
  14. package/dist_ts_web/elements/00group-input/dees-input-text/dees-input-text.d.ts +23 -3
  15. package/dist_ts_web/elements/00group-input/dees-input-text/dees-input-text.js +71 -11
  16. package/dist_ts_web/elements/00group-input/index.d.ts +0 -1
  17. package/dist_ts_web/elements/00group-input/index.js +1 -2
  18. package/dist_ts_web/elements/00group-overlay/dees-modal/dees-modal.d.ts +7 -0
  19. package/dist_ts_web/elements/00group-overlay/dees-modal/dees-modal.js +13 -3
  20. package/dist_ts_web/elements/index.d.ts +1 -2
  21. package/dist_ts_web/elements/index.js +2 -4
  22. package/package.json +3 -3
  23. package/readme.md +71 -4
  24. package/scripts/check-third-party-notices.mjs +261 -0
  25. package/third-party-notices.md +783 -10
  26. package/ts_web/00_commitinfo_data.ts +1 -1
  27. package/ts_web/elements/00actionerror.ts +66 -0
  28. package/ts_web/elements/00group-appui/dees-appui-tabs/dees-appui-tabs.ts +38 -2
  29. package/ts_web/elements/00group-dataview/dees-table/dees-table.ts +18 -16
  30. package/ts_web/elements/00group-feedback/dees-hint/dees-hint.demo.ts +22 -3
  31. package/ts_web/elements/00group-feedback/dees-hint/dees-hint.ts +71 -3
  32. package/ts_web/elements/00group-input/dees-input-text/dees-input-text.ts +73 -13
  33. package/ts_web/elements/00group-input/index.ts +0 -1
  34. package/ts_web/elements/00group-overlay/dees-modal/dees-modal.ts +13 -2
  35. package/ts_web/elements/index.ts +1 -4
  36. package/dist_ts_web/elements/00group-input/dees-input-searchselect/dees-input-searchselect.d.ts +0 -1
  37. package/dist_ts_web/elements/00group-input/dees-input-searchselect/dees-input-searchselect.js +0 -2
  38. package/dist_ts_web/elements/00group-input/dees-input-searchselect/index.d.ts +0 -1
  39. package/dist_ts_web/elements/00group-input/dees-input-searchselect/index.js +0 -2
  40. package/dist_ts_web/elements/dees-search/dees-search.d.ts +0 -1
  41. package/dist_ts_web/elements/dees-search/dees-search.js +0 -2
  42. package/dist_ts_web/elements/dees-search/index.d.ts +0 -1
  43. package/dist_ts_web/elements/dees-search/index.js +0 -2
  44. package/dist_ts_web/elements/dees-tooltip/dees-tooltip.d.ts +0 -1
  45. package/dist_ts_web/elements/dees-tooltip/dees-tooltip.js +0 -2
  46. package/dist_ts_web/elements/dees-tooltip/index.d.ts +0 -1
  47. package/dist_ts_web/elements/dees-tooltip/index.js +0 -2
  48. package/ts_web/elements/00group-input/dees-input-searchselect/dees-input-searchselect.ts +0 -0
  49. package/ts_web/elements/00group-input/dees-input-searchselect/index.ts +0 -1
  50. package/ts_web/elements/dees-search/dees-search.ts +0 -0
  51. package/ts_web/elements/dees-search/index.ts +0 -1
  52. package/ts_web/elements/dees-tooltip/dees-tooltip.ts +0 -1
  53. package/ts_web/elements/dees-tooltip/index.ts +0 -1
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@design.estate/dees-catalog',
6
- version: '9.8.0',
6
+ version: '10.0.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
  }
@@ -0,0 +1,66 @@
1
+ import { DeesToast } from './00group-feedback/dees-toast/dees-toast.js';
2
+
3
+ /** Payload of the cancelable `dees-action-error` event. */
4
+ export interface IActionErrorDetail<TAction = unknown> {
5
+ /** Whatever the action callback rejected with. */
6
+ error: unknown;
7
+ /** The action descriptor whose callback rejected. */
8
+ action: TAction;
9
+ /**
10
+ * Where the action was invoked from. Catalog sources are `modal-menu`,
11
+ * `table-header`, `table-footer`, `table-row`, `table-rowmenu`,
12
+ * `table-contextmenu` and `table-doubleclick`.
13
+ */
14
+ source: string;
15
+ }
16
+
17
+ /** The event a component dispatches before it falls back to a toast. */
18
+ export const actionErrorEventName = 'dees-action-error';
19
+
20
+ /** Human-readable text for an unknown rejection value. */
21
+ export const actionErrorMessage = (errorArg: unknown): string => {
22
+ if (errorArg instanceof Error) return errorArg.message || errorArg.name;
23
+ if (typeof errorArg === 'string' && errorArg) return errorArg;
24
+ const text = String(errorArg);
25
+ return text === '[object Object]' || text === 'undefined' || text === 'null' ? 'The action failed.' : text;
26
+ };
27
+
28
+ /**
29
+ * Announces a failed action on `hostArg` and, unless a listener cancels the
30
+ * event, surfaces it through the catalog's own notification primitive. The
31
+ * event is cancelable so an application can render its own error surface.
32
+ */
33
+ export const reportActionError = <TAction>(
34
+ hostArg: HTMLElement,
35
+ detailArg: IActionErrorDetail<TAction>,
36
+ ownerElementArg?: Node | null,
37
+ ): void => {
38
+ const handled = !hostArg.dispatchEvent(new CustomEvent<IActionErrorDetail<TAction>>(actionErrorEventName, {
39
+ detail: detailArg,
40
+ bubbles: true,
41
+ composed: true,
42
+ cancelable: true,
43
+ }));
44
+ if (handled) return;
45
+ void DeesToast.show({
46
+ message: actionErrorMessage(detailArg.error),
47
+ type: 'error',
48
+ ownerElement: ownerElementArg ?? undefined,
49
+ });
50
+ };
51
+
52
+ /**
53
+ * Awaits an action callback so a rejected promise becomes a visible failure
54
+ * instead of an unhandled rejection. Successful actions resolve unchanged.
55
+ */
56
+ export const runAction = async <TAction>(
57
+ hostArg: HTMLElement,
58
+ detailArg: { action: TAction; source: string; ownerElement?: Node | null },
59
+ runArg: () => unknown,
60
+ ): Promise<void> => {
61
+ try {
62
+ await runArg();
63
+ } catch (error) {
64
+ reportActionError(hostArg, { error, action: detailArg.action, source: detailArg.source }, detailArg.ownerElement);
65
+ }
66
+ };
@@ -442,7 +442,7 @@ export class DeesAppuiTabs extends DeesElement {
442
442
  }
443
443
 
444
444
  private renderTab(tab: interfaces.IMenuItem, _isHorizontal: boolean): TemplateResult {
445
- const isSelected = tab === this.selectedTab;
445
+ const isSelected = this.isTabSelected(tab);
446
446
  const label = tab.label ?? tab.key;
447
447
  return html`<div class="tab ${isSelected ? 'selectedTab' : ''} ${isSelected && this.showTabIndicator ? 'has-indicator' : ''}" role="presentation">
448
448
  <button type="button" class="tab-select" role="tab" title=${label} aria-selected=${isSelected} tabindex=${isSelected ? 0 : -1}
@@ -494,6 +494,29 @@ export class DeesAppuiTabs extends DeesElement {
494
494
  return tab.iconName ? html`<dees-icon .icon=${tab.iconName}></dees-icon>` : '';
495
495
  }
496
496
 
497
+ /**
498
+ * The stable identity of a tab descriptor. `key` is the documented
499
+ * identifier; `label` is the fallback for descriptors that carry only a
500
+ * display name.
501
+ */
502
+ private tabKey(tabArg: interfaces.IMenuItem | null | undefined): string | undefined {
503
+ return tabArg ? (tabArg.key ?? tabArg.label) : undefined;
504
+ }
505
+
506
+ /** The current descriptor for a selection, matched by key rather than by object identity. */
507
+ private findTabByKey(tabArg: interfaces.IMenuItem | null): interfaces.IMenuItem | undefined {
508
+ const key = this.tabKey(tabArg);
509
+ if (key === undefined) return this.tabs.find((tab) => tab === tabArg);
510
+ return this.tabs.find((tab) => this.tabKey(tab) === key);
511
+ }
512
+
513
+ private isTabSelected(tabArg: interfaces.IMenuItem): boolean {
514
+ if (!this.selectedTab) return false;
515
+ if (tabArg === this.selectedTab) return true;
516
+ const key = this.tabKey(tabArg);
517
+ return key !== undefined && key === this.tabKey(this.selectedTab);
518
+ }
519
+
497
520
  private selectTab(tabArg: interfaces.IMenuItem) {
498
521
  this.selectedTab = tabArg;
499
522
  tabArg.action();
@@ -612,6 +635,19 @@ export class DeesAppuiTabs extends DeesElement {
612
635
  }
613
636
  }
614
637
 
638
+ /**
639
+ * A host that binds a literal `.tabs=${[...]}` hands us new descriptor
640
+ * objects on every render. The selection follows the descriptor key, so the
641
+ * open view survives a rerender; adopting the fresh object here keeps index
642
+ * lookups (close, scroll-into-view) working without re-running the action.
643
+ */
644
+ public willUpdate(changedProperties: Map<string, any>) {
645
+ super.willUpdate(changedProperties);
646
+ if (!changedProperties.has('tabs') || !this.selectedTab) return;
647
+ const current = this.findTabByKey(this.selectedTab);
648
+ if (current && current !== this.selectedTab) this.selectedTab = current;
649
+ }
650
+
615
651
  async updated(changedProperties: Map<string, any>) {
616
652
  super.updated(changedProperties);
617
653
 
@@ -626,7 +662,7 @@ export class DeesAppuiTabs extends DeesElement {
626
662
  composed: true,
627
663
  }));
628
664
  }
629
- } else if (this.selectedTab && !this.tabs.includes(this.selectedTab)) {
665
+ } else if (this.selectedTab && !this.findTabByKey(this.selectedTab)) {
630
666
  // Selected tab was removed => select first available
631
667
  this.selectTab(this.tabs[0]);
632
668
  } else if (!this.selectedTab) {
@@ -28,6 +28,7 @@ import {
28
28
  } from './data.js';
29
29
  import { compileLucenePredicate } from './lucene.js';
30
30
  import { themeDefaultStyles } from '../../00theme.js';
31
+ import { runAction } from '../../00actionerror.js';
31
32
  import '../../00group-layout/dees-tile/dees-tile.js';
32
33
 
33
34
  export type { Column, ISortDescriptor, ITableAction, ITableActionDataArg, TDisplayFunction } from './types.js';
@@ -614,12 +615,7 @@ export class DeesTable<T> extends DeesElement {
614
615
  ${headerActions.map(
615
616
  (action) => html`<button type="button"
616
617
  class="headerAction control"
617
- @click=${() => {
618
- action.actionFunc({
619
- item: this.selectedDataRow,
620
- table: this,
621
- });
622
- }}
618
+ @click=${() => this.runDataAction(action, this.selectedDataRow, 'table-header')}
623
619
  >
624
620
  ${action.iconName
625
621
  ? html`<dees-icon aria-hidden="true" .iconSize=${14} .icon=${action.iconName}></dees-icon>
@@ -755,12 +751,7 @@ export class DeesTable<T> extends DeesElement {
755
751
  ${footerActions.map(
756
752
  (action) => html`<button type="button"
757
753
  class="footerAction control"
758
- @click=${() => {
759
- action.actionFunc({
760
- item: this.selectedDataRow,
761
- table: this,
762
- });
763
- }}
754
+ @click=${() => this.runDataAction(action, this.selectedDataRow, 'table-footer')}
764
755
  >
765
756
  ${action.iconName
766
757
  ? html`<dees-icon aria-hidden="true" .iconSize=${14} .icon=${action.iconName}></dees-icon>
@@ -1586,7 +1577,7 @@ export class DeesTable<T> extends DeesElement {
1586
1577
  return html`<td class="actionsCol"><div class="actionsContainer">
1587
1578
  ${relevant.slice(0, 3).map((action) => html`<button type="button" class="action control primaryRowAction"
1588
1579
  title=${action.name} aria-label=${action.name} @keydown=${this.handleMenuKey}
1589
- @click=${() => action.actionFunc({ item, table: this })}>
1580
+ @click=${() => this.runDataAction(action, item, 'table-row')}>
1590
1581
  ${action.iconName ? html`<dees-icon aria-hidden="true" .icon=${action.iconName} iconSize="16"></dees-icon>` : action.name}
1591
1582
  </button>`)}
1592
1583
  ${overflow.length ? menuButton(overflow, 'primaryRowAction', 'More actions') : ''}
@@ -1597,7 +1588,7 @@ export class DeesTable<T> extends DeesElement {
1597
1588
  private openRowActionsMenu(event: MouseEvent, item: T, actions: ITableAction<T>[]) {
1598
1589
  void DeesContextmenu.openContextMenuWithOptions(event, actions.map((action) => ({
1599
1590
  name: action.name, iconName: action.iconName,
1600
- action: async () => { await action.actionFunc({ item, table: this }); return null; },
1591
+ action: async () => { await this.runDataAction(action, item, 'table-rowmenu'); return null; },
1601
1592
  })));
1602
1593
  }
1603
1594
 
@@ -2145,7 +2136,7 @@ export class DeesTable<T> extends DeesElement {
2145
2136
  action.type?.includes('doubleClick')
2146
2137
  && this.isActionRelevant(action, cell.item)
2147
2138
  );
2148
- if (dblAction) dblAction.actionFunc({ item: cell.item, table: this });
2139
+ if (dblAction) void this.runDataAction(dblAction, cell.item, 'table-doubleclick');
2149
2140
  };
2150
2141
 
2151
2142
  private __onTbodyMousedown = (eventArg: MouseEvent) => {
@@ -2178,7 +2169,7 @@ export class DeesTable<T> extends DeesElement {
2178
2169
  name: action.name,
2179
2170
  iconName: action.iconName as any,
2180
2171
  action: async () => {
2181
- await action.actionFunc({ item, table: this });
2172
+ await this.runDataAction(action, item, 'table-contextmenu');
2182
2173
  return null;
2183
2174
  },
2184
2175
  })
@@ -2361,6 +2352,17 @@ export class DeesTable<T> extends DeesElement {
2361
2352
  );
2362
2353
  }
2363
2354
 
2355
+ /**
2356
+ * Runs a data action and awaits it, so a rejected callback surfaces as a
2357
+ * visible failure instead of an unhandled rejection. The error is announced
2358
+ * on the table through the cancelable `dees-action-error` event and, unless
2359
+ * a listener cancels it, shown as a notification.
2360
+ */
2361
+ private async runDataAction(actionArg: ITableAction<T>, itemArg: T, sourceArg: string): Promise<void> {
2362
+ await runAction(this, { action: actionArg, source: sourceArg, ownerElement: this }, () =>
2363
+ actionArg.actionFunc({ item: itemArg, table: this }));
2364
+ }
2365
+
2364
2366
  private isActionRelevant(actionArg: ITableAction<T>, itemArg: T): boolean {
2365
2367
  return (
2366
2368
  !actionArg.actionRelevancyCheckFunc
@@ -1,5 +1,24 @@
1
1
  import { html } from '@design.estate/dees-element';
2
+ import { demoPage, demoSection, demoCode } from '../../../demos/templates.js';
2
3
 
3
- export const demoFunc = () => html`
4
- <dees-hint></dees-hint>
5
- `;
4
+ export const demoFunc = () => demoPage('Hint', 'An inline note next to the thing it explains. Four types carry the catalog status colors; a plain sentence or slotted markup both work.', html`
5
+ ${demoSection('Types', 'The type chooses the icon and the accent color. Info is the default.', html`
6
+ <dees-hint text="Workspace names are visible to everyone you invite."></dees-hint>
7
+ <dees-hint type="success" text="Your changes are saved and shared with your team."></dees-hint>
8
+ <dees-hint type="warning" text="This workspace has no owner. Assign one before the trial ends."></dees-hint>
9
+ <dees-hint type="error" text="The deployment could not reach the registry."></dees-hint>
10
+ ${demoCode('<dees-hint type="warning" text="Assign an owner before the trial ends."></dees-hint>')}
11
+ `)}
12
+ ${demoSection('Slotted content', 'Use the default slot when the note needs markup. Text and slotted content render together.', html`
13
+ <dees-hint type="info">Read the <a href="https://code.foss.global/design.estate/dees-catalog">component index</a> before adding a control.</dees-hint>
14
+ <dees-hint type="warning" text="Rotation is due.">
15
+ <strong> Keys older than 90 days stop working.</strong>
16
+ </dees-hint>
17
+ ${demoCode('<dees-hint type="info">Read the <a href="…">component index</a> first.</dees-hint>')}
18
+ `)}
19
+ ${demoSection('Empty hints', 'A hint with neither text nor slotted content reserves no space, so a conditional note needs no wrapper.', html`
20
+ <p class="dees-demo-caption">There is an empty hint between these two lines.</p>
21
+ <dees-hint></dees-hint>
22
+ <p class="dees-demo-caption">It renders nothing until it is given text.</p>
23
+ `)}
24
+ `);
@@ -5,13 +5,14 @@ import {
5
5
  customElement,
6
6
  html,
7
7
  property,
8
- type CSSResult,
8
+ state,
9
9
  type TemplateResult,
10
10
  } from '@design.estate/dees-element';
11
11
 
12
12
  import * as domtools from '@design.estate/dees-domtools';
13
13
  import { demoFunc } from './dees-hint.demo.js';
14
14
  import { themeDefaultStyles } from '../../00theme.js';
15
+ import '../../00group-utility/dees-icon/dees-icon.js';
15
16
 
16
17
  declare global {
17
18
  interface HTMLElementTagNameMap {
@@ -19,13 +20,34 @@ declare global {
19
20
  }
20
21
  }
21
22
 
23
+ export type THintType = 'info' | 'success' | 'warning' | 'error';
24
+
25
+ const hintIcons: Record<THintType, string> = {
26
+ info: 'lucide:Info',
27
+ success: 'lucide:CircleCheck',
28
+ warning: 'lucide:TriangleAlert',
29
+ error: 'lucide:CircleAlert',
30
+ };
31
+
32
+ /**
33
+ * An inline advisory note next to the thing it explains. Use `text` for a plain
34
+ * sentence and the default slot for markup; both render when both are set.
35
+ * For a hover-revealed label on a control, use the control's own tooltip.
36
+ */
22
37
  @customElement('dees-hint')
23
38
  export class DeesHint extends DeesElement {
24
39
  public static demo = demoFunc;
25
40
  public static demoGroups = ['Feedback'];
26
41
 
42
+ @property({ type: String, reflect: true })
43
+ accessor type: THintType = 'info';
44
+
45
+ /** The hint sentence. Rendered before any slotted content. */
27
46
  @property({ type: String })
28
- accessor type: 'info' | 'warn' | 'error' | 'critical' = 'info';
47
+ accessor text: string = '';
48
+
49
+ @state()
50
+ private accessor hasSlottedContent = false;
29
51
 
30
52
  constructor() {
31
53
  super();
@@ -37,14 +59,60 @@ export class DeesHint extends DeesElement {
37
59
  display: block;
38
60
  font-family: var(--dees-font-family);
39
61
  color: var(--dees-color-text-primary);
62
+ --dees-hint-accent: var(--dees-color-accent-primary);
40
63
  }
41
64
 
65
+ :host([type='success']) { --dees-hint-accent: var(--dees-color-text-success); }
66
+ :host([type='warning']) { --dees-hint-accent: var(--dees-color-text-warning); }
67
+ :host([type='error']) { --dees-hint-accent: var(--dees-color-text-error); }
68
+
42
69
  .mainbox {
43
70
  box-sizing: border-box;
71
+ display: flex;
72
+ align-items: flex-start;
73
+ gap: var(--dees-spacing-sm);
74
+ padding: var(--dees-spacing-sm) var(--dees-spacing-md);
75
+ border: 1px solid color-mix(in srgb, var(--dees-hint-accent) 28%, transparent);
76
+ border-radius: var(--dees-radius-lg);
77
+ corner-shape: var(--dees-corner-shape);
78
+ background: color-mix(in srgb, var(--dees-hint-accent) 8%, transparent);
79
+ font-size: var(--dees-font-footnote-size);
80
+ line-height: var(--dees-font-footnote-line);
81
+ overflow-wrap: anywhere;
82
+ }
83
+
84
+ /* An element with neither text nor slotted content reserves no space. */
85
+ .mainbox.empty {
86
+ display: none;
87
+ }
88
+
89
+ .icon {
90
+ flex-shrink: 0;
91
+ color: var(--dees-hint-accent);
92
+ font-size: 16px;
93
+ /* Optically aligns the glyph with the first line of text. */
94
+ margin-top: 1px;
95
+ }
96
+
97
+ .content {
98
+ min-width: 0;
99
+ color: var(--dees-color-text-secondary);
44
100
  }
45
101
  `];
46
102
 
47
103
  public render(): TemplateResult {
48
- return html` <div class="mainbox"></div> `;
104
+ const isEmpty = !this.text && !this.hasSlottedContent;
105
+ return html`
106
+ <div class="mainbox ${isEmpty ? 'empty' : ''}" role="note">
107
+ <dees-icon class="icon" aria-hidden="true" .icon=${hintIcons[this.type] ?? hintIcons.info}></dees-icon>
108
+ <div class="content">${this.text}<slot @slotchange=${this.handleSlotChange}></slot></div>
109
+ </div>
110
+ `;
49
111
  }
112
+
113
+ private handleSlotChange = (eventArg: Event) => {
114
+ const assigned = (eventArg.target as HTMLSlotElement).assignedNodes({ flatten: true });
115
+ this.hasSlottedContent = assigned.some((node) =>
116
+ node.nodeType === Node.ELEMENT_NODE || (node.textContent ?? '').trim().length > 0);
117
+ };
50
118
  }
@@ -21,17 +21,44 @@ declare global {
21
21
  }
22
22
  }
23
23
 
24
+ /** Native input types this field maps to. Anything richer has its own element. */
25
+ export type TTextInputType = 'text' | 'number' | 'date' | 'email' | 'url' | 'tel' | 'password';
26
+
27
+ /** Keyboard hint per type; `inputMode` on the host still wins. */
28
+ const defaultInputModes: Record<TTextInputType, string> = {
29
+ text: 'text',
30
+ number: 'decimal',
31
+ date: 'text',
32
+ email: 'email',
33
+ url: 'url',
34
+ tel: 'tel',
35
+ password: 'text',
36
+ };
37
+
24
38
  @customElement('dees-input-text')
25
39
  export class DeesInputText extends DeesInputBase {
26
40
  public static demo = demoFunc;
27
41
  public static demoGroups = ['Input'];
28
42
 
29
43
  // INSTANCE
44
+ /**
45
+ * The native input type. `number` keeps the field's value numeric, so a form
46
+ * collects a number rather than a numeric string; every other type keeps the
47
+ * value a string. `password` masks the value; `isPasswordBool` additionally
48
+ * renders the reveal button.
49
+ */
50
+ @property({
51
+ type: String,
52
+ reflect: true,
53
+ })
54
+ accessor type: TTextInputType = 'text';
55
+
56
+ /** A string for every type except `number`, which holds a number or null while empty. */
30
57
  @property({
31
58
  type: String,
32
59
  reflect: true,
33
60
  })
34
- accessor value: string = '';
61
+ accessor value: string | number | null = '';
35
62
 
36
63
  @property({
37
64
  type: Boolean,
@@ -315,8 +342,8 @@ export class DeesInputText extends DeesInputBase {
315
342
  <div class="maincontainer">
316
343
  <input
317
344
  class=${this.vintegrated ? '' : 'field-control'}
318
- type="${this.isPasswordBool && !this.showPasswordBool ? 'password' : 'text'}"
319
- .value=${this.value}
345
+ type="${this.nativeInputType}"
346
+ .value=${this.valueAsText}
320
347
  @input="${this.updateValue}"
321
348
  .disabled=${this.disabled}
322
349
  ?required=${this.required}
@@ -324,7 +351,7 @@ export class DeesInputText extends DeesInputBase {
324
351
  aria-invalid=${this.validationState === 'invalid' ? 'true' : 'false'}
325
352
  aria-describedby="field-description field-validation"
326
353
  placeholder=${this.placeholder || (this.label ? '' : 'Enter text…')}
327
- inputmode=${this.inputMode || 'text'}
354
+ inputmode=${this.inputMode || defaultInputModes[this.type] || 'text'}
328
355
  />
329
356
  ${this.isPasswordBool
330
357
  ? html`
@@ -352,6 +379,39 @@ export class DeesInputText extends DeesInputBase {
352
379
  `;
353
380
  }
354
381
 
382
+ /** The type handed to the native input; masking wins over the declared type. */
383
+ private get nativeInputType(): string {
384
+ if (this.isPasswordBool || this.type === 'password') {
385
+ return this.showPasswordBool ? 'text' : 'password';
386
+ }
387
+ return this.type;
388
+ }
389
+
390
+ /** The value as text, for validators, the context menu and the native element. */
391
+ private get valueAsText(): string {
392
+ return this.value === null || this.value === undefined ? '' : String(this.value);
393
+ }
394
+
395
+ /**
396
+ * Keeps `value` in the shape the current type promises, whatever a host
397
+ * binds: a number (or null while empty) for `number`, a string otherwise.
398
+ */
399
+ private normalizeValue(valueArg: string | number | null | undefined): string | number | null {
400
+ if (this.type !== 'number') {
401
+ return valueArg === null || valueArg === undefined ? '' : String(valueArg);
402
+ }
403
+ if (valueArg === null || valueArg === undefined || valueArg === '') return null;
404
+ const parsed = typeof valueArg === 'number' ? valueArg : Number(valueArg);
405
+ return Number.isNaN(parsed) ? null : parsed;
406
+ }
407
+
408
+ public willUpdate(changedProperties: Map<PropertyKey, unknown>): void {
409
+ super.willUpdate(changedProperties);
410
+ if (!changedProperties.has('value') && !changedProperties.has('type')) return;
411
+ const normalized = this.normalizeValue(this.value);
412
+ if (normalized !== this.value) this.value = normalized;
413
+ }
414
+
355
415
  /**
356
416
  * `autocomplete` is applied imperatively rather than bound in the template so that the
357
417
  * default renders no attribute at all. Binding it would emit `autocomplete=""` on every
@@ -372,8 +432,8 @@ export class DeesInputText extends DeesInputBase {
372
432
  }
373
433
 
374
434
  firstUpdated() {
375
- if (this.validationFunction && this.value) {
376
- const result = this.validationFunction(this.value);
435
+ if (this.validationFunction && this.value !== null && this.value !== '') {
436
+ const result = this.validationFunction(this.valueAsText);
377
437
  this.validationState = result.valid ? 'valid' : 'invalid';
378
438
  this.validationText = result.message || '';
379
439
  if (result.valid) {
@@ -387,10 +447,10 @@ export class DeesInputText extends DeesInputBase {
387
447
  }
388
448
 
389
449
  public async updateValue(eventArg: Event) {
390
- const target: any = eventArg.target;
391
- this.value = target.value;
450
+ const target = eventArg.target as HTMLInputElement;
451
+ this.value = this.normalizeValue(target.value);
392
452
  if (this.validationFunction) {
393
- const result = this.validationFunction(this.value);
453
+ const result = this.validationFunction(this.valueAsText);
394
454
  this.validationState = result.valid ? 'valid' : 'invalid';
395
455
  this.validationText = result.message || '';
396
456
  if (result.valid) {
@@ -411,16 +471,16 @@ export class DeesInputText extends DeesInputBase {
411
471
  public getContextMenuItems() {
412
472
  return textInputContextMenuItems({
413
473
  input: this.shadowRoot!.querySelector('input')!, disabled: this.disabled,
414
- commit: value => { this.value = value; this.changeSubject.next(this); },
474
+ commit: value => { this.value = this.normalizeValue(value); this.changeSubject.next(this); },
415
475
  });
416
476
  }
417
477
 
418
- public getValue(): string {
478
+ public getValue(): string | number | null {
419
479
  return this.value;
420
480
  }
421
481
 
422
- public setValue(value: string): void {
423
- this.value = value;
482
+ public setValue(value: string | number | null): void {
483
+ this.value = this.normalizeValue(value);
424
484
  }
425
485
 
426
486
  public async togglePasswordView() {
@@ -12,7 +12,6 @@ export * from './dees-input-phone/index.js';
12
12
  export * from './dees-input-quantityselector/index.js';
13
13
  export * from './dees-input-radiogroup/index.js';
14
14
  export * from './dees-input-richtext/index.js';
15
- export * from './dees-input-searchselect/index.js';
16
15
  export * from './dees-input-tags/index.js';
17
16
  export * from './dees-input-text/index.js';
18
17
  export * from './dees-input-textarea/index.js';
@@ -1,5 +1,6 @@
1
1
  import * as plugins from '../../00plugins.js';
2
2
  import { zIndexRegistry } from '../../00zindex.js';
3
+ import { runAction } from '../../00actionerror.js';
3
4
 
4
5
  import { demoFunc } from './dees-modal.demo.js';
5
6
  import {
@@ -49,7 +50,7 @@ export class DeesModal extends DeesElement {
49
50
  let modal: DeesModal;
50
51
  const confirm = async () => {
51
52
  if (modal.promptPending) return;
52
- const value = input.value.trim();
53
+ const value = String(input.value ?? '').trim();
53
54
  const error = value ? options.validate?.(value) : 'Enter a name.';
54
55
  if (error) {
55
56
  input.validationState = 'invalid'; input.validationText = error;
@@ -332,7 +333,7 @@ export class DeesModal extends DeesElement {
332
333
  <dees-button class="modal-action" .text=${action.name}
333
334
  .disabled=${this.promptPending}
334
335
  .type=${index === this.menuOptions.length - 1 ? 'accent' : 'secondary'}
335
- @clicked=${() => action.action(this)}></dees-button>
336
+ @clicked=${() => this.runMenuAction(action)}></dees-button>
336
337
  `)}
337
338
  </div>
338
339
  ` : ''}
@@ -385,4 +386,14 @@ export class DeesModal extends DeesElement {
385
386
  private async handleHelp() {
386
387
  if (this.onHelp) await this.onHelp();
387
388
  }
389
+
390
+ /**
391
+ * A menu action is awaited so a rejected callback surfaces as a visible
392
+ * failure instead of an unhandled rejection. The error is announced on the
393
+ * modal through the cancelable `dees-action-error` event and, unless a
394
+ * listener cancels it, shown as a notification inside this dialog.
395
+ */
396
+ private async runMenuAction(actionArg: plugins.tsclass.website.IMenuItem<DeesModal>) {
397
+ await runAction(this, { action: actionArg, source: 'modal-menu', ownerElement: this.dialog }, () => actionArg.action(this));
398
+ }
388
399
  }
@@ -1,3 +1,4 @@
1
+ export * from './00actionerror.js';
1
2
  export * from './00zindex.js';
2
3
  export * from './00theme.js';
3
4
 
@@ -17,7 +18,3 @@ export * from './00group-runtime/index.js';
17
18
  export * from './00group-simple/index.js';
18
19
  export * from './00group-utility/index.js';
19
20
  export * from './00group-workspace/index.js';
20
-
21
- // Standalone Components
22
- export * from './dees-search/index.js';
23
- export * from './dees-tooltip/index.js';
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVlcy1pbnB1dC1zZWFyY2hzZWxlY3QuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi90c193ZWIvZWxlbWVudHMvMDBncm91cC1pbnB1dC9kZWVzLWlucHV0LXNlYXJjaHNlbGVjdC9kZWVzLWlucHV0LXNlYXJjaHNlbGVjdC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiIn0=
@@ -1 +0,0 @@
1
- export * from './dees-input-searchselect.js';
@@ -1,2 +0,0 @@
1
- export * from './dees-input-searchselect.js';
2
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi90c193ZWIvZWxlbWVudHMvMDBncm91cC1pbnB1dC9kZWVzLWlucHV0LXNlYXJjaHNlbGVjdC9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjLDhCQUE4QixDQUFDIn0=
@@ -1 +0,0 @@
1
- export {};
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVlcy1zZWFyY2guanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90c193ZWIvZWxlbWVudHMvZGVlcy1zZWFyY2gvZGVlcy1zZWFyY2gudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiJ9
@@ -1 +0,0 @@
1
- export * from './dees-search.js';
@@ -1,2 +0,0 @@
1
- export * from './dees-search.js';
2
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90c193ZWIvZWxlbWVudHMvZGVlcy1zZWFyY2gvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsY0FBYyxrQkFBa0IsQ0FBQyJ9
@@ -1 +0,0 @@
1
- export {};
@@ -1,2 +0,0 @@
1
- import {} from '@design.estate/dees-element';
2
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGVlcy10b29sdGlwLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vdHNfd2ViL2VsZW1lbnRzL2RlZXMtdG9vbHRpcC9kZWVzLXRvb2x0aXAudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLE1BQU0sNkJBQTZCLENBQUMifQ==
@@ -1 +0,0 @@
1
- export * from './dees-tooltip.js';
@@ -1,2 +0,0 @@
1
- export * from './dees-tooltip.js';
2
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90c193ZWIvZWxlbWVudHMvZGVlcy10b29sdGlwL2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLGNBQWMsbUJBQW1CLENBQUMifQ==
@@ -1 +0,0 @@
1
- export * from './dees-input-searchselect.js';
File without changes
@@ -1 +0,0 @@
1
- export * from './dees-search.js';
@@ -1 +0,0 @@
1
- import {} from '@design.estate/dees-element';
@@ -1 +0,0 @@
1
- export * from './dees-tooltip.js';