-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
dd0c588
commit 80fd954
Showing
41 changed files
with
1,713 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './tree.component'; | ||
export * from './tree.gen'; |
239 changes: 239 additions & 0 deletions
239
angular/bootstrap/src/components/tree/tree.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,239 @@ | ||
import type {SlotContent} from '@agnos-ui/angular-headless'; | ||
import {BaseWidgetDirective, callWidgetFactory, ComponentTemplate, SlotDirective, UseDirective} from '@agnos-ui/angular-headless'; | ||
import { | ||
ChangeDetectionStrategy, | ||
Component, | ||
ContentChild, | ||
Directive, | ||
EventEmitter, | ||
inject, | ||
Input, | ||
Output, | ||
TemplateRef, | ||
ViewChild, | ||
} from '@angular/core'; | ||
import type {TreeContext, TreeItem, TreeSlotItemContext, TreeWidget} from './tree.gen'; | ||
import {createTree} from './tree.gen'; | ||
|
||
@Directive({selector: 'ng-template[auTreeStructure]', standalone: true}) | ||
export class TreeStructureDirective { | ||
public templateRef = inject(TemplateRef<TreeContext>); | ||
static ngTemplateContextGuard(_dir: TreeStructureDirective, context: unknown): context is TreeContext { | ||
return true; | ||
} | ||
} | ||
|
||
@Component({ | ||
standalone: true, | ||
changeDetection: ChangeDetectionStrategy.OnPush, | ||
imports: [UseDirective, TreeStructureDirective, SlotDirective], | ||
template: ` | ||
<ng-template autTreeStructure #structure let-state="state" let-directives="directives" let-api="api"> | ||
<ul role="tree" class="au-tree {{ state.className() }}" [auUse]="directives.navigationDirective"> | ||
@for (node of state.normalizedNodes(); track node) { | ||
<ng-template [auSlot]="state.root()" [auSlotProps]="{state, api, directives, item: node}"></ng-template> | ||
} | ||
</ul> | ||
</ng-template> | ||
`, | ||
}) | ||
export class TreeDefaultStructureSlotComponent { | ||
@ViewChild('structure', {static: true}) readonly structure!: TemplateRef<TreeContext>; | ||
} | ||
|
||
export const treeDefaultSlotStructure = new ComponentTemplate(TreeDefaultStructureSlotComponent, 'structure'); | ||
|
||
@Directive({selector: 'ng-template[auTreeToggle]', standalone: true}) | ||
export class TreeToggleDirective { | ||
public templateRef = inject(TemplateRef<TreeSlotItemContext>); | ||
static ngTemplateContextGuard(_dir: TreeToggleDirective, context: unknown): context is TreeSlotItemContext { | ||
return true; | ||
} | ||
} | ||
|
||
@Component({ | ||
standalone: true, | ||
changeDetection: ChangeDetectionStrategy.OnPush, | ||
imports: [UseDirective, TreeToggleDirective], | ||
template: ` | ||
<ng-template auTreeToggle #toggle let-state="state" let-directives="directives" let-api="api" let-item="item"> | ||
@if (item.children!.length > 0) { | ||
<button [auUse]="[directives.itemToggleDirective, {item}]"></button> | ||
} @else { | ||
<span class="au-tree-expand-icon-placeholder"></span> | ||
} | ||
</ng-template> | ||
`, | ||
}) | ||
export class TreeDefaultToggleSlotComponent { | ||
@ViewChild('toggle', {static: true}) readonly toggle!: TemplateRef<TreeSlotItemContext>; | ||
} | ||
|
||
export const treeDefaultSlotToggle = new ComponentTemplate(TreeDefaultToggleSlotComponent, 'toggle'); | ||
|
||
@Directive({selector: 'ng-template[auTreeItem]', standalone: true}) | ||
export class TreeItemDirective { | ||
public templateRef = inject(TemplateRef<TreeSlotItemContext>); | ||
static ngTemplateContextGuard(_dir: TreeItemDirective, context: unknown): context is TreeSlotItemContext { | ||
return true; | ||
} | ||
} | ||
|
||
@Component({ | ||
standalone: true, | ||
changeDetection: ChangeDetectionStrategy.OnPush, | ||
imports: [UseDirective, SlotDirective, TreeItemDirective], | ||
template: ` | ||
<ng-template auTreeItem #treeItem let-state="state" let-directives="directives" let-item="item" let-api="api"> | ||
<span class="au-tree-item"> | ||
<ng-template [auSlot]="state.toggle()" [auSlotProps]="{state, api, directives, item}"></ng-template> | ||
{{ item.label }} | ||
</span> | ||
</ng-template> | ||
`, | ||
}) | ||
export class TreeDefaultItemSlotComponent { | ||
@ViewChild('treeItem', {static: true}) readonly treeItem!: TemplateRef<TreeSlotItemContext>; | ||
} | ||
|
||
export const treeDefaultSlotItem = new ComponentTemplate(TreeDefaultItemSlotComponent, 'treeItem'); | ||
|
||
@Directive({selector: 'ng-template[auTreeRoot]', standalone: true}) | ||
export class TreeRootDirective { | ||
public templateRef = inject(TemplateRef<TreeSlotItemContext>); | ||
static ngTemplateContextGuard(_dir: TreeRootDirective, context: unknown): context is TreeSlotItemContext { | ||
return true; | ||
} | ||
} | ||
|
||
@Component({ | ||
standalone: true, | ||
changeDetection: ChangeDetectionStrategy.OnPush, | ||
imports: [UseDirective, SlotDirective, TreeRootDirective], | ||
template: ` | ||
<ng-template auTreeRoot #treeRoot let-state="state" let-directives="directives" let-item="item" let-api="api"> | ||
<li [auUse]="[directives.itemAttributesDirective, {item}]"> | ||
<ng-template [auSlot]="state.item()" [auSlotProps]="{state, api, directives, item}"></ng-template> | ||
@if (state.expandedMap().get(item)) { | ||
<ul role="group"> | ||
@for (child of item.children; track child) { | ||
<ng-template [auSlot]="state.root()" [auSlotProps]="{state, api, directives, item: child}"></ng-template> | ||
} | ||
</ul> | ||
} | ||
</li> | ||
</ng-template> | ||
`, | ||
}) | ||
export class TreeDefaultRootSlotComponent { | ||
@ViewChild('treeRoot', {static: true}) readonly treeRoot!: TemplateRef<TreeSlotItemContext>; | ||
} | ||
|
||
export const treeDefaultSlotRoot = new ComponentTemplate(TreeDefaultRootSlotComponent, 'treeRoot'); | ||
|
||
@Component({ | ||
selector: '[auTree]', | ||
standalone: true, | ||
imports: [UseDirective, SlotDirective], | ||
template: ` <ng-template [auSlot]="state.structure()" [auSlotProps]="{state, api, directives}"></ng-template> `, | ||
}) | ||
export class TreeComponent extends BaseWidgetDirective<TreeWidget> { | ||
constructor() { | ||
super( | ||
callWidgetFactory({ | ||
factory: createTree, | ||
widgetName: 'tree', | ||
defaultConfig: { | ||
structure: treeDefaultSlotStructure, | ||
root: treeDefaultSlotRoot, | ||
item: treeDefaultSlotItem, | ||
toggle: treeDefaultSlotToggle, | ||
}, | ||
events: { | ||
onExpandToggle: (item: TreeItem) => this.expandToggle.emit(item), | ||
}, | ||
slotTemplates: () => ({ | ||
structure: this.slotStructureFromContent?.templateRef, | ||
root: this.slotRootFromContent?.templateRef, | ||
item: this.slotItemFromContent?.templateRef, | ||
toggle: this.slotToggleFromContent?.templateRef, | ||
}), | ||
}), | ||
); | ||
} | ||
/** | ||
* Optional accessibility label for the tree if there is no explicit label | ||
* | ||
* @defaultValue `''` | ||
*/ | ||
@Input('auAriaLabel') ariaLabel: string | undefined; | ||
/** | ||
* Array of the tree nodes to display | ||
* | ||
* @defaultValue `[]` | ||
*/ | ||
@Input('auNodes') nodes: TreeItem[] | undefined; | ||
/** | ||
* CSS classes to be applied on the widget main container | ||
* | ||
* @defaultValue `''` | ||
*/ | ||
@Input('auClassName') className: string | undefined; | ||
/** | ||
* Retrieves expand items of the TreeItem | ||
* | ||
* @param node - HTML element that is representing the expand item | ||
* | ||
* @defaultValue | ||
* ```ts | ||
* (node: HTMLElement) => node.querySelectorAll('button') | ||
* ``` | ||
*/ | ||
@Input('auNavSelector') navSelector: ((node: HTMLElement) => NodeListOf<HTMLButtonElement>) | undefined; | ||
/** | ||
* Return the value for the 'aria-label' attribute of the toggle | ||
* @param label - tree item label | ||
* | ||
* @defaultValue | ||
* ```ts | ||
* (label: string) => `Toggle ${label}` | ||
* ``` | ||
*/ | ||
@Input('auAriaLabelToggleFn') ariaLabelToggleFn: ((label: string) => string) | undefined; | ||
|
||
/** | ||
* An event emitted when the user toggles the expand of the TreeItem. | ||
* | ||
* Event payload is equal to the TreeItem clicked. | ||
* | ||
* @defaultValue | ||
* ```ts | ||
* () => {} | ||
* ``` | ||
*/ | ||
@Output('auExpandToggle') expandToggle = new EventEmitter<TreeItem>(); | ||
|
||
/** | ||
* Slot to change the default tree item | ||
*/ | ||
@Input('auItem') item: SlotContent<TreeSlotItemContext>; | ||
@ContentChild(TreeItemDirective, {static: false}) slotItemFromContent: TreeItemDirective | undefined; | ||
|
||
/** | ||
* Slot to change the default display of the tree | ||
*/ | ||
@Input('auStructure') structure: SlotContent<TreeContext>; | ||
@ContentChild(TreeStructureDirective, {static: false}) slotStructureFromContent: TreeStructureDirective | undefined; | ||
|
||
/** | ||
* Slot to change the default tree item toggle | ||
*/ | ||
@Input('auToggle') toggle: SlotContent<TreeSlotItemContext>; | ||
@ContentChild(TreeToggleDirective, {static: false}) slotToggleFromContent: TreeToggleDirective | undefined; | ||
|
||
/** | ||
* Slot to change the default tree root | ||
*/ | ||
@Input('auRoot') root: SlotContent<TreeSlotItemContext>; | ||
@ContentChild(TreeRootDirective, {static: false}) slotRootFromContent: TreeRootDirective | undefined; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
41 changes: 41 additions & 0 deletions
41
angular/demo/bootstrap/src/app/samples/tree/basic.route.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import type {TreeItem} from '@agnos-ui/angular-bootstrap'; | ||
import {AgnosUIAngularModule} from '@agnos-ui/angular-bootstrap'; | ||
import {Component} from '@angular/core'; | ||
|
||
@Component({ | ||
standalone: true, | ||
template: ` <au-component auTree [auNodes]="nodes"></au-component> `, | ||
imports: [AgnosUIAngularModule], | ||
}) | ||
export default class BasicTreeComponent { | ||
nodes: TreeItem[] = [ | ||
{ | ||
label: 'Node 1', | ||
isExpanded: true, | ||
ariaLabel: 'Node 1', | ||
children: [ | ||
{ | ||
label: 'Node 1.1', | ||
isExpanded: false, | ||
ariaLabel: 'Node 1.1', | ||
children: [ | ||
{ | ||
label: 'Node 1.1.1', | ||
ariaLabel: 'Node 1.1.1', | ||
}, | ||
], | ||
}, | ||
{ | ||
label: 'Node 1.2', | ||
ariaLabel: 'Node 1.2', | ||
children: [ | ||
{ | ||
label: 'Node 1.2.1', | ||
ariaLabel: 'Node 1.2.1', | ||
}, | ||
], | ||
}, | ||
], | ||
}, | ||
]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './tree'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import type {TreeProps as CoreProps, TreeState as CoreState, TreeApi, TreeDirectives, TreeItem} from '@agnos-ui/core/components/tree'; | ||
import {createTree as createCoreTree, getTreeDefaultConfig as getCoreDefaultConfig} from '@agnos-ui/core/components/tree'; | ||
import {extendWidgetProps} from '@agnos-ui/core/services/extendWidget'; | ||
import type {SlotContent, Widget, WidgetFactory, WidgetSlotContext} from '@agnos-ui/core/types'; | ||
|
||
export * from '@agnos-ui/core/components/tree'; | ||
|
||
/** | ||
* Represents the context for a Tree widget. | ||
* This interface is an alias for `WidgetSlotContext<TreeWidget>`. | ||
*/ | ||
export type TreeContext = WidgetSlotContext<TreeWidget>; | ||
/** | ||
* Represents the context for a tree item, extending the base `TreeContext` | ||
* with an additional `item` property. | ||
*/ | ||
export type TreeSlotItemContext = TreeContext & {item: TreeItem}; | ||
|
||
interface TreeExtraProps { | ||
/** | ||
* Slot to change the default display of the tree | ||
*/ | ||
structure: SlotContent<TreeContext>; | ||
/** | ||
* Slot to change the default tree root | ||
*/ | ||
root: SlotContent<TreeSlotItemContext>; | ||
/** | ||
* Slot to change the default tree item | ||
*/ | ||
item: SlotContent<TreeSlotItemContext>; | ||
/** | ||
* Slot to change the default tree item toggle | ||
*/ | ||
toggle: SlotContent<TreeSlotItemContext>; | ||
} | ||
|
||
/** | ||
* Represents the state of a Tree component. | ||
*/ | ||
export interface TreeState extends CoreState, TreeExtraProps {} | ||
/** | ||
* Represents the properties for the Tree component. | ||
*/ | ||
export interface TreeProps extends CoreProps, TreeExtraProps {} | ||
/** | ||
* Represents a Tree widget component. | ||
*/ | ||
export type TreeWidget = Widget<TreeProps, TreeState, TreeApi, TreeDirectives>; | ||
|
||
const defaultConfigExtraProps: TreeExtraProps = { | ||
structure: undefined, | ||
root: undefined, | ||
item: undefined, | ||
toggle: undefined, | ||
}; | ||
|
||
/** | ||
* Retrieve a shallow copy of the default Tree config | ||
* @returns the default Tree config | ||
*/ | ||
export function getTreeDefaultConfig(): TreeProps { | ||
return {...getCoreDefaultConfig(), ...defaultConfigExtraProps}; | ||
} | ||
|
||
/** | ||
* Create a Tree with given config props | ||
* @param config - an optional tree config | ||
* @returns a TreeWidget | ||
*/ | ||
export const createTree: WidgetFactory<TreeWidget> = extendWidgetProps(createCoreTree, defaultConfigExtraProps, { | ||
structure: undefined, | ||
root: undefined, | ||
item: undefined, | ||
toggle: undefined, | ||
}); |
Oops, something went wrong.