Skip to content

Commit

Permalink
feat: svelte service examples using runes (#966)
Browse files Browse the repository at this point in the history
* feat: svelte service examples using runes

* fix: coffee progressbar
  • Loading branch information
quentinderoubaix authored Oct 23, 2024
1 parent ebd20ac commit d8971b3
Show file tree
Hide file tree
Showing 41 changed files with 323 additions and 219 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {UseDirective, toAngularSignal} from '@agnos-ui/angular-bootstrap';
import {activeElement$, createHasFocus} from '@agnos-ui/core';
import {ChangeDetectionStrategy, Component, computed, effect, signal} from '@angular/core';
import {ChangeDetectionStrategy, Component, effect, signal} from '@angular/core';

@Component({
standalone: true,
Expand All @@ -18,17 +18,31 @@ import {ChangeDetectionStrategy, Component, computed, effect, signal} from '@ang
<input class="form-check-input" type="checkbox" id="containerHasFocus" [checked]="hasFocus()" disabled />
<label class="form-check-label" for="containerHasFocus">Focus in container</label>
</div>
<label for="activeElementHistory" class="form-label">Active element history:</label>
<textarea class="form-control mb-2" id="activeElementHistory" readonly>{{ activeElementsJson() }}</textarea>
<button class="btn btn-primary" (click)="clear()">Clear</button>
<div class="d-flex justify-content-between">
<div>Active element history:</div>
<button class="btn btn-sm btn-primary" (click)="clear()">Clear</button>
</div>
<div class="card my-2">
<div class="card-body">
<ul class="mb-0">
@for (element of activeElements(); track element) {
<li>
<strong>{{ element.tagName }}</strong>
@if (element.id; as id) {
with id <strong>{{ id }}</strong>
}
</li>
}
</ul>
</div>
</div>
</div>
`,
})
export default class FocustrackComponent {
public readonly hasFocusApi = createHasFocus();
public readonly hasFocus = toAngularSignal(this.hasFocusApi.hasFocus$);
public readonly activeElements = signal<{tagName?: string; id?: string}[]>([]);
public readonly activeElementsJson = computed(() => JSON.stringify(this.activeElements()));
private readonly activeElement = toAngularSignal(activeElement$);

constructor() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {MatchMediaService} from './matchMedia.service';
template: `
<div class="mb-2">Resize the window to see the message below updated.</div>
<au-component auAlert auDismissible="false" auType="info">
Window is above 768 px ? <strong>{{ aboveMd() ? 'YES' : 'NO' }}</strong>
Window width is above 768 px ? <strong>{{ aboveMd() ? 'YES' : 'NO' }}</strong>
</au-component>
`,
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import {ProgressbarComponent, provideWidgetsConfig} from '@agnos-ui/angular-bootstrap';
import type {OnDestroy} from '@angular/core';
import {Component, signal} from '@angular/core';
import {Component, inject, signal} from '@angular/core';
import type {Subscription} from 'rxjs';
import {interval, takeWhile} from 'rxjs';
import CoffeeProgressbarComponent from './cofee-progressbar.component';
import {DomSanitizer} from '@angular/platform-browser';
import playSvg from 'bootstrap-icons/icons/play-fill.svg';
import pauseSvg from 'bootstrap-icons/icons/pause-fill.svg';
import stopSvg from 'bootstrap-icons/icons/stop-fill.svg';

@Component({
standalone: true,
Expand All @@ -15,16 +19,21 @@ import CoffeeProgressbarComponent from './cofee-progressbar.component';
<div auProgressbar #progressbar [auValue]="value()"></div>
</div>
<div class="d-flex flex-column justify-content-evenly h-100 ms-5">
<div class="btn-group" role="group">
<button class="btn btn-outline-primary" (click)="start()" [disabled]="progressbar.state().started">Start</button>
<div class="d-flex gap-1">
<button
class="btn btn-outline-primary"
[disabled]="!progressbar.state().started || progressbar.state().finished"
class="btn btn-primary d-flex align-items-center"
[disabled]="value() >= 100"
(click)="toggleProgress()"
>
{{ subscription ? 'Pause' : 'Resume' }}
</button>
<button class="btn btn-outline-primary" [disabled]="!progressbar.state().started" (click)="stop(true)">Reset</button>
[attr.aria-label]="subscription ? 'pause' : 'play'"
[innerHTML]="subscription ? pauseSvg : playSvg"
></button>
<button
class="btn btn-primary d-flex align-items-center"
[disabled]="!subscription"
(click)="stop(true)"
aria-label="stop"
[innerHTML]="stopSvg"
></button>
</div>
<p class="mt-3">
<span>{{ !subscription ? 'Need to wake up.' : value() < 100 ? 'Retrieving coffee... ' + value() + '%' : 'Ready to work !' }}</span>
Expand All @@ -35,6 +44,11 @@ import CoffeeProgressbarComponent from './cofee-progressbar.component';
styles: "@import '@agnos-ui/common/samples/progressbar/custom.scss';",
})
export default class FullCustomProgressBarComponent implements OnDestroy {
readonly sanitizer = inject(DomSanitizer);
readonly playSvg = this.sanitizer.bypassSecurityTrustHtml(playSvg);
readonly pauseSvg = this.sanitizer.bypassSecurityTrustHtml(pauseSvg);
readonly stopSvg = this.sanitizer.bypassSecurityTrustHtml(stopSvg);

readonly value = signal(0);
subscription: Subscription | undefined;

Expand Down
5 changes: 1 addition & 4 deletions demo/src/lib/layout/Dropdown.svelte
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
<script lang="ts">
<script lang="ts" generics="Item extends import('./dropdown').DropdownItem">
import {createHasFocus} from '@agnos-ui/svelte-bootstrap/services/focustrack';
import type {DropdownItem} from './dropdown';
import {fromStore} from 'svelte/store';
import type {Snippet} from 'svelte';
let open = $state(false);
type Item = $$Generic<DropdownItem>;
const {hasFocus$, directive} = createHasFocus();
const focus = fromStore(hasFocus$);
$effect(() => {
Expand Down
16 changes: 2 additions & 14 deletions e2e/demo-po/focustrack.po.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
import {BasePO} from '@agnos-ui/base-po';

interface ActiveElement {
tagName: string;
id?: string;
}

export class FocusTrackPO extends BasePO {
getComponentSelector(): string {
return 'div.demo-focustrack';
Expand All @@ -31,17 +26,10 @@ export class FocusTrackPO extends BasePO {
return this.locatorRoot.locator('#disabledInput');
}

/**
* Textarea containing the activeElements history
*/
get locatorTextareaHistory() {
return this.locatorRoot.locator('#activeElementHistory');
}

async getState(): Promise<{activeElements: ActiveElement[]; isInContainer: boolean}> {
async getState(): Promise<{activeElements: string[]; isInContainer: boolean}> {
return await this.locatorRoot.evaluate((root) => {
return {
activeElements: JSON.parse((root.querySelector('#activeElementHistory') as HTMLTextAreaElement).value),
activeElements: [...root.querySelectorAll('li')].map((el) => el.textContent!.trim()),
isInContainer: (root.querySelector('#containerHasFocus') as HTMLInputElement).checked,
};
});
Expand Down
8 changes: 4 additions & 4 deletions e2e/focustrack/focustrack.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,26 @@ test.describe(`Select tests`, () => {
await focustrackPO.waitLoaded();

const expectedState: State = {
activeElements: [{tagName: 'body'}],
activeElements: ['body'],
isInContainer: false,
};

await expect.poll(() => focustrackPO.getState()).toEqual(expectedState);

await focustrackPO.locatorFocusableInput.click();

expectedState.activeElements.push({tagName: 'input', id: 'focusableInput'});
expectedState.activeElements.push('input with id focusableInput');
expectedState.isInContainer = true;
await expect.poll(() => focustrackPO.getState()).toEqual(expectedState);

await focustrackPO.locatorOtherFocusableInput.click();

expectedState.activeElements.push({tagName: 'input', id: 'otherFocusableInput'});
expectedState.activeElements.push('input with id otherFocusableInput');
await expect.poll(() => focustrackPO.getState()).toEqual(expectedState);

await focustrackPO.locatorDisabledInput.click({force: true});

expectedState.activeElements.push({tagName: 'body'});
expectedState.activeElements.push('body');
expectedState.isInContainer = false;
await expect.poll(() => focustrackPO.getState()).toEqual(expectedState);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,24 +53,35 @@ <h5>
"Focus in container"
</label>
</div>
<label
class="form-label"
for="activeElementHistory"
>
"Active element history:"
</label>
<textarea
class="form-control mb-2"
id="activeElementHistory"
readonly="readonly"
<div
class="d-flex justify-content-between"
>
"[{\"tagName\":\"body\"}]"
</textarea>
<button
class="btn btn-primary"
<div>
"Active element history:"
</div>
<button
class="btn btn-primary btn-sm"
>
"Clear"
</button>
</div>
<div
class="card my-2"
>
"Clear"
</button>
<div
class="card-body"
>
<ul
class="mb-0"
>
<li>
<strong>
"body"
</strong>
</li>
</ul>
</div>
</div>
</div>
</div>
</body>
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<div
class="alert-body"
>
"Window is above 768 px ?"
"Window width is above 768 px ?"
<strong>
"YES"
</strong>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,25 +39,42 @@
class="d-flex flex-column h-100 justify-content-evenly ms-5"
>
<div
class="btn-group"
role="group"
class="d-flex gap-1"
>
<button
class="btn btn-outline-primary"
aria-label="play"
class="align-items-center btn btn-primary d-flex"
>
"Start"
</button>
<button
class="btn btn-outline-primary"
disabled="disabled"
>
"Resume"
<svg
class="bi bi-play-fill"
fill="currentColor"
height="16"
viewBox="0 0 16 16"
width="16"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="m11.596 8.697-6.363 3.692c-.54.313-1.233-.066-1.233-.697V4.308c0-.63.692-1.01 1.233-.696l6.363 3.692a.802.802 0 0 1 0 1.393"
/>
</svg>
</button>
<button
class="btn btn-outline-primary"
aria-label="stop"
class="align-items-center btn btn-primary d-flex"
disabled="disabled"
>
"Reset"
<svg
class="bi bi-stop-fill"
fill="currentColor"
height="16"
viewBox="0 0 16 16"
width="16"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M5 3.5h6A1.5 1.5 0 0 1 12.5 5v6a1.5 1.5 0 0 1-1.5 1.5H5A1.5 1.5 0 0 1 3.5 11V5A1.5 1.5 0 0 1 5 3.5"
/>
</svg>
</button>
</div>
<p
Expand Down
15 changes: 6 additions & 9 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -300,15 +300,12 @@ export default tseslint.config(
files: ['svelte/demo/src/bootstrap/**'],
rules: {
'@agnos-ui/check-replace-imports': ['error', '@agnos-ui/svelte-bootstrap'],
'@typescript-eslint/no-unused-vars': [
'error',
{
vars: 'all',
args: 'none',
ignoreRestSiblings: false,
varsIgnorePattern: '^\\$\\$(Props|Slots)$',
},
],
},
},
{
files: ['svelte/demo/src/bootstrap/samples/focustrack/Focustrack.route.svelte'],
rules: {
'@typescript-eslint/no-unused-expressions': 'off',
},
},
// jsdoc,
Expand Down
30 changes: 23 additions & 7 deletions react/demo/src/bootstrap/samples/focustrack/Focustrack.route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,29 @@ const Focustrack = () => {
Focus in container
</label>
</div>
<label htmlFor="activeElementHistory" className="form-label">
Active element history:
</label>
<textarea className="form-control mb-2" id="activeElementHistory" value={JSON.stringify(activeElements)} readOnly />
<button className="btn btn-primary" onClick={() => setActiveElements([])}>
Clear
</button>
<div className="d-flex justify-content-between">
<div>Active element history:</div>
<button className="btn btn-sm btn-primary" onClick={() => setActiveElements([])}>
Clear
</button>
</div>
<div className="card my-2">
<div className="card-body">
<ul className="mb-0">
{activeElements.map((activeElement) => (
<li key={activeElement}>
<strong>{activeElement.tagName}</strong>
{activeElement.id && (
<>
{' '}
with id <strong>{activeElement.id}</strong>
</>
)}
</li>
))}
</ul>
</div>
</div>
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const MatchMediaDemo = () => {
<>
<div className="mb-2">Resize the window to see the message below updated.</div>
<Alert dismissible={false} type="info">
Window is above 768 px ? <strong>{aboveMd ? 'YES' : 'NO'}</strong>
Window width is above 768 px ? <strong>{aboveMd ? 'YES' : 'NO'}</strong>
</Alert>
</>
);
Expand Down
Loading

0 comments on commit d8971b3

Please sign in to comment.