feat: Implement unified input component architecture with standardized margins and layout modes
This commit is contained in:
314
readme.plan.md
314
readme.plan.md
@ -1,202 +1,174 @@
|
||||
# dees-appui-appbar Improvement Plan
|
||||
# Input Component Unification Plan
|
||||
|
||||
## Phase 1: Core Menu System
|
||||
Command to reread guidelines: `cat /home/philkunz/.claude/CLAUDE.md`
|
||||
|
||||
### Menu Data Structure
|
||||
- [x] Extend existing `plugins.tsclass.website.IMenuItem` to create `IAppBarMenuItem` with additional properties: id, shortcut, submenu, divider, disabled
|
||||
- [x] Create `IMenuBar` interface with menuItems array and onMenuSelect callback
|
||||
- [x] Add `@property() menuItems` to accept menu configuration
|
||||
- [x] Add `@property() onMenuSelect` event handler
|
||||
- [ ] Consider reusing existing `interfaces.ITab` for simpler menu scenarios
|
||||
## Problem Summary
|
||||
|
||||
### Basic Menu Rendering
|
||||
- [x] Replace hardcoded menu items with dynamic rendering from menuItems property
|
||||
- [x] Add support for menu item icons
|
||||
- [x] Implement menu item disabled state styling
|
||||
- [x] Add menu separator/divider support
|
||||
The dees-input components have inconsistent margin behavior causing vertical alignment issues in horizontal flexbox layouts:
|
||||
|
||||
### Dropdown Implementation
|
||||
- [x] Create dropdown container component (consider reusing logic from dees-contextmenu)
|
||||
- [x] Implement click to open/close dropdown
|
||||
- [x] Add dropdown positioning logic (below menu item)
|
||||
- [x] Implement click outside to close
|
||||
- [x] Add dropdown arrow/caret indicator
|
||||
- [x] Style dropdown with shadows and borders
|
||||
- [ ] Ensure visual consistency with existing dees-contextmenu component
|
||||
- **dees-input-text**: 8px top, 24px bottom margin
|
||||
- **dees-input-dropdown**: 0px top, 24px bottom margin
|
||||
- **dees-input-checkbox/radio**: 20px top, 20px bottom margin
|
||||
- Different components use different label implementations (some use dees-label, others have built-in labels)
|
||||
|
||||
### Keyboard Navigation
|
||||
- [x] Add tabindex to menu items
|
||||
- [x] Implement Tab navigation between top-level items
|
||||
- [x] Add Enter key to open dropdown
|
||||
- [x] Implement arrow keys for dropdown navigation
|
||||
- [x] Add Escape key to close dropdown
|
||||
- [x] Implement Home/End keys for first/last item
|
||||
## Proposed Solution
|
||||
|
||||
### Submenu Support
|
||||
- [ ] Detect submenu items and add arrow indicator
|
||||
- [ ] Implement submenu positioning (to the right)
|
||||
- [ ] Add hover delay before opening submenu
|
||||
- [ ] Handle nested keyboard navigation
|
||||
- [ ] Prevent submenus from going off-screen
|
||||
### 1. Standardize Margin System
|
||||
|
||||
## Phase 2: Breadcrumb Navigation & Theming
|
||||
Create a unified margin approach for all input components:
|
||||
|
||||
### Breadcrumb System
|
||||
- [ ] Define `IBreadcrumb` interface with label, path, icon
|
||||
- [x] Add `@property() breadcrumbs` array
|
||||
- [x] Add `@property() breadcrumbSeparator` (default '>')
|
||||
- [x] Implement breadcrumb rendering with separators
|
||||
- [x] Add click handlers for navigation
|
||||
- [x] Emit 'breadcrumb-navigate' custom event
|
||||
- [ ] Add breadcrumb truncation for long paths
|
||||
- [ ] Implement breadcrumb overflow with horizontal scroll
|
||||
```css
|
||||
/* Default vertical stacking mode (for forms) */
|
||||
:host {
|
||||
margin: 0;
|
||||
margin-bottom: 16px; /* Reduced from 24px for better density */
|
||||
}
|
||||
|
||||
### Theme Support
|
||||
- [x] Add CSS variables for all colors and sizes
|
||||
- [x] Create `--appbar-height` variable (default 40px)
|
||||
- [x] Add `--appbar-bg`, `--appbar-text`, `--appbar-border` variables
|
||||
- [x] Implement `--appbar-hover` and `--appbar-active` states
|
||||
- [x] Add `@property() theme` with 'light' | 'dark' options
|
||||
- [x] Create light theme CSS variables
|
||||
- [x] Add theme toggle to demo
|
||||
/* Last child in container should have no bottom margin */
|
||||
:host(:last-child) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
### Visual Improvements
|
||||
- [x] Add smooth transitions for hover states
|
||||
- [ ] Implement ripple effect on click
|
||||
- [x] Add focus ring styles for accessibility
|
||||
- [x] Improve menu item padding and spacing
|
||||
- [ ] Add subtle gradient or texture to appbar
|
||||
/* Horizontal layout mode - activated by parent context or attribute */
|
||||
:host([horizontal-layout]) {
|
||||
margin: 0;
|
||||
margin-right: 16px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
## Phase 3: Search & User Account
|
||||
:host([horizontal-layout]:last-child) {
|
||||
margin-right: 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Search Integration
|
||||
- [x] Add search icon in center section
|
||||
- [ ] Create expandable search input
|
||||
- [x] Add `@property() showSearch` boolean
|
||||
- [ ] Implement Cmd/Ctrl+K keyboard shortcut
|
||||
- [ ] Add search input with placeholder
|
||||
- [x] Emit 'search-submit' event
|
||||
- [ ] Add search suggestions dropdown
|
||||
- [ ] Implement recent searches storage
|
||||
### 2. Unified Label Architecture
|
||||
|
||||
### User Account Section
|
||||
- [ ] Define `IUserAccount` interface
|
||||
- [x] Add `@property() user` for user data
|
||||
- [x] Render user avatar (with fallback to initials)
|
||||
- [x] Display user name
|
||||
- [x] Add status indicator (online/offline/busy/away)
|
||||
- [ ] Create user dropdown menu
|
||||
- [ ] Add logout/settings options
|
||||
- [x] Emit 'user-menu-open' event
|
||||
All input components should use the `dees-label` component for consistency:
|
||||
|
||||
## Phase 4: Platform & Accessibility
|
||||
- Move label rendering from built-in implementations to `dees-label` usage
|
||||
- Add a `labelPosition` property to all inputs: `'top' | 'left' | 'right' | 'none'`
|
||||
- Default to 'top' for text/dropdown, 'right' for checkbox/radio
|
||||
|
||||
### Platform-Specific Features
|
||||
- [ ] Add `@property() platform` detection
|
||||
- [x] Conditionally show window controls
|
||||
- [ ] Implement platform-specific styling (macOS/Windows/Linux)
|
||||
- [ ] Add platform-specific keyboard shortcuts
|
||||
- [ ] Handle window dragging per platform
|
||||
- [ ] Add fullscreen toggle button
|
||||
### 3. Layout Mode Support
|
||||
|
||||
### Window Controls Integration
|
||||
- [ ] Make window controls position configurable
|
||||
- [x] Add `@property() showWindowControls`
|
||||
- [ ] Handle window controls on different platforms
|
||||
- [ ] Add minimize/maximize/close functionality
|
||||
- [ ] Style window controls to match theme
|
||||
Add a `layoutMode` property to all input components:
|
||||
|
||||
### Accessibility (A11Y)
|
||||
- [x] Add proper ARIA roles (menubar, menuitem)
|
||||
- [x] Implement aria-haspopup for dropdowns
|
||||
- [x] Add aria-expanded state
|
||||
- [ ] Include aria-label for navigation
|
||||
- [ ] Support screen reader announcements
|
||||
- [ ] Add high contrast mode support
|
||||
- [ ] Implement focus trap in dropdowns
|
||||
- [ ] Add skip navigation link
|
||||
```typescript
|
||||
@property({ type: String })
|
||||
public layoutMode: 'vertical' | 'horizontal' | 'auto' = 'auto';
|
||||
```
|
||||
|
||||
### Responsive Design
|
||||
- [ ] Add breakpoint detection
|
||||
- [ ] Implement hamburger menu for mobile
|
||||
- [ ] Create slide-out menu drawer
|
||||
- [ ] Make breadcrumbs responsive
|
||||
- [ ] Hide non-essential items on small screens
|
||||
- [ ] Add touch gesture support
|
||||
- `vertical`: Traditional form layout (label on top)
|
||||
- `horizontal`: Inline layout (label position configurable)
|
||||
- `auto`: Detect from parent context
|
||||
|
||||
## Phase 5: Advanced Features
|
||||
### 4. Implementation Steps
|
||||
|
||||
### Notification System
|
||||
- [ ] Add notification icon with badge
|
||||
- [ ] Create `@property() notifications` array
|
||||
- [ ] Implement notification dropdown
|
||||
- [ ] Add notification actions (mark read, dismiss)
|
||||
- [ ] Emit notification events
|
||||
- [ ] Add notification sound option
|
||||
- [ ] Implement notification grouping
|
||||
1. **Create base input class** (`DeesInputBase`):
|
||||
- Common margin styles
|
||||
- Layout mode detection
|
||||
- Label position handling
|
||||
- Shared properties (key, required, disabled, value)
|
||||
|
||||
### Plugin System
|
||||
- [ ] Define `IAppBarPlugin` interface
|
||||
- [ ] Add plugin registration method
|
||||
- [ ] Implement plugin rendering slots
|
||||
- [ ] Add plugin positioning (left/center/right)
|
||||
- [ ] Support plugin weight for ordering
|
||||
- [ ] Create plugin lifecycle hooks
|
||||
2. **Update dees-input-text**:
|
||||
- Extend from DeesInputBase
|
||||
- Remove hardcoded margins
|
||||
- Keep using dees-label component
|
||||
|
||||
### Custom Slots
|
||||
- [ ] Add named slots for sections
|
||||
- [ ] Implement slot change detection
|
||||
- [ ] Style slotted content appropriately
|
||||
- [ ] Document slot usage
|
||||
3. **Update dees-input-dropdown**:
|
||||
- Extend from DeesInputBase
|
||||
- Remove hardcoded margins
|
||||
- Switch from built-in label to dees-label
|
||||
|
||||
### Context Menus
|
||||
- [ ] Add right-click context menu support
|
||||
- [ ] Implement context menu positioning
|
||||
- [ ] Add context-specific menu items
|
||||
- [ ] Support custom context menus
|
||||
4. **Update dees-input-checkbox**:
|
||||
- Extend from DeesInputBase
|
||||
- Remove hardcoded margins
|
||||
- Add support for label position (keep default as 'right')
|
||||
- Switch to dees-label component
|
||||
|
||||
## Phase 6: Performance & Polish
|
||||
5. **Update dees-input-radio**:
|
||||
- Same as checkbox
|
||||
|
||||
### Performance Optimizations
|
||||
- [ ] Implement virtual scrolling for long menus
|
||||
- [ ] Add lazy loading for submenu content
|
||||
- [ ] Debounce search input
|
||||
- [ ] Memoize menu rendering
|
||||
- [ ] Optimize re-renders with lit's `guard` directive
|
||||
- [ ] Add loading states for async operations
|
||||
6. **Update dees-form**:
|
||||
- Add property to control child input layout mode
|
||||
- Ensure proper spacing context
|
||||
|
||||
### Testing
|
||||
- [x] Create comprehensive demo page
|
||||
- [ ] Add unit tests for menu logic
|
||||
- [ ] Test keyboard navigation
|
||||
- [ ] Test platform-specific behavior
|
||||
- [ ] Add visual regression tests
|
||||
- [ ] Test accessibility with screen readers
|
||||
- [ ] Performance benchmark tests
|
||||
### 5. CSS Variable System
|
||||
|
||||
### Documentation
|
||||
- [ ] Document all properties and methods
|
||||
- [x] Create usage examples
|
||||
- [ ] Add migration guide from current version
|
||||
- [ ] Document keyboard shortcuts
|
||||
- [ ] Create accessibility guide
|
||||
- [ ] Add troubleshooting section
|
||||
Introduce CSS variables for consistent spacing:
|
||||
|
||||
### Polish
|
||||
- [ ] Add loading skeletons
|
||||
- [ ] Implement error states
|
||||
- [ ] Add empty states
|
||||
- [ ] Create onboarding tooltips
|
||||
- [ ] Add animation preferences (reduced motion)
|
||||
- [ ] Implement print styles
|
||||
```css
|
||||
:host {
|
||||
--dees-input-spacing-unit: 8px;
|
||||
--dees-input-vertical-gap: calc(var(--dees-input-spacing-unit) * 2); /* 16px */
|
||||
--dees-input-horizontal-gap: calc(var(--dees-input-spacing-unit) * 2); /* 16px */
|
||||
--dees-input-label-gap: var(--dees-input-spacing-unit); /* 8px */
|
||||
}
|
||||
```
|
||||
|
||||
## Completion Tracking
|
||||
### 6. Backward Compatibility
|
||||
|
||||
- Phase 1: 20/22 tasks
|
||||
- Phase 2: 10/15 tasks
|
||||
- Phase 3: 6/16 tasks
|
||||
- Phase 4: 6/24 tasks
|
||||
- Phase 5: 0/18 tasks
|
||||
- Phase 6: 2/20 tasks
|
||||
- Keep existing properties and methods
|
||||
- Add deprecation notices for properties that will be removed
|
||||
- Provide migration guide in documentation
|
||||
|
||||
**Total: 44/115 tasks completed**
|
||||
### 7. Testing Requirements
|
||||
|
||||
- Test all inputs in vertical form layouts
|
||||
- Test all inputs in horizontal flexbox containers
|
||||
- Test mixed input types in same container
|
||||
- Test with and without labels
|
||||
- Test theme switching (light/dark)
|
||||
- Test responsive behavior
|
||||
|
||||
## Expected Outcome
|
||||
|
||||
- All input components will align properly in horizontal layouts
|
||||
- Consistent spacing in vertical forms
|
||||
- Unified label handling across all inputs
|
||||
- Better developer experience with predictable behavior
|
||||
- Maintained backward compatibility
|
||||
|
||||
## Timeline
|
||||
|
||||
1. Phase 1: Create DeesInputBase class and update dees-input-text ✅
|
||||
2. Phase 2: Update remaining input components ✅
|
||||
3. Phase 3: Update documentation and examples
|
||||
4. Phase 4: Testing and refinement ✅
|
||||
|
||||
## Implementation Status
|
||||
|
||||
### Completed:
|
||||
|
||||
1. **Created DeesInputBase class** (`dees-input-base.ts`):
|
||||
- Generic base class with unified margin system
|
||||
- Layout mode support (vertical/horizontal/auto)
|
||||
- Label position control
|
||||
- Common properties and methods
|
||||
- CSS variables for consistent spacing
|
||||
|
||||
2. **Updated all input components**:
|
||||
- `dees-input-text`: Now extends DeesInputBase, margins removed
|
||||
- `dees-input-dropdown`: Now extends DeesInputBase, uses dees-label
|
||||
- `dees-input-checkbox`: Now extends DeesInputBase, uses dees-label (default label position: right)
|
||||
- `dees-input-radio`: Now extends DeesInputBase, uses dees-label (default label position: right)
|
||||
|
||||
3. **Updated dees-form**:
|
||||
- Added `horizontal-layout` property
|
||||
- Auto-detection of layout mode for child inputs
|
||||
- Added dropdown to form input types
|
||||
|
||||
4. **Fixed TypeScript errors**:
|
||||
- Added value property to dropdown for form compatibility
|
||||
- Fixed changeSubject typing
|
||||
- Updated form value type to include dropdown options
|
||||
|
||||
### Result:
|
||||
|
||||
All input components now have:
|
||||
- Unified 16px bottom margin in vertical layouts
|
||||
- 16px right margin in horizontal layouts
|
||||
- No margin on last child
|
||||
- Consistent label handling via dees-label
|
||||
- Flexible layout modes
|
||||
- Better alignment in flexbox containers
|
@ -4,6 +4,7 @@ import {
|
||||
type TemplateResult,
|
||||
DeesElement,
|
||||
type CSSResult,
|
||||
property,
|
||||
} from '@design.estate/dees-element';
|
||||
import * as domtools from '@design.estate/dees-domtools';
|
||||
|
||||
@ -11,6 +12,7 @@ import { DeesInputCheckbox } from './dees-input-checkbox.js';
|
||||
import { DeesInputText } from './dees-input-text.js';
|
||||
import { DeesInputQuantitySelector } from './dees-input-quantityselector.js';
|
||||
import { DeesInputRadio } from './dees-input-radio.js';
|
||||
import { DeesInputDropdown } from './dees-input-dropdown.js';
|
||||
import { DeesFormSubmit } from './dees-form-submit.js';
|
||||
import { DeesTable } from './dees-table.js';
|
||||
import { demoFunc } from './dees-form.demo.js';
|
||||
@ -19,6 +21,7 @@ import { DeesInputIban } from './dees-input-iban.js';
|
||||
// Unified set for form input types
|
||||
const FORM_INPUT_TYPES = [
|
||||
DeesInputCheckbox,
|
||||
DeesInputDropdown,
|
||||
DeesInputIban,
|
||||
DeesInputText,
|
||||
DeesInputQuantitySelector,
|
||||
@ -28,6 +31,7 @@ const FORM_INPUT_TYPES = [
|
||||
|
||||
export type TFormInputElement =
|
||||
| DeesInputCheckbox
|
||||
| DeesInputDropdown
|
||||
| DeesInputIban
|
||||
| DeesInputText
|
||||
| DeesInputQuantitySelector
|
||||
@ -48,6 +52,13 @@ export class DeesForm extends DeesElement {
|
||||
public changeSubject = new domtools.plugins.smartrx.rxjs.Subject();
|
||||
public readyDeferred = domtools.plugins.smartpromise.defer();
|
||||
|
||||
/**
|
||||
* Controls the layout mode of child input components
|
||||
* When true, sets all child inputs to horizontal layout
|
||||
*/
|
||||
@property({ type: Boolean, reflect: true, attribute: 'horizontal-layout' })
|
||||
public horizontalLayout: boolean = false;
|
||||
|
||||
public render(): TemplateResult {
|
||||
return html`
|
||||
<style>
|
||||
@ -62,6 +73,7 @@ export class DeesForm extends DeesElement {
|
||||
public async firstUpdated() {
|
||||
const formChildren = this.getFormElements();
|
||||
this.updateRequiredStatus();
|
||||
this.updateChildrenLayoutMode();
|
||||
|
||||
for (const child of formChildren) {
|
||||
child.changeSubject.subscribe(async () => {
|
||||
@ -107,7 +119,7 @@ export class DeesForm extends DeesElement {
|
||||
*/
|
||||
public async collectFormData() {
|
||||
const children = this.getFormElements();
|
||||
const valueObject: { [key: string]: string | number | boolean | any[] } = {};
|
||||
const valueObject: { [key: string]: string | number | boolean | any[] | { option: string; key: string; payload?: any } } = {};
|
||||
for (const child of children) {
|
||||
if (!child.key) {
|
||||
console.log(`form element with label "${child.label}" has no key. skipping.`);
|
||||
@ -202,4 +214,28 @@ export class DeesForm extends DeesElement {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the layout mode of child input components based on form's horizontalLayout property
|
||||
*/
|
||||
private updateChildrenLayoutMode() {
|
||||
const formChildren = this.getFormElements();
|
||||
for (const child of formChildren) {
|
||||
if ('layoutMode' in child) {
|
||||
// The child's auto mode will detect this form's horizontal-layout attribute
|
||||
(child as any).layoutMode = 'auto';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when properties change
|
||||
*/
|
||||
updated(changedProperties: Map<string, any>) {
|
||||
super.updated(changedProperties);
|
||||
|
||||
if (changedProperties.has('horizontalLayout')) {
|
||||
this.updateChildrenLayoutMode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
184
ts_web/elements/dees-input-base.ts
Normal file
184
ts_web/elements/dees-input-base.ts
Normal file
@ -0,0 +1,184 @@
|
||||
import {
|
||||
DeesElement,
|
||||
property,
|
||||
css,
|
||||
type CSSResult,
|
||||
cssManager,
|
||||
} from '@design.estate/dees-element';
|
||||
import * as domtools from '@design.estate/dees-domtools';
|
||||
|
||||
/**
|
||||
* Base class for all dees-input components
|
||||
* Provides unified margin system and layout mode support
|
||||
*/
|
||||
export abstract class DeesInputBase<T = any> extends DeesElement {
|
||||
/**
|
||||
* Layout mode for the input component
|
||||
* - vertical: Traditional form layout (label on top)
|
||||
* - horizontal: Inline layout (label position configurable)
|
||||
* - auto: Detect from parent context
|
||||
*/
|
||||
@property({ type: String })
|
||||
public layoutMode: 'vertical' | 'horizontal' | 'auto' = 'auto';
|
||||
|
||||
/**
|
||||
* Position of the label relative to the input
|
||||
*/
|
||||
@property({ type: String })
|
||||
public labelPosition: 'top' | 'left' | 'right' | 'none' = 'top';
|
||||
|
||||
/**
|
||||
* Common properties for all inputs
|
||||
*/
|
||||
@property({ type: String })
|
||||
public key: string;
|
||||
|
||||
@property({ type: String })
|
||||
public label: string;
|
||||
|
||||
@property({ type: Boolean })
|
||||
public required: boolean = false;
|
||||
|
||||
@property({ type: Boolean })
|
||||
public disabled: boolean = false;
|
||||
|
||||
@property({ type: String })
|
||||
public description: string;
|
||||
|
||||
/**
|
||||
* Common styles for all input components
|
||||
*/
|
||||
public static get baseStyles(): CSSResult[] {
|
||||
return [
|
||||
css`
|
||||
/* CSS Variables for consistent spacing */
|
||||
:host {
|
||||
--dees-input-spacing-unit: 8px;
|
||||
--dees-input-vertical-gap: calc(var(--dees-input-spacing-unit) * 2); /* 16px */
|
||||
--dees-input-horizontal-gap: calc(var(--dees-input-spacing-unit) * 2); /* 16px */
|
||||
--dees-input-label-gap: var(--dees-input-spacing-unit); /* 8px */
|
||||
}
|
||||
|
||||
/* Default vertical stacking mode (for forms) */
|
||||
:host {
|
||||
display: block;
|
||||
margin: 0;
|
||||
margin-bottom: var(--dees-input-vertical-gap);
|
||||
}
|
||||
|
||||
/* Last child in container should have no bottom margin */
|
||||
:host(:last-child) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* Horizontal layout mode - activated by attribute */
|
||||
:host([layout-mode="horizontal"]) {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
margin-right: var(--dees-input-horizontal-gap);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
:host([layout-mode="horizontal"]:last-child) {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
/* Auto mode - inherit from parent dees-form if present */
|
||||
|
||||
/* Label position variations */
|
||||
:host([label-position="left"]) .input-wrapper {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: var(--dees-input-label-gap);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:host([label-position="right"]) .input-wrapper {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
gap: var(--dees-input-label-gap);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:host([label-position="top"]) .input-wrapper {
|
||||
display: block;
|
||||
}
|
||||
|
||||
:host([label-position="none"]) dees-label {
|
||||
display: none;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Subject for value changes that all inputs should implement
|
||||
*/
|
||||
public changeSubject = new domtools.plugins.smartrx.rxjs.Subject<T>();
|
||||
|
||||
/**
|
||||
* Called when the element is connected to the DOM
|
||||
* Sets up layout mode detection
|
||||
*/
|
||||
async connectedCallback() {
|
||||
await super.connectedCallback();
|
||||
this.detectLayoutMode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects the appropriate layout mode based on parent context
|
||||
*/
|
||||
private detectLayoutMode() {
|
||||
if (this.layoutMode !== 'auto') {
|
||||
this.setAttribute('layout-mode', this.layoutMode);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if parent is a form with horizontal layout
|
||||
const parentForm = this.closest('dees-form');
|
||||
if (parentForm && parentForm.hasAttribute('horizontal-layout')) {
|
||||
this.setAttribute('layout-mode', 'horizontal');
|
||||
} else {
|
||||
this.setAttribute('layout-mode', 'vertical');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the layout mode attribute when property changes
|
||||
*/
|
||||
updated(changedProperties: Map<string, any>) {
|
||||
super.updated(changedProperties);
|
||||
|
||||
if (changedProperties.has('layoutMode')) {
|
||||
this.detectLayoutMode();
|
||||
}
|
||||
|
||||
if (changedProperties.has('labelPosition')) {
|
||||
this.setAttribute('label-position', this.labelPosition);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard method for freezing input (disabling)
|
||||
*/
|
||||
public async freeze() {
|
||||
this.disabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard method for unfreezing input (enabling)
|
||||
*/
|
||||
public async unfreeze() {
|
||||
this.disabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract method that child classes must implement to get their value
|
||||
*/
|
||||
public abstract getValue(): any;
|
||||
|
||||
/**
|
||||
* Abstract method that child classes must implement to set their value
|
||||
*/
|
||||
public abstract setValue(value: any): void;
|
||||
}
|
@ -1,14 +1,13 @@
|
||||
import {
|
||||
customElement,
|
||||
DeesElement,
|
||||
type TemplateResult,
|
||||
property,
|
||||
html,
|
||||
css,
|
||||
cssManager,
|
||||
type CSSResult,
|
||||
} from '@design.estate/dees-element';
|
||||
import * as domtools from '@design.estate/dees-domtools';
|
||||
import { DeesInputBase } from './dees-input-base.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
@ -17,51 +16,33 @@ declare global {
|
||||
}
|
||||
|
||||
@customElement('dees-input-checkbox')
|
||||
export class DeesInputCheckbox extends DeesElement {
|
||||
export class DeesInputCheckbox extends DeesInputBase<DeesInputCheckbox> {
|
||||
// STATIC
|
||||
public static demo = () => html`<dees-input-checkbox></dees-input-checkbox>`;
|
||||
|
||||
// INSTANCE
|
||||
public changeSubject = new domtools.plugins.smartrx.rxjs.Subject();
|
||||
|
||||
@property({
|
||||
type: String,
|
||||
reflect: true,
|
||||
})
|
||||
public key: string;
|
||||
|
||||
@property({
|
||||
type: String,
|
||||
})
|
||||
public label: string = 'Label';
|
||||
|
||||
@property({
|
||||
type: Boolean,
|
||||
})
|
||||
public value: boolean = false;
|
||||
|
||||
@property({
|
||||
type: Boolean,
|
||||
})
|
||||
public required: boolean = false;
|
||||
|
||||
@property({
|
||||
type: Boolean
|
||||
})
|
||||
public disabled: boolean = false;
|
||||
constructor() {
|
||||
super();
|
||||
this.labelPosition = 'right'; // Checkboxes default to label on the right
|
||||
}
|
||||
|
||||
public render(): TemplateResult {
|
||||
return html`
|
||||
${domtools.elementBasic.styles}
|
||||
<style>
|
||||
public static styles = [
|
||||
...DeesInputBase.baseStyles,
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
position: relative;
|
||||
margin: 20px 0px;
|
||||
cursor: default;
|
||||
}
|
||||
:host(:hover) {
|
||||
@ -69,21 +50,12 @@ export class DeesInputCheckbox extends DeesElement {
|
||||
}
|
||||
|
||||
.maincontainer {
|
||||
display: grid;
|
||||
grid-template-columns: 25px auto;
|
||||
padding: 5px 0px;
|
||||
color: ${this.goBright ? '#333' : '#ccc'};
|
||||
color: ${cssManager.bdTheme('#333', '#ccc')};
|
||||
}
|
||||
|
||||
.maincontainer:hover {
|
||||
${this.goBright ? '#000' : '#ccc'};
|
||||
}
|
||||
|
||||
.label {
|
||||
margin-left: 15px;
|
||||
line-height: 25px;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
color: ${cssManager.bdTheme('#000', '#fff')};
|
||||
}
|
||||
|
||||
input:focus {
|
||||
@ -94,12 +66,12 @@ export class DeesInputCheckbox extends DeesElement {
|
||||
.checkbox {
|
||||
transition: all 0.1s;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid ${this.goBright ? '#CCC' : '#999'};
|
||||
border: 1px solid ${cssManager.bdTheme('#CCC', '#999')};
|
||||
border-radius: 2px;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
display: inline-block;
|
||||
background: ${this.goBright ? '#fafafa' : '#222'};
|
||||
background: ${cssManager.bdTheme('#fafafa', '#222')};
|
||||
}
|
||||
|
||||
.checkbox.selected {
|
||||
@ -146,19 +118,25 @@ export class DeesInputCheckbox extends DeesElement {
|
||||
img {
|
||||
padding: 4px;
|
||||
}
|
||||
</style>
|
||||
<div class="maincontainer" @click="${this.toggleSelected}">
|
||||
<div class="checkbox ${this.value ? 'selected' : ''} ${this.disabled ? 'disabled' : ''}" tabindex="0">
|
||||
${this.value
|
||||
? html`
|
||||
<span class="checkmark">
|
||||
<div class="checkmark_stem"></div>
|
||||
<div class="checkmark_kick"></div>
|
||||
</span>
|
||||
`
|
||||
: html``}
|
||||
`,
|
||||
];
|
||||
|
||||
public render(): TemplateResult {
|
||||
return html`
|
||||
<div class="input-wrapper">
|
||||
<div class="maincontainer" @click="${this.toggleSelected}">
|
||||
<div class="checkbox ${this.value ? 'selected' : ''} ${this.disabled ? 'disabled' : ''}" tabindex="0">
|
||||
${this.value
|
||||
? html`
|
||||
<span class="checkmark">
|
||||
<div class="checkmark_stem"></div>
|
||||
<div class="checkmark_kick"></div>
|
||||
</span>
|
||||
`
|
||||
: html``}
|
||||
</div>
|
||||
</div>
|
||||
<div class="label">${this.label}</div>
|
||||
<dees-label .label=${this.label}></dees-label>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@ -177,6 +155,14 @@ export class DeesInputCheckbox extends DeesElement {
|
||||
this.changeSubject.next(this);
|
||||
}
|
||||
|
||||
public getValue(): boolean {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public setValue(value: boolean): void {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
const checkboxDiv = this.shadowRoot.querySelector('.checkbox');
|
||||
if (checkboxDiv) {
|
||||
|
@ -1,17 +1,16 @@
|
||||
import {
|
||||
customElement,
|
||||
DeesElement,
|
||||
type TemplateResult,
|
||||
property,
|
||||
state,
|
||||
html,
|
||||
css,
|
||||
cssManager,
|
||||
type CSSResult,
|
||||
} from '@design.estate/dees-element';
|
||||
import * as domtools from '@design.estate/dees-domtools';
|
||||
import { demoFunc } from './dees-input-dropdown.demo.js';
|
||||
import { DeesWindowLayer } from './dees-windowlayer.js';
|
||||
import { DeesInputBase } from './dees-input-base.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
@ -20,20 +19,10 @@ declare global {
|
||||
}
|
||||
|
||||
@customElement('dees-input-dropdown')
|
||||
export class DeesInputDropdown extends DeesElement {
|
||||
export class DeesInputDropdown extends DeesInputBase<DeesInputDropdown> {
|
||||
public static demo = demoFunc;
|
||||
|
||||
// INSTANCE
|
||||
public changeSubject = new domtools.plugins.smartrx.rxjs.Subject();
|
||||
|
||||
@property({
|
||||
type: String,
|
||||
reflect: true,
|
||||
})
|
||||
public label: string = 'Label';
|
||||
|
||||
@property()
|
||||
public key: string;
|
||||
|
||||
@property()
|
||||
public options: { option: string; key: string; payload?: any }[] = [];
|
||||
@ -41,20 +30,21 @@ export class DeesInputDropdown extends DeesElement {
|
||||
@property()
|
||||
public selectedOption: { option: string; key: string; payload?: any } = null;
|
||||
|
||||
@property({
|
||||
type: Boolean,
|
||||
})
|
||||
public required: boolean = false;
|
||||
// Add value property for form compatibility
|
||||
public get value() {
|
||||
return this.selectedOption;
|
||||
}
|
||||
|
||||
public set value(val: { option: string; key: string; payload?: any }) {
|
||||
this.selectedOption = val;
|
||||
}
|
||||
|
||||
|
||||
@property({
|
||||
type: Boolean,
|
||||
})
|
||||
public enableSearch: boolean = true;
|
||||
|
||||
@property({
|
||||
type: Boolean,
|
||||
})
|
||||
public disabled: boolean = false;
|
||||
|
||||
@state()
|
||||
public opensToTop: boolean = false;
|
||||
@ -69,6 +59,7 @@ export class DeesInputDropdown extends DeesElement {
|
||||
public isOpened = false;
|
||||
|
||||
public static styles = [
|
||||
...DeesInputBase.baseStyles,
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
* {
|
||||
@ -78,19 +69,13 @@ export class DeesInputDropdown extends DeesElement {
|
||||
:host {
|
||||
font-family: Roboto;
|
||||
position: relative;
|
||||
display: block;
|
||||
color: ${cssManager.bdTheme('#222', '#fff')};
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.maincontainer {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 14px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.selectedBox {
|
||||
user-select: none;
|
||||
@ -205,9 +190,10 @@ export class DeesInputDropdown extends DeesElement {
|
||||
|
||||
public render(): TemplateResult {
|
||||
return html`
|
||||
<div class="maincontainer" @keydown="${this.isOpened ? this.handleKeyDown : undefined}">
|
||||
${this.label ? html`<div class="label">${this.label}</div>` : html``}
|
||||
<div class="selectionBox">
|
||||
<div class="input-wrapper">
|
||||
<dees-label .label=${this.label}></dees-label>
|
||||
<div class="maincontainer" @keydown="${this.isOpened ? this.handleKeyDown : undefined}">
|
||||
<div class="selectionBox">
|
||||
${this.enableSearch && !this.opensToTop
|
||||
? html`
|
||||
<div class="search top">
|
||||
@ -247,6 +233,7 @@ export class DeesInputDropdown extends DeesElement {
|
||||
}}"
|
||||
>
|
||||
${this.selectedOption?.option || 'Select...'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@ -372,4 +359,12 @@ export class DeesInputDropdown extends DeesElement {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
public getValue(): { option: string; key: string; payload?: any } {
|
||||
return this.selectedOption;
|
||||
}
|
||||
|
||||
public setValue(value: { option: string; key: string; payload?: any }): void {
|
||||
this.selectedOption = value;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import {customElement, DeesElement, type TemplateResult, property, html, type CSSResult,} from '@design.estate/dees-element';
|
||||
import {customElement, type TemplateResult, property, html, css, cssManager} from '@design.estate/dees-element';
|
||||
import * as domtools from '@design.estate/dees-domtools';
|
||||
import { DeesInputBase } from './dees-input-base.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
@ -8,55 +9,34 @@ declare global {
|
||||
}
|
||||
|
||||
@customElement('dees-input-radio')
|
||||
export class DeesInputRadio extends DeesElement {
|
||||
export class DeesInputRadio extends DeesInputBase<DeesInputRadio> {
|
||||
public static demo = () => html`<dees-input-radio></dees-input-radio>`;
|
||||
|
||||
// INSTANCE
|
||||
public changeSubject = new domtools.plugins.smartrx.rxjs.Subject();
|
||||
|
||||
@property({
|
||||
type: String,
|
||||
reflect: true,
|
||||
})
|
||||
public key: string;
|
||||
|
||||
@property()
|
||||
public label: string = 'Label';
|
||||
|
||||
@property()
|
||||
public value: boolean = false;
|
||||
|
||||
@property({
|
||||
type: Boolean,
|
||||
})
|
||||
public required: boolean = false;
|
||||
|
||||
@property({
|
||||
type: Boolean
|
||||
})
|
||||
public disabled: boolean = false;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.labelPosition = 'right'; // Radio buttons default to label on the right
|
||||
}
|
||||
|
||||
public render(): TemplateResult {
|
||||
return html `
|
||||
<style>
|
||||
public static styles = [
|
||||
...DeesInputBase.baseStyles,
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
position: relative;
|
||||
margin: 20px 0px;
|
||||
}
|
||||
|
||||
.maincontainer {
|
||||
transition: all 0.3s;
|
||||
display: grid;
|
||||
grid-template-columns: 25px auto;
|
||||
padding: 5px 0px;
|
||||
color: #ccc;
|
||||
}
|
||||
@ -65,14 +45,6 @@ export class DeesInputRadio extends DeesElement {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.label {
|
||||
margin-left: 15px;
|
||||
line-height: 25px;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
|
||||
}
|
||||
|
||||
input:focus {
|
||||
outline: none;
|
||||
border-bottom: 1px solid #e4002b;
|
||||
@ -106,12 +78,18 @@ export class DeesInputRadio extends DeesElement {
|
||||
height: 10px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
</style>
|
||||
<div class="maincontainer" @click="${this.toggleSelected}">
|
||||
<div class="checkbox ${this.value ? 'selected' : ''}">
|
||||
${this.value ? html`<div class="innercircle"></div>`: html``}
|
||||
`,
|
||||
];
|
||||
|
||||
public render(): TemplateResult {
|
||||
return html`
|
||||
<div class="input-wrapper">
|
||||
<div class="maincontainer" @click="${this.toggleSelected}">
|
||||
<div class="checkbox ${this.value ? 'selected' : ''}">
|
||||
${this.value ? html`<div class="innercircle"></div>`: html``}
|
||||
</div>
|
||||
</div>
|
||||
<div class="label">${this.label}</div>
|
||||
<dees-label .label=${this.label}></dees-label>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@ -124,4 +102,12 @@ export class DeesInputRadio extends DeesElement {
|
||||
}));
|
||||
this.changeSubject.next(this);
|
||||
}
|
||||
|
||||
public getValue(): boolean {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public setValue(value: boolean): void {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
@ -1,16 +1,14 @@
|
||||
import * as colors from './00colors.js';
|
||||
import { DeesInputBase } from './dees-input-base.js';
|
||||
|
||||
import {
|
||||
customElement,
|
||||
DeesElement,
|
||||
type TemplateResult,
|
||||
property,
|
||||
html,
|
||||
cssManager,
|
||||
css,
|
||||
type CSSResult,
|
||||
} from '@design.estate/dees-element';
|
||||
import * as domtools from '@design.estate/dees-domtools';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
@ -19,47 +17,19 @@ declare global {
|
||||
}
|
||||
|
||||
@customElement('dees-input-text')
|
||||
export class DeesInputText extends DeesElement {
|
||||
export class DeesInputText extends DeesInputBase {
|
||||
public static demo = () => html`
|
||||
<dees-input-text .label=${'this is a label'} .value=${'test'}></dees-input-text>
|
||||
<dees-input-text .isPasswordBool=${true}></dees-input-text>
|
||||
`;
|
||||
|
||||
// INSTANCE
|
||||
public changeSubject = new domtools.plugins.smartrx.rxjs.Subject<DeesInputText>();
|
||||
|
||||
@property({
|
||||
type: String,
|
||||
})
|
||||
public label: string;
|
||||
|
||||
@property({
|
||||
type: String,
|
||||
})
|
||||
public description: string;
|
||||
|
||||
@property({
|
||||
type: String,
|
||||
reflect: true,
|
||||
})
|
||||
public key: string;
|
||||
|
||||
@property({
|
||||
type: String,
|
||||
reflect: true,
|
||||
})
|
||||
public value: string = '';
|
||||
|
||||
@property({
|
||||
type: Boolean,
|
||||
})
|
||||
public required: boolean = false;
|
||||
|
||||
@property({
|
||||
type: Boolean,
|
||||
})
|
||||
public disabled: boolean = false;
|
||||
|
||||
@property({
|
||||
type: Boolean,
|
||||
reflect: true,
|
||||
@ -87,6 +57,7 @@ export class DeesInputText extends DeesElement {
|
||||
validationFunction: (value: string) => boolean;
|
||||
|
||||
public static styles = [
|
||||
...DeesInputBase.baseStyles,
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
* {
|
||||
@ -95,9 +66,6 @@ export class DeesInputText extends DeesElement {
|
||||
|
||||
:host {
|
||||
position: relative;
|
||||
display: grid;
|
||||
margin: 8px 0px;
|
||||
margin-bottom: 24px;
|
||||
z-index: auto;
|
||||
}
|
||||
|
||||
@ -193,33 +161,32 @@ export class DeesInputText extends DeesElement {
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
<div class="maincontainer">
|
||||
<div class="input-wrapper">
|
||||
<dees-label .label=${this.label} .description=${this.description}></dees-label>
|
||||
<input
|
||||
type="${this.isPasswordBool && !this.showPasswordBool ? 'password' : 'text'}"
|
||||
.value=${this.value}
|
||||
@input="${this.updateValue}"
|
||||
.disabled=${this.disabled}
|
||||
/>
|
||||
<div class="validationContainer">
|
||||
${this.validationText}
|
||||
<div class="maincontainer">
|
||||
<input
|
||||
type="${this.isPasswordBool && !this.showPasswordBool ? 'password' : 'text'}"
|
||||
.value=${this.value}
|
||||
@input="${this.updateValue}"
|
||||
.disabled=${this.disabled}
|
||||
/>
|
||||
<div class="validationContainer">
|
||||
${this.validationText}
|
||||
</div>
|
||||
${this.isPasswordBool
|
||||
? html`
|
||||
<div class="showPassword" @click=${this.togglePasswordView}>
|
||||
<dees-icon .iconFA=${this.showPasswordBool ? 'eye' : 'eyeSlash'}></dees-icon>
|
||||
</div>
|
||||
`
|
||||
: html``}
|
||||
</div>
|
||||
${this.isPasswordBool
|
||||
? html`
|
||||
<div class="showPassword" @click=${this.togglePasswordView}>
|
||||
<dees-icon .iconFA=${this.showPasswordBool ? 'eye' : 'eyeSlash'}></dees-icon>
|
||||
</div>
|
||||
`
|
||||
: html``}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
const input = this.shadowRoot.querySelector('input');
|
||||
input.addEventListener('input', (eventArg: InputEvent) => {
|
||||
|
||||
});
|
||||
// Input event handling is already done in updateValue method
|
||||
}
|
||||
|
||||
public async updateValue(eventArg: Event) {
|
||||
@ -228,16 +195,15 @@ export class DeesInputText extends DeesElement {
|
||||
this.changeSubject.next(this);
|
||||
}
|
||||
|
||||
public async freeze() {
|
||||
this.disabled = true;
|
||||
public getValue(): string {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public async unfreeze() {
|
||||
this.disabled = false;
|
||||
public setValue(value: string): void {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public async togglePasswordView() {
|
||||
const domtools = await this.domtoolsPromise;
|
||||
this.showPasswordBool = !this.showPasswordBool;
|
||||
console.log(`this.showPasswordBool is: ${this.showPasswordBool}`);
|
||||
}
|
||||
|
Reference in New Issue
Block a user