From 11a54740fdb346e6ad728ea383a2b46e88cc3a92 Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Mon, 7 Jul 2025 20:27:00 +0200 Subject: [PATCH 01/31] BREAKING CHANGE: rename pos-navigation-bar to pos-navigation --- docs/elements/apps/pos-app-browser/readme.md | 18 +++++------ .../components/pos-make-findable/readme.md | 4 +-- .../components/pos-rich-link/readme.md | 4 +-- elements/CHANGELOG.md | 5 +++ .../apps/pos-app-browser/pos-app-browser.css | 4 +-- .../pos-app-browser/pos-app-browser.spec.tsx | 12 +++---- .../apps/pos-app-browser/pos-app-browser.tsx | 2 +- elements/src/components.d.ts | 32 +++++++++---------- .../pos-navigation.css} | 0 .../pos-navigation.integration.spec.tsx} | 8 ++--- .../pos-navigation.spec.tsx} | 28 ++++++++-------- .../pos-navigation.tsx} | 6 ++-- .../src/components/pos-router/pos-router.css | 2 +- 13 files changed, 65 insertions(+), 60 deletions(-) rename elements/src/components/{pos-navigation-bar/pos-navigation-bar.css => pos-navigation/pos-navigation.css} (100%) rename elements/src/components/{pos-navigation-bar/pos-navigation-bar.integration.spec.tsx => pos-navigation/pos-navigation.integration.spec.tsx} (90%) rename elements/src/components/{pos-navigation-bar/pos-navigation-bar.spec.tsx => pos-navigation/pos-navigation.spec.tsx} (94%) rename elements/src/components/{pos-navigation-bar/pos-navigation-bar.tsx => pos-navigation/pos-navigation.tsx} (96%) diff --git a/docs/elements/apps/pos-app-browser/readme.md b/docs/elements/apps/pos-app-browser/readme.md index 219a2574..4945b766 100644 --- a/docs/elements/apps/pos-app-browser/readme.md +++ b/docs/elements/apps/pos-app-browser/readme.md @@ -7,10 +7,10 @@ ## Properties -| Property | Attribute | Description | Type | Default | -| ------------------------ | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | -------------- | -| `mode` | `mode` | The mode the app is running in: - standalone: use this when you deploy it as a standalone web application - pod: use this when you host this app as a default interface for you pod | `"pod" \| "standalone"` | `'standalone'` | -| `restorePreviousSession` | `restore-previous-session` | | `boolean` | `false` | +| Property | Attribute | Description | Type | Default | +| ------------------------ | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------- | -------------- | +| `mode` | `mode` | The mode the app is running in: - standalone: use this when you deploy it as a standalone web application - pod: use this when you host this app as a default interface for you pod | `"pod" \| "standalone"` | `'standalone'` | +| `restorePreviousSession` | `restore-previous-session` | | `boolean` | `false` | ## Dependencies @@ -21,7 +21,7 @@ - [pos-error-toast](../../components/pos-error-toast) - [pos-router](../../components/pos-router) - [pos-add-new-thing](../../components/pos-add-new-thing) -- [pos-navigation-bar](../../components/pos-navigation-bar) +- [pos-navigation](../../components/pos-navigation) - [pos-login](../../components/pos-login) - [pos-internal-router](../../components/pos-internal-router) - [pos-resource](../../components/pos-resource) @@ -34,7 +34,7 @@ graph TD; pos-app-browser --> pos-error-toast pos-app-browser --> pos-router pos-app-browser --> pos-add-new-thing - pos-app-browser --> pos-navigation-bar + pos-app-browser --> pos-navigation pos-app-browser --> pos-login pos-app-browser --> pos-internal-router pos-app-browser --> pos-resource @@ -48,9 +48,9 @@ graph TD; pos-add-new-thing --> pos-new-thing-form pos-dialog --> ion-icon pos-new-thing-form --> pos-select-term - pos-navigation-bar --> pos-make-findable - pos-navigation-bar --> ion-searchbar - pos-navigation-bar --> pos-rich-link + pos-navigation --> pos-make-findable + pos-navigation --> ion-searchbar + pos-navigation --> pos-rich-link pos-make-findable --> pos-resource pos-make-findable --> pos-label pos-resource --> ion-progress-bar diff --git a/docs/elements/components/pos-make-findable/readme.md b/docs/elements/components/pos-make-findable/readme.md index 117053c0..bcf92853 100644 --- a/docs/elements/components/pos-make-findable/readme.md +++ b/docs/elements/components/pos-make-findable/readme.md @@ -23,7 +23,7 @@ ### Used by - - [pos-navigation-bar](../pos-navigation-bar) + - [pos-navigation](../pos-navigation) ### Depends on @@ -36,7 +36,7 @@ graph TD; pos-make-findable --> pos-resource pos-make-findable --> pos-label pos-resource --> ion-progress-bar - pos-navigation-bar --> pos-make-findable + pos-navigation --> pos-make-findable style pos-make-findable fill:#f9f,stroke:#333,stroke-width:4px ``` diff --git a/docs/elements/components/pos-rich-link/readme.md b/docs/elements/components/pos-rich-link/readme.md index b9563d04..289a7d6a 100644 --- a/docs/elements/components/pos-rich-link/readme.md +++ b/docs/elements/components/pos-rich-link/readme.md @@ -24,7 +24,7 @@ ### Used by - [pos-example-resources](../../apps/pos-app-dashboard/pos-example-resources) - - [pos-navigation-bar](../pos-navigation-bar) + - [pos-navigation](../pos-navigation) - [pos-relations](../pos-relations) - [pos-reverse-relations](../pos-reverse-relations) - [pos-subjects](../pos-subjects) @@ -43,7 +43,7 @@ graph TD; pos-rich-link --> pos-description pos-resource --> ion-progress-bar pos-example-resources --> pos-rich-link - pos-navigation-bar --> pos-rich-link + pos-navigation --> pos-rich-link pos-relations --> pos-rich-link pos-reverse-relations --> pos-rich-link pos-subjects --> pos-rich-link diff --git a/elements/CHANGELOG.md b/elements/CHANGELOG.md index 1396b84d..634fdd97 100644 --- a/elements/CHANGELOG.md +++ b/elements/CHANGELOG.md @@ -6,6 +6,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## Unreleased +### ⚠ BREAKING CHANGES + +- [pos-navigation](../docs/elements/components/pos-navigation) + - `pos-navigation-bar` has been renamed to `pos-navigation` and will undergo further major changes + ### Fixed - [pos-app-browser](../docs/elements/apps/pos-app-browser): prevent error message flashing up while uri is unset on hard refresh diff --git a/elements/src/apps/pos-app-browser/pos-app-browser.css b/elements/src/apps/pos-app-browser/pos-app-browser.css index 5c1d94ab..6cbbd958 100644 --- a/elements/src/apps/pos-app-browser/pos-app-browser.css +++ b/elements/src/apps/pos-app-browser/pos-app-browser.css @@ -6,7 +6,7 @@ pos-router { height: 100%; } -pos-navigation-bar { +pos-navigation { max-width: var(--width-lg); margin: 0; } @@ -66,7 +66,7 @@ main { justify-content: space-between; } - pos-navigation-bar { + pos-navigation { flex-basis: 100%; order: 0; } diff --git a/elements/src/apps/pos-app-browser/pos-app-browser.spec.tsx b/elements/src/apps/pos-app-browser/pos-app-browser.spec.tsx index 35baf501..38b83bdf 100644 --- a/elements/src/apps/pos-app-browser/pos-app-browser.spec.tsx +++ b/elements/src/apps/pos-app-browser/pos-app-browser.spec.tsx @@ -33,9 +33,9 @@ describe('pos-app-browser', () => { await page.waitForChanges(); const main = getByRole(page.root, 'banner'); - const navigationBar = main.querySelector('pos-navigation-bar'); + const navigation = main.querySelector('pos-navigation'); - expect(navigationBar).toEqualAttribute('uri', ''); + expect(navigation).toEqualAttribute('uri', ''); }); it('shows uri in navigation bar, if visiting other internal pages', async () => { @@ -49,9 +49,9 @@ describe('pos-app-browser', () => { await page.waitForChanges(); const main = getByRole(page.root, 'banner'); - const navigationBar = main.querySelector('pos-navigation-bar'); + const navigation = main.querySelector('pos-navigation'); - expect(navigationBar).toEqualAttribute('uri', 'pod-os:other'); + expect(navigation).toEqualAttribute('uri', 'pod-os:other'); }); it('shows uri in navigation bar, if visiting http(s) URIs', async () => { @@ -65,9 +65,9 @@ describe('pos-app-browser', () => { await page.waitForChanges(); const main = getByRole(page.root, 'banner'); - const navigationBar = main.querySelector('pos-navigation-bar'); + const navigation = main.querySelector('pos-navigation'); - expect(navigationBar).toEqualAttribute('uri', 'https://resource.test'); + expect(navigation).toEqualAttribute('uri', 'https://resource.test'); }); it('uses type router for http(s) URIs ', async () => { diff --git a/elements/src/apps/pos-app-browser/pos-app-browser.tsx b/elements/src/apps/pos-app-browser/pos-app-browser.tsx index 4bcc0f9f..5e734f73 100644 --- a/elements/src/apps/pos-app-browser/pos-app-browser.tsx +++ b/elements/src/apps/pos-app-browser/pos-app-browser.tsx @@ -25,7 +25,7 @@ export class PosAppBrowser { (this.uri = e.detail)}>
- +
{this.mainContent()}
diff --git a/elements/src/components.d.ts b/elements/src/components.d.ts index a2885085..27b1aaf3 100644 --- a/elements/src/components.d.ts +++ b/elements/src/components.d.ts @@ -95,7 +95,7 @@ export namespace Components { interface PosMakeFindable { "uri": string; } - interface PosNavigationBar { + interface PosNavigation { "uri": string; } interface PosNewThingForm { @@ -213,9 +213,9 @@ export interface PosMakeFindableCustomEvent extends CustomEvent { detail: T; target: HTMLPosMakeFindableElement; } -export interface PosNavigationBarCustomEvent extends CustomEvent { +export interface PosNavigationCustomEvent extends CustomEvent { detail: T; - target: HTMLPosNavigationBarElement; + target: HTMLPosNavigationElement; } export interface PosNewThingFormCustomEvent extends CustomEvent { detail: T; @@ -610,23 +610,23 @@ declare global { prototype: HTMLPosMakeFindableElement; new (): HTMLPosMakeFindableElement; }; - interface HTMLPosNavigationBarElementEventMap { + interface HTMLPosNavigationElementEventMap { "pod-os:init": any; "pod-os:link": any; } - interface HTMLPosNavigationBarElement extends Components.PosNavigationBar, HTMLStencilElement { - addEventListener(type: K, listener: (this: HTMLPosNavigationBarElement, ev: PosNavigationBarCustomEvent) => any, options?: boolean | AddEventListenerOptions): void; + interface HTMLPosNavigationElement extends Components.PosNavigation, HTMLStencilElement { + addEventListener(type: K, listener: (this: HTMLPosNavigationElement, ev: PosNavigationCustomEvent) => any, options?: boolean | AddEventListenerOptions): void; addEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; addEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; - removeEventListener(type: K, listener: (this: HTMLPosNavigationBarElement, ev: PosNavigationBarCustomEvent) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: K, listener: (this: HTMLPosNavigationElement, ev: PosNavigationCustomEvent) => any, options?: boolean | EventListenerOptions): void; removeEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void; removeEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void; removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; } - var HTMLPosNavigationBarElement: { - prototype: HTMLPosNavigationBarElement; - new (): HTMLPosNavigationBarElement; + var HTMLPosNavigationElement: { + prototype: HTMLPosNavigationElement; + new (): HTMLPosNavigationElement; }; interface HTMLPosNewThingFormElementEventMap { "pod-os:link": any; @@ -881,7 +881,7 @@ declare global { "pos-login": HTMLPosLoginElement; "pos-login-form": HTMLPosLoginFormElement; "pos-make-findable": HTMLPosMakeFindableElement; - "pos-navigation-bar": HTMLPosNavigationBarElement; + "pos-navigation": HTMLPosNavigationElement; "pos-new-thing-form": HTMLPosNewThingFormElement; "pos-picture": HTMLPosPictureElement; "pos-predicate": HTMLPosPredicateElement; @@ -1028,9 +1028,9 @@ declare namespace LocalJSX { "onPod-os:search:index-updated"?: (event: PosMakeFindableCustomEvent) => void; "uri": string; } - interface PosNavigationBar { - "onPod-os:init"?: (event: PosNavigationBarCustomEvent) => void; - "onPod-os:link"?: (event: PosNavigationBarCustomEvent) => void; + interface PosNavigation { + "onPod-os:init"?: (event: PosNavigationCustomEvent) => void; + "onPod-os:link"?: (event: PosNavigationCustomEvent) => void; "uri"?: string; } interface PosNewThingForm { @@ -1139,7 +1139,7 @@ declare namespace LocalJSX { "pos-login": PosLogin; "pos-login-form": PosLoginForm; "pos-make-findable": PosMakeFindable; - "pos-navigation-bar": PosNavigationBar; + "pos-navigation": PosNavigation; "pos-new-thing-form": PosNewThingForm; "pos-picture": PosPicture; "pos-predicate": PosPredicate; @@ -1195,7 +1195,7 @@ declare module "@stencil/core" { "pos-login": LocalJSX.PosLogin & JSXBase.HTMLAttributes; "pos-login-form": LocalJSX.PosLoginForm & JSXBase.HTMLAttributes; "pos-make-findable": LocalJSX.PosMakeFindable & JSXBase.HTMLAttributes; - "pos-navigation-bar": LocalJSX.PosNavigationBar & JSXBase.HTMLAttributes; + "pos-navigation": LocalJSX.PosNavigation & JSXBase.HTMLAttributes; "pos-new-thing-form": LocalJSX.PosNewThingForm & JSXBase.HTMLAttributes; "pos-picture": LocalJSX.PosPicture & JSXBase.HTMLAttributes; "pos-predicate": LocalJSX.PosPredicate & JSXBase.HTMLAttributes; diff --git a/elements/src/components/pos-navigation-bar/pos-navigation-bar.css b/elements/src/components/pos-navigation/pos-navigation.css similarity index 100% rename from elements/src/components/pos-navigation-bar/pos-navigation-bar.css rename to elements/src/components/pos-navigation/pos-navigation.css diff --git a/elements/src/components/pos-navigation-bar/pos-navigation-bar.integration.spec.tsx b/elements/src/components/pos-navigation/pos-navigation.integration.spec.tsx similarity index 90% rename from elements/src/components/pos-navigation-bar/pos-navigation-bar.integration.spec.tsx rename to elements/src/components/pos-navigation/pos-navigation.integration.spec.tsx index d10b0cc7..a3470efb 100644 --- a/elements/src/components/pos-navigation-bar/pos-navigation-bar.integration.spec.tsx +++ b/elements/src/components/pos-navigation/pos-navigation.integration.spec.tsx @@ -6,9 +6,9 @@ import session from '../../store/session'; import { mockPodOS } from '../../test/mockPodOS'; import { PosApp } from '../pos-app/pos-app'; import { PosRichLink } from '../pos-rich-link/pos-rich-link'; -import { PosNavigationBar } from './pos-navigation-bar'; +import { PosNavigation } from './pos-navigation'; -describe('pos-navigation-bar', () => { +describe('pos-navigation', () => { it('can search after login', async () => { // given PodOS const os = mockPodOS(); @@ -33,8 +33,8 @@ describe('pos-navigation-bar', () => { // and a page with a navigation bar const page = await newSpecPage({ supportsShadowDom: false, - components: [PosApp, PosNavigationBar, PosRichLink], - html: ``, + components: [PosApp, PosNavigation, PosRichLink], + html: ``, }); // and the user is not logged in yet diff --git a/elements/src/components/pos-navigation-bar/pos-navigation-bar.spec.tsx b/elements/src/components/pos-navigation/pos-navigation.spec.tsx similarity index 94% rename from elements/src/components/pos-navigation-bar/pos-navigation-bar.spec.tsx rename to elements/src/components/pos-navigation/pos-navigation.spec.tsx index fdd7b446..9b18ad9e 100644 --- a/elements/src/components/pos-navigation-bar/pos-navigation-bar.spec.tsx +++ b/elements/src/components/pos-navigation/pos-navigation.spec.tsx @@ -1,17 +1,17 @@ import { newSpecPage } from '@stencil/core/testing'; import { fireEvent } from '@testing-library/dom'; import { mockSessionStore } from '../../test/mockSessionStore'; -import { PosNavigationBar } from './pos-navigation-bar'; +import { PosNavigation } from './pos-navigation'; import { pressKey } from '../../test/pressKey'; -describe('pos-navigation-bar', () => { +describe('pos-navigation', () => { it('renders a search bar within a form', async () => { const page = await newSpecPage({ - components: [PosNavigationBar], - html: ``, + components: [PosNavigation], + html: ``, }); expect(page.root).toEqualHtml(` - +
@@ -19,15 +19,15 @@ describe('pos-navigation-bar', () => {
-
`); +
`); }); it('navigates to entered URI when form is submitted', async () => { // given a page with a navigation bar const page = await newSpecPage({ supportsShadowDom: false, - components: [PosNavigationBar], - html: ``, + components: [PosNavigation], + html: ``, }); // and the page listens for pod-os:link events @@ -60,8 +60,8 @@ describe('pos-navigation-bar', () => { // and a page with a navigation nar page = await newSpecPage({ supportsShadowDom: false, - components: [PosNavigationBar], - html: ``, + components: [PosNavigation], + html: ``, }); // and a fake search index giving 2 results @@ -91,27 +91,27 @@ describe('pos-navigation-bar', () => { it('shows the make-findable button as soon as search index is available', () => { expect(page.root).toEqualHtml(` - +
-
`); +
`); }); it('does not show the make-findable button if URI is empty', async () => { page.root.setAttribute('uri', ''); await page.waitForChanges(); expect(page.root).toEqualHtml(` - +
-
`); +
`); }); it(' searches for the typed text and shows suggestions', async () => { diff --git a/elements/src/components/pos-navigation-bar/pos-navigation-bar.tsx b/elements/src/components/pos-navigation/pos-navigation.tsx similarity index 96% rename from elements/src/components/pos-navigation-bar/pos-navigation-bar.tsx rename to elements/src/components/pos-navigation/pos-navigation.tsx index 4a7f831c..e7238cf5 100644 --- a/elements/src/components/pos-navigation-bar/pos-navigation-bar.tsx +++ b/elements/src/components/pos-navigation/pos-navigation.tsx @@ -5,11 +5,11 @@ import session from '../../store/session'; import { PodOsAware, PodOsEventEmitter, subscribePodOs } from '../events/PodOsAware'; @Component({ - tag: 'pos-navigation-bar', + tag: 'pos-navigation', shadow: true, - styleUrl: 'pos-navigation-bar.css', + styleUrl: 'pos-navigation.css', }) -export class PosNavigationBar implements PodOsAware { +export class PosNavigation implements PodOsAware { @State() os: PodOS; @Event({ eventName: 'pod-os:init' }) subscribePodOs: PodOsEventEmitter; diff --git a/elements/src/components/pos-router/pos-router.css b/elements/src/components/pos-router/pos-router.css index 765be7b7..c5a0c665 100644 --- a/elements/src/components/pos-router/pos-router.css +++ b/elements/src/components/pos-router/pos-router.css @@ -6,6 +6,6 @@ margin-left: 0.5rem; } -pos-navigation-bar { +pos-navigation { flex-grow: 1; } From ef044b259b016affeceffe7cbbf12a868f9e5378 Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Mon, 7 Jul 2025 21:31:57 +0200 Subject: [PATCH 02/31] feat(elements): add new pos-navigation-bar component based on a button --- .../pos-navigation/bar/pos-navigation-bar.css | 0 .../bar/pos-navigation-bar.spec.tsx | 27 +++++++++++++++++++ .../pos-navigation/bar/pos-navigation-bar.tsx | 19 +++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 elements/src/components/pos-navigation/bar/pos-navigation-bar.css create mode 100644 elements/src/components/pos-navigation/bar/pos-navigation-bar.spec.tsx create mode 100644 elements/src/components/pos-navigation/bar/pos-navigation-bar.tsx diff --git a/elements/src/components/pos-navigation/bar/pos-navigation-bar.css b/elements/src/components/pos-navigation/bar/pos-navigation-bar.css new file mode 100644 index 00000000..e69de29b diff --git a/elements/src/components/pos-navigation/bar/pos-navigation-bar.spec.tsx b/elements/src/components/pos-navigation/bar/pos-navigation-bar.spec.tsx new file mode 100644 index 00000000..409d8cc0 --- /dev/null +++ b/elements/src/components/pos-navigation/bar/pos-navigation-bar.spec.tsx @@ -0,0 +1,27 @@ +import { h } from '@stencil/core'; +import { newSpecPage } from '@stencil/core/testing'; +import { PosNavigationBar } from './pos-navigation-bar'; + +describe('pos-navigation-bar', () => { + it('shows the resource label', async () => { + const mockThing = { + label: () => 'Test Label', + }; + + const page = await newSpecPage({ + components: [PosNavigationBar], + template: () => , + supportsShadowDom: false, + }); + + expect(page.root).toEqualHtml(` + + + + `); + }); +}); diff --git a/elements/src/components/pos-navigation/bar/pos-navigation-bar.tsx b/elements/src/components/pos-navigation/bar/pos-navigation-bar.tsx new file mode 100644 index 00000000..bfcf4efb --- /dev/null +++ b/elements/src/components/pos-navigation/bar/pos-navigation-bar.tsx @@ -0,0 +1,19 @@ +import { Component, h, Prop } from '@stencil/core'; +import { Thing } from '@pod-os/core'; + +@Component({ + tag: 'pos-navigation-bar', + shadow: true, + styleUrl: 'pos-navigation-bar.css', +}) +export class PosNavigationBar { + @Prop() current: Thing; + + render() { + return ( + + ); + } +} From a9d0633cd9cb0049e286d949d4f0955e7ca5709d Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Mon, 7 Jul 2025 21:43:34 +0200 Subject: [PATCH 03/31] feat(elements): emit navigation event on button click in pos-navigation-bar --- .../bar/pos-navigation-bar.spec.tsx | 24 +++++++++++++++++++ .../pos-navigation/bar/pos-navigation-bar.tsx | 10 ++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/elements/src/components/pos-navigation/bar/pos-navigation-bar.spec.tsx b/elements/src/components/pos-navigation/bar/pos-navigation-bar.spec.tsx index 409d8cc0..0e0facc0 100644 --- a/elements/src/components/pos-navigation/bar/pos-navigation-bar.spec.tsx +++ b/elements/src/components/pos-navigation/bar/pos-navigation-bar.spec.tsx @@ -1,6 +1,7 @@ import { h } from '@stencil/core'; import { newSpecPage } from '@stencil/core/testing'; import { PosNavigationBar } from './pos-navigation-bar'; +import { screen } from '@testing-library/dom'; describe('pos-navigation-bar', () => { it('shows the resource label', async () => { @@ -24,4 +25,27 @@ describe('pos-navigation-bar', () => { `); }); + + it('emits navigation event on button click', async () => { + const mockThing = { + label: () => 'Test Label', + }; + + const page = await newSpecPage({ + components: [PosNavigationBar], + template: () => , + supportsShadowDom: false, + }); + + const onNavigate = jest.fn(); + page.root.addEventListener('pod-os:navigate', onNavigate); + + screen.getByRole('button').click(); + + expect(onNavigate).toHaveBeenCalledWith( + expect.objectContaining({ + type: 'pod-os:navigate', + }), + ); + }); }); diff --git a/elements/src/components/pos-navigation/bar/pos-navigation-bar.tsx b/elements/src/components/pos-navigation/bar/pos-navigation-bar.tsx index bfcf4efb..c03bec11 100644 --- a/elements/src/components/pos-navigation/bar/pos-navigation-bar.tsx +++ b/elements/src/components/pos-navigation/bar/pos-navigation-bar.tsx @@ -1,4 +1,4 @@ -import { Component, h, Prop } from '@stencil/core'; +import { Component, Event, EventEmitter, h, Prop } from '@stencil/core'; import { Thing } from '@pod-os/core'; @Component({ @@ -9,10 +9,16 @@ import { Thing } from '@pod-os/core'; export class PosNavigationBar { @Prop() current: Thing; + @Event({ eventName: 'pod-os:navigate' }) navigate: EventEmitter; + + private onClick() { + this.navigate.emit(); + } + render() { return ( ); } From feb6ae921b1c239349b4c56eefbfd973514228a3 Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Mon, 7 Jul 2025 22:32:29 +0200 Subject: [PATCH 04/31] feat(elements): move search input & suggestions to modal dialog --- .../pos-navigation/pos-navigation.css | 4 ++ .../pos-navigation/pos-navigation.spec.tsx | 72 ++++++++++++++++--- .../pos-navigation/pos-navigation.tsx | 69 +++++++++++------- 3 files changed, 111 insertions(+), 34 deletions(-) diff --git a/elements/src/components/pos-navigation/pos-navigation.css b/elements/src/components/pos-navigation/pos-navigation.css index 2b15db7b..4d6bb00e 100644 --- a/elements/src/components/pos-navigation/pos-navigation.css +++ b/elements/src/components/pos-navigation/pos-navigation.css @@ -49,3 +49,7 @@ form { .bar { flex-grow: 1; } + +dialog { + min-height: 50%; /* TODO better height value*/ +} diff --git a/elements/src/components/pos-navigation/pos-navigation.spec.tsx b/elements/src/components/pos-navigation/pos-navigation.spec.tsx index 9b18ad9e..917e066c 100644 --- a/elements/src/components/pos-navigation/pos-navigation.spec.tsx +++ b/elements/src/components/pos-navigation/pos-navigation.spec.tsx @@ -13,15 +13,65 @@ describe('pos-navigation', () => { expect(page.root).toEqualHtml(` -
+ + +
+ +
+ +
+
+
`); + }); + + it('renders pos-navigation-bar when resource is loaded', async () => { + const page = await newSpecPage({ + components: [PosNavigation], + html: ``, + supportsShadowDom: false, + }); + + const mockResource = { fake: 'resource' }; + page.rootInstance.os = { + store: { + get: jest.fn().mockReturnValue(mockResource), + }, + }; + + await page.rootInstance.getResource('https://pod.example/resource'); + await page.waitForChanges(); + + expect(page.root).toEqualHtml(` + + + +
- +
`); }); + it('opens the dialog when navigate event is emitted', async () => { + // given a page with a navigation + const page = await newSpecPage({ + supportsShadowDom: false, + components: [PosNavigation], + html: ``, + }); + + const dialog = page.root.querySelector('dialog'); + dialog.showModal = jest.fn(); + + // when a "navigate" event is emitted + fireEvent(page.root, new CustomEvent('pod-os:navigate')); + + // then the dialog should be shown + expect(dialog.showModal).toHaveBeenCalled(); + }); + it('navigates to entered URI when form is submitted', async () => { // given a page with a navigation bar const page = await newSpecPage({ @@ -92,12 +142,14 @@ describe('pos-navigation', () => { it('shows the make-findable button as soon as search index is available', () => { expect(page.root).toEqualHtml(` -
- + + +
+
`); }); @@ -106,11 +158,13 @@ describe('pos-navigation', () => { await page.waitForChanges(); expect(page.root).toEqualHtml(` -
-
- -
-
+ +
+
+ +
+
+
`); }); diff --git a/elements/src/components/pos-navigation/pos-navigation.tsx b/elements/src/components/pos-navigation/pos-navigation.tsx index e7238cf5..10bd549f 100644 --- a/elements/src/components/pos-navigation/pos-navigation.tsx +++ b/elements/src/components/pos-navigation/pos-navigation.tsx @@ -1,5 +1,5 @@ -import { PodOS, SearchIndex } from '@pod-os/core'; -import { Component, Event, EventEmitter, h, Listen, Prop, State } from '@stencil/core'; +import { PodOS, SearchIndex, Thing } from '@pod-os/core'; +import { Component, Event, EventEmitter, h, Host, Listen, Prop, State, Watch } from '@stencil/core'; import session from '../../store/session'; import { PodOsAware, PodOsEventEmitter, subscribePodOs } from '../events/PodOsAware'; @@ -11,6 +11,7 @@ import { PodOsAware, PodOsEventEmitter, subscribePodOs } from '../events/PodOsAw }) export class PosNavigation implements PodOsAware { @State() os: PodOS; + private dialogRef?: HTMLDialogElement; @Event({ eventName: 'pod-os:init' }) subscribePodOs: PodOsEventEmitter; @Prop() uri: string = ''; @@ -25,6 +26,14 @@ export class PosNavigation implements PodOsAware { @State() selectedIndex = -1; + @State() resource: Thing = null; + + @Watch('uri') + async getResource() { + // TODO should this be done on componentWillLoad? + this.resource = this.uri ? this.os.store.get(this.uri) : null; + } + componentWillLoad() { subscribePodOs(this); session.onChange('isLoggedIn', async isLoggedIn => { @@ -46,6 +55,11 @@ export class PosNavigation implements PodOsAware { this.searchIndex.rebuild(); } + @Listen('pod-os:navigate') + openNavigationDialog() { + this.dialogRef?.showModal(); + } + private clearSearchIndex() { this.searchIndex?.clear(); } @@ -91,7 +105,6 @@ export class PosNavigation implements PodOsAware { } private onSubmit(event) { - event.preventDefault(); if (this.suggestions && this.selectedIndex > -1) { this.linkEmitter.emit(this.suggestions[this.selectedIndex].ref); } else { @@ -100,31 +113,37 @@ export class PosNavigation implements PodOsAware { } render() { + // TODO: move make findable to pos-navigation-bar return ( -
this.onSubmit(e)}> + {this.searchIndex && this.uri ? : ''} -
- this.onChange(e)} - onIonInput={e => this.onChange(e)} - /> - {this.suggestions.length > 0 ? ( -
-
    - {this.suggestions.map((it, index) => ( -
  1. - -
  2. - ))} -
+ {this.resource && } + (this.dialogRef = el as HTMLDialogElement)}> + this.onSubmit(e)}> +
+ this.onChange(e)} + onIonInput={e => this.onChange(e)} + /> + {this.suggestions.length > 0 ? ( +
+
    + {this.suggestions.map((it, index) => ( +
  1. + +
  2. + ))} +
+
+ ) : null}
- ) : null} -
- + + + ); } } From ef285943e79a9ddaf215a958823a33d81fd1df60 Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Mon, 7 Jul 2025 22:47:58 +0200 Subject: [PATCH 05/31] refactor(elements): replace ion-searchbar with native input in pos-navigation component --- .../pos-navigation.integration.spec.tsx | 6 ++++-- .../pos-navigation/pos-navigation.spec.tsx | 17 ++++++++++------- .../pos-navigation/pos-navigation.tsx | 10 +++++----- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/elements/src/components/pos-navigation/pos-navigation.integration.spec.tsx b/elements/src/components/pos-navigation/pos-navigation.integration.spec.tsx index a3470efb..afcc6250 100644 --- a/elements/src/components/pos-navigation/pos-navigation.integration.spec.tsx +++ b/elements/src/components/pos-navigation/pos-navigation.integration.spec.tsx @@ -50,8 +50,10 @@ describe('pos-navigation', () => { await page.waitForChanges(); // when the user types "test" into the navigation bar - const searchBar = page.root.querySelector('ion-searchbar'); - fireEvent(searchBar, new CustomEvent('ionInput', { detail: { value: 'test' } })); + const searchBar = page.root.querySelector('input'); + // @ts-ignore + searchBar.value = 'test'; + fireEvent(searchBar, new CustomEvent('change', { target: { value: 'test' } })); // then a search is triggered expect(searchIndex.search).toHaveBeenCalledWith('test'); diff --git a/elements/src/components/pos-navigation/pos-navigation.spec.tsx b/elements/src/components/pos-navigation/pos-navigation.spec.tsx index 917e066c..1f253f5b 100644 --- a/elements/src/components/pos-navigation/pos-navigation.spec.tsx +++ b/elements/src/components/pos-navigation/pos-navigation.spec.tsx @@ -3,6 +3,7 @@ import { fireEvent } from '@testing-library/dom'; import { mockSessionStore } from '../../test/mockSessionStore'; import { PosNavigation } from './pos-navigation'; import { pressKey } from '../../test/pressKey'; +import * as test from 'node:test'; describe('pos-navigation', () => { it('renders a search bar within a form', async () => { @@ -16,7 +17,7 @@ describe('pos-navigation', () => {
- +
@@ -47,7 +48,7 @@ describe('pos-navigation', () => {
- +
@@ -146,7 +147,7 @@ describe('pos-navigation', () => {
- +
@@ -161,7 +162,7 @@ describe('pos-navigation', () => {
- +
@@ -288,7 +289,7 @@ describe('pos-navigation', () => { expect(page.root.querySelectorAll('.suggestions li')).toHaveLength(2); // when the user clicks into the search bar - const searchBar = page.root.querySelector('ion-searchbar'); + const searchBar = page.root.querySelector('input'); searchBar.click(); await page.waitForChanges(); @@ -406,7 +407,9 @@ describe('pos-navigation', () => { }); async function type(page, text: string) { - const searchBar = page.root.querySelector('ion-searchbar'); - fireEvent(searchBar, new CustomEvent('ionInput', { detail: { value: text } })); + const searchBar = page.root.querySelector('input'); + searchBar.value = text; + // @ts-ignore + fireEvent(searchBar, new CustomEvent('change', { target: { value: text } })); await page.waitForChanges(); } diff --git a/elements/src/components/pos-navigation/pos-navigation.tsx b/elements/src/components/pos-navigation/pos-navigation.tsx index 10bd549f..7e249a5b 100644 --- a/elements/src/components/pos-navigation/pos-navigation.tsx +++ b/elements/src/components/pos-navigation/pos-navigation.tsx @@ -69,7 +69,8 @@ export class PosNavigation implements PodOsAware { }; private onChange(event) { - this.value = event.detail.value; + this.value = event.target.value; + // TODO add debounce this.search(); } @@ -121,13 +122,12 @@ export class PosNavigation implements PodOsAware { (this.dialogRef = el as HTMLDialogElement)}>
this.onSubmit(e)}>
- this.onChange(e)} - onIonInput={e => this.onChange(e)} + onChange={e => this.onChange(e)} + onInput={e => this.onChange(e)} /> {this.suggestions.length > 0 ? (
From 93c903743c4413c7bf8848a7b72fbaf3ba852342 Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Mon, 7 Jul 2025 23:04:53 +0200 Subject: [PATCH 06/31] fix(elements): suggestions no longer need to be relatively positioned, as the dialog does this --- .../src/components/pos-navigation/pos-navigation.css | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/elements/src/components/pos-navigation/pos-navigation.css b/elements/src/components/pos-navigation/pos-navigation.css index 4d6bb00e..c44760ca 100644 --- a/elements/src/components/pos-navigation/pos-navigation.css +++ b/elements/src/components/pos-navigation/pos-navigation.css @@ -2,7 +2,6 @@ border: 1px solid var(--pos-border-color); display: flex; flex-direction: column; - position: absolute; margin: 0; padding: 0; z-index: var(--layer-top); @@ -11,7 +10,7 @@ } .suggestions { - position: relative; + width: 100%; li { padding: 1rem; background-color: var(--pos-background-color); @@ -50,6 +49,8 @@ form { flex-grow: 1; } -dialog { - min-height: 50%; /* TODO better height value*/ +dialog[open] { + margin-top: 0; + width: 80%; // TODO better width value + padding: 0; } From 8c036327e4bcba730a34e9803e4f737a1a12603b Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Mon, 7 Jul 2025 23:05:30 +0200 Subject: [PATCH 07/31] update type definitions --- elements/src/components.d.ts | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/elements/src/components.d.ts b/elements/src/components.d.ts index 27b1aaf3..f2f859a6 100644 --- a/elements/src/components.d.ts +++ b/elements/src/components.d.ts @@ -5,6 +5,8 @@ * It contains typing information for all components that exist in this project. */ import { HTMLStencilElement, JSXBase } from "@stencil/core/internal"; +import { Thing } from "@pod-os/core"; +export { Thing } from "@pod-os/core"; export namespace Components { interface PosAddLiteralValue { } @@ -98,6 +100,9 @@ export namespace Components { interface PosNavigation { "uri": string; } + interface PosNavigationBar { + "current": Thing; + } interface PosNewThingForm { "referenceUri": string; } @@ -217,6 +222,10 @@ export interface PosNavigationCustomEvent extends CustomEvent { detail: T; target: HTMLPosNavigationElement; } +export interface PosNavigationBarCustomEvent extends CustomEvent { + detail: T; + target: HTMLPosNavigationBarElement; +} export interface PosNewThingFormCustomEvent extends CustomEvent { detail: T; target: HTMLPosNewThingFormElement; @@ -628,6 +637,23 @@ declare global { prototype: HTMLPosNavigationElement; new (): HTMLPosNavigationElement; }; + interface HTMLPosNavigationBarElementEventMap { + "pod-os:navigate": any; + } + interface HTMLPosNavigationBarElement extends Components.PosNavigationBar, HTMLStencilElement { + addEventListener(type: K, listener: (this: HTMLPosNavigationBarElement, ev: PosNavigationBarCustomEvent) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; + removeEventListener(type: K, listener: (this: HTMLPosNavigationBarElement, ev: PosNavigationBarCustomEvent) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; + } + var HTMLPosNavigationBarElement: { + prototype: HTMLPosNavigationBarElement; + new (): HTMLPosNavigationBarElement; + }; interface HTMLPosNewThingFormElementEventMap { "pod-os:link": any; "pod-os:error": any; @@ -882,6 +908,7 @@ declare global { "pos-login-form": HTMLPosLoginFormElement; "pos-make-findable": HTMLPosMakeFindableElement; "pos-navigation": HTMLPosNavigationElement; + "pos-navigation-bar": HTMLPosNavigationBarElement; "pos-new-thing-form": HTMLPosNewThingFormElement; "pos-picture": HTMLPosPictureElement; "pos-predicate": HTMLPosPredicateElement; @@ -1033,6 +1060,10 @@ declare namespace LocalJSX { "onPod-os:link"?: (event: PosNavigationCustomEvent) => void; "uri"?: string; } + interface PosNavigationBar { + "current"?: Thing; + "onPod-os:navigate"?: (event: PosNavigationBarCustomEvent) => void; + } interface PosNewThingForm { "onPod-os:error"?: (event: PosNewThingFormCustomEvent) => void; "onPod-os:init"?: (event: PosNewThingFormCustomEvent) => void; @@ -1140,6 +1171,7 @@ declare namespace LocalJSX { "pos-login-form": PosLoginForm; "pos-make-findable": PosMakeFindable; "pos-navigation": PosNavigation; + "pos-navigation-bar": PosNavigationBar; "pos-new-thing-form": PosNewThingForm; "pos-picture": PosPicture; "pos-predicate": PosPredicate; @@ -1196,6 +1228,7 @@ declare module "@stencil/core" { "pos-login-form": LocalJSX.PosLoginForm & JSXBase.HTMLAttributes; "pos-make-findable": LocalJSX.PosMakeFindable & JSXBase.HTMLAttributes; "pos-navigation": LocalJSX.PosNavigation & JSXBase.HTMLAttributes; + "pos-navigation-bar": LocalJSX.PosNavigationBar & JSXBase.HTMLAttributes; "pos-new-thing-form": LocalJSX.PosNewThingForm & JSXBase.HTMLAttributes; "pos-picture": LocalJSX.PosPicture & JSXBase.HTMLAttributes; "pos-predicate": LocalJSX.PosPredicate & JSXBase.HTMLAttributes; From 0065dada4e11eb3218e953b90cf4cffb78d6c196 Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Tue, 8 Jul 2025 08:12:51 +0200 Subject: [PATCH 08/31] feat(pos-navigation): move pos-make-findable to navigation bar; allow empty current resource --- elements/src/components.d.ts | 4 +- .../pos-navigation/bar/pos-navigation-bar.css | 4 ++ .../bar/pos-navigation-bar.spec.tsx | 50 +++++++++++++++++++ .../pos-navigation/bar/pos-navigation-bar.tsx | 5 +- .../pos-navigation/pos-navigation.spec.tsx | 26 +++------- .../pos-navigation/pos-navigation.tsx | 12 +++-- 6 files changed, 76 insertions(+), 25 deletions(-) diff --git a/elements/src/components.d.ts b/elements/src/components.d.ts index f2f859a6..8c2d79c6 100644 --- a/elements/src/components.d.ts +++ b/elements/src/components.d.ts @@ -101,7 +101,8 @@ export namespace Components { "uri": string; } interface PosNavigationBar { - "current": Thing; + "current"?: Thing; + "searchIndexReady": boolean; } interface PosNewThingForm { "referenceUri": string; @@ -1063,6 +1064,7 @@ declare namespace LocalJSX { interface PosNavigationBar { "current"?: Thing; "onPod-os:navigate"?: (event: PosNavigationBarCustomEvent) => void; + "searchIndexReady"?: boolean; } interface PosNewThingForm { "onPod-os:error"?: (event: PosNewThingFormCustomEvent) => void; diff --git a/elements/src/components/pos-navigation/bar/pos-navigation-bar.css b/elements/src/components/pos-navigation/bar/pos-navigation-bar.css index e69de29b..5424e9c1 100644 --- a/elements/src/components/pos-navigation/bar/pos-navigation-bar.css +++ b/elements/src/components/pos-navigation/bar/pos-navigation-bar.css @@ -0,0 +1,4 @@ +nav { + display: flex; + gap: 0; +} diff --git a/elements/src/components/pos-navigation/bar/pos-navigation-bar.spec.tsx b/elements/src/components/pos-navigation/bar/pos-navigation-bar.spec.tsx index 0e0facc0..2d4ad403 100644 --- a/elements/src/components/pos-navigation/bar/pos-navigation-bar.spec.tsx +++ b/elements/src/components/pos-navigation/bar/pos-navigation-bar.spec.tsx @@ -2,10 +2,12 @@ import { h } from '@stencil/core'; import { newSpecPage } from '@stencil/core/testing'; import { PosNavigationBar } from './pos-navigation-bar'; import { screen } from '@testing-library/dom'; +import '@testing-library/jest-dom'; describe('pos-navigation-bar', () => { it('shows the resource label', async () => { const mockThing = { + uri: 'https://test.pod/resource/1234567890', label: () => 'Test Label', }; @@ -26,6 +28,54 @@ describe('pos-navigation-bar', () => { `); }); + it('shows nothing if current resource is not set', async () => { + const page = await newSpecPage({ + components: [PosNavigationBar], + template: () => , + supportsShadowDom: false, + }); + + expect(page.root).toEqualHtml(` + + + + `); + }); + + describe('make findable', () => { + it('shows pos-make-findable when searchIndexReady is true', async () => { + const mockThing = { + uri: 'https://test.pod/resource/1234567890', + label: () => 'Test Label', + }; + + const page = await newSpecPage({ + components: [PosNavigationBar], + template: () => , + supportsShadowDom: false, + }); + + const makeFindable = page.root.querySelector('pos-make-findable'); + expect(makeFindable).not.toBeNull(); + }); + + it('hides pos-make-findable when searchIndexReady is false', async () => { + const mockThing = { + uri: 'https://test.pod/resource/1234567890', + label: () => 'Test Label', + }; + + const page = await newSpecPage({ + components: [PosNavigationBar], + template: () => , + supportsShadowDom: false, + }); + + const makeFindable = page.root.querySelector('pos-make-findable'); + expect(makeFindable).toBeNull(); + }); + }); + it('emits navigation event on button click', async () => { const mockThing = { label: () => 'Test Label', diff --git a/elements/src/components/pos-navigation/bar/pos-navigation-bar.tsx b/elements/src/components/pos-navigation/bar/pos-navigation-bar.tsx index c03bec11..0c0c0aa7 100644 --- a/elements/src/components/pos-navigation/bar/pos-navigation-bar.tsx +++ b/elements/src/components/pos-navigation/bar/pos-navigation-bar.tsx @@ -7,7 +7,8 @@ import { Thing } from '@pod-os/core'; styleUrl: 'pos-navigation-bar.css', }) export class PosNavigationBar { - @Prop() current: Thing; + @Prop() current?: Thing; + @Prop() searchIndexReady: boolean; @Event({ eventName: 'pod-os:navigate' }) navigate: EventEmitter; @@ -16,9 +17,11 @@ export class PosNavigationBar { } render() { + if (!this.current) return ; return ( ); } diff --git a/elements/src/components/pos-navigation/pos-navigation.spec.tsx b/elements/src/components/pos-navigation/pos-navigation.spec.tsx index 1f253f5b..af66af8d 100644 --- a/elements/src/components/pos-navigation/pos-navigation.spec.tsx +++ b/elements/src/components/pos-navigation/pos-navigation.spec.tsx @@ -3,10 +3,9 @@ import { fireEvent } from '@testing-library/dom'; import { mockSessionStore } from '../../test/mockSessionStore'; import { PosNavigation } from './pos-navigation'; import { pressKey } from '../../test/pressKey'; -import * as test from 'node:test'; describe('pos-navigation', () => { - it('renders a search bar within a form', async () => { + it('renders navigation bar and search dialog', async () => { const page = await newSpecPage({ components: [PosNavigation], html: ``, @@ -14,6 +13,7 @@ describe('pos-navigation', () => { expect(page.root).toEqualHtml(` +
@@ -129,6 +129,9 @@ describe('pos-navigation', () => { rebuild: jest.fn(), }; page.rootInstance.receivePodOs({ + store: { + get: jest.fn().mockReturnValue({ fake: 'resource' }), + }, buildSearchIndex: jest.fn().mockReturnValue(mockSearchIndex), }); @@ -140,10 +143,10 @@ describe('pos-navigation', () => { expect(page.rootInstance.searchIndex).toBeDefined(); }); - it('shows the make-findable button as soon as search index is available', () => { + it('informs navigation bar as soon as search index is available', () => { expect(page.root).toEqualHtml(` - +
@@ -154,21 +157,6 @@ describe('pos-navigation', () => { `); }); - it('does not show the make-findable button if URI is empty', async () => { - page.root.setAttribute('uri', ''); - await page.waitForChanges(); - expect(page.root).toEqualHtml(` - - - -
- -
- -
-
`); - }); - it(' searches for the typed text and shows suggestions', async () => { // when the user enters a text into the searchbar await type(page, 'test'); diff --git a/elements/src/components/pos-navigation/pos-navigation.tsx b/elements/src/components/pos-navigation/pos-navigation.tsx index 7e249a5b..8c31f00f 100644 --- a/elements/src/components/pos-navigation/pos-navigation.tsx +++ b/elements/src/components/pos-navigation/pos-navigation.tsx @@ -29,13 +29,16 @@ export class PosNavigation implements PodOsAware { @State() resource: Thing = null; @Watch('uri') + @Watch('os') async getResource() { // TODO should this be done on componentWillLoad? - this.resource = this.uri ? this.os.store.get(this.uri) : null; + // TODO why can this.uri be null + this.resource = this.uri ? this.os?.store.get(this.uri) : null; } componentWillLoad() { subscribePodOs(this); + this.getResource(); session.onChange('isLoggedIn', async isLoggedIn => { if (isLoggedIn) { await this.buildSearchIndex(); @@ -114,11 +117,12 @@ export class PosNavigation implements PodOsAware { } render() { - // TODO: move make findable to pos-navigation-bar return ( - {this.searchIndex && this.uri ? : ''} - {this.resource && } + (this.dialogRef = el as HTMLDialogElement)}>
this.onSubmit(e)}>
From 852858aadb4fa374b253e9e691b598b3efbbe095 Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Tue, 8 Jul 2025 08:20:16 +0200 Subject: [PATCH 09/31] feat(pos-app-browser): make the new navbar appear inline with the rest of the header on mobile --- .../apps/pos-app-browser/pos-app-browser.css | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/elements/src/apps/pos-app-browser/pos-app-browser.css b/elements/src/apps/pos-app-browser/pos-app-browser.css index 6cbbd958..5bfdc647 100644 --- a/elements/src/apps/pos-app-browser/pos-app-browser.css +++ b/elements/src/apps/pos-app-browser/pos-app-browser.css @@ -50,7 +50,7 @@ footer { } header { - flex-wrap: wrap; + flex-wrap: nowrap; padding: 0 var(--size-8); } @@ -59,20 +59,3 @@ main { max-height: 100vh; overflow: auto; } - -@media (max-width: 640px) { - header { - padding: 0 var(--size-1) var(--size-1); - justify-content: space-between; - } - - pos-navigation { - flex-basis: 100%; - order: 0; - } - - pos-add-new-thing, - pos-login { - order: 1; - } -} From 86f466e112c76366284dc6052c145dc28bfb50c3 Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Wed, 9 Jul 2025 07:31:58 +0200 Subject: [PATCH 10/31] refactor(pos-navigation): remove obsolete event parameter and type cast --- elements/src/components/pos-navigation/pos-navigation.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/elements/src/components/pos-navigation/pos-navigation.tsx b/elements/src/components/pos-navigation/pos-navigation.tsx index 8c31f00f..0b36495a 100644 --- a/elements/src/components/pos-navigation/pos-navigation.tsx +++ b/elements/src/components/pos-navigation/pos-navigation.tsx @@ -108,7 +108,7 @@ export class PosNavigation implements PodOsAware { } } - private onSubmit(event) { + private onSubmit() { if (this.suggestions && this.selectedIndex > -1) { this.linkEmitter.emit(this.suggestions[this.selectedIndex].ref); } else { @@ -123,8 +123,8 @@ export class PosNavigation implements PodOsAware { searchIndexReady={this.searchIndex !== undefined} current={this.resource} > - (this.dialogRef = el as HTMLDialogElement)}> - this.onSubmit(e)}> + (this.dialogRef = el)}> + this.onSubmit()}>
Date: Wed, 9 Jul 2025 07:38:25 +0200 Subject: [PATCH 11/31] feat(pos-navigation-bar): show button to trigger search if no current resource is set --- .../pos-navigation/bar/pos-navigation-bar.spec.tsx | 12 ++++++++---- .../pos-navigation/bar/pos-navigation-bar.tsx | 5 ++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/elements/src/components/pos-navigation/bar/pos-navigation-bar.spec.tsx b/elements/src/components/pos-navigation/bar/pos-navigation-bar.spec.tsx index 2d4ad403..3295fdd0 100644 --- a/elements/src/components/pos-navigation/bar/pos-navigation-bar.spec.tsx +++ b/elements/src/components/pos-navigation/bar/pos-navigation-bar.spec.tsx @@ -36,10 +36,14 @@ describe('pos-navigation-bar', () => { }); expect(page.root).toEqualHtml(` - - - - `); + + + + `); }); describe('make findable', () => { diff --git a/elements/src/components/pos-navigation/bar/pos-navigation-bar.tsx b/elements/src/components/pos-navigation/bar/pos-navigation-bar.tsx index 0c0c0aa7..60c2ba98 100644 --- a/elements/src/components/pos-navigation/bar/pos-navigation-bar.tsx +++ b/elements/src/components/pos-navigation/bar/pos-navigation-bar.tsx @@ -17,11 +17,10 @@ export class PosNavigationBar { } render() { - if (!this.current) return ; return ( ); } From 6aedd375ad17128c230ed3e1bb83e2e39988e77d Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Wed, 9 Jul 2025 08:06:46 +0200 Subject: [PATCH 12/31] feat(pos-navigation-bar): style nav bar to look more like a browser address bar --- .../apps/pos-app-browser/pos-app-browser.css | 2 ++ .../pos-navigation/bar/pos-navigation-bar.css | 17 +++++++++++++++++ .../bar/pos-navigation-bar.spec.tsx | 16 ++++++++++------ .../pos-navigation/bar/pos-navigation-bar.tsx | 6 ++++-- 4 files changed, 33 insertions(+), 8 deletions(-) diff --git a/elements/src/apps/pos-app-browser/pos-app-browser.css b/elements/src/apps/pos-app-browser/pos-app-browser.css index 5bfdc647..b7ce92e8 100644 --- a/elements/src/apps/pos-app-browser/pos-app-browser.css +++ b/elements/src/apps/pos-app-browser/pos-app-browser.css @@ -52,6 +52,8 @@ footer { header { flex-wrap: nowrap; padding: 0 var(--size-8); + margin-top: var(--size-1); + margin-bottom: var(--size-1); } main { diff --git a/elements/src/components/pos-navigation/bar/pos-navigation-bar.css b/elements/src/components/pos-navigation/bar/pos-navigation-bar.css index 5424e9c1..c891dac9 100644 --- a/elements/src/components/pos-navigation/bar/pos-navigation-bar.css +++ b/elements/src/components/pos-navigation/bar/pos-navigation-bar.css @@ -1,4 +1,21 @@ nav { display: flex; + height: var(--size-8); gap: 0; } + +section.current { + display: flex; + flex-grow: 1; + gap: 0; + background-color: var(--pos-input-background-color); + border-radius: var(--radius-md); + button { + cursor: pointer; + flex-grow: 1; + background: none; + color: var(--pos-normal-text-color); + outline: none; + border: none; + } +} diff --git a/elements/src/components/pos-navigation/bar/pos-navigation-bar.spec.tsx b/elements/src/components/pos-navigation/bar/pos-navigation-bar.spec.tsx index 3295fdd0..62b96f4e 100644 --- a/elements/src/components/pos-navigation/bar/pos-navigation-bar.spec.tsx +++ b/elements/src/components/pos-navigation/bar/pos-navigation-bar.spec.tsx @@ -20,9 +20,11 @@ describe('pos-navigation-bar', () => { expect(page.root).toEqualHtml(` `); @@ -38,9 +40,11 @@ describe('pos-navigation-bar', () => { expect(page.root).toEqualHtml(` `); diff --git a/elements/src/components/pos-navigation/bar/pos-navigation-bar.tsx b/elements/src/components/pos-navigation/bar/pos-navigation-bar.tsx index 60c2ba98..566dd462 100644 --- a/elements/src/components/pos-navigation/bar/pos-navigation-bar.tsx +++ b/elements/src/components/pos-navigation/bar/pos-navigation-bar.tsx @@ -19,8 +19,10 @@ export class PosNavigationBar { render() { return ( ); } From 4ac636e2cc080b0602f8dbebd6eae71d506bb6f7 Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Wed, 9 Jul 2025 21:27:39 +0200 Subject: [PATCH 13/31] feat(pos-app-browser, pos-navigation-bar): ensure navbar can shrink and shortens text without wrap --- .../apps/pos-app-browser/pos-app-browser.css | 16 ++++++++++---- .../pos-navigation/bar/pos-navigation-bar.css | 21 ++++++++++++------- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/elements/src/apps/pos-app-browser/pos-app-browser.css b/elements/src/apps/pos-app-browser/pos-app-browser.css index b7ce92e8..e624602f 100644 --- a/elements/src/apps/pos-app-browser/pos-app-browser.css +++ b/elements/src/apps/pos-app-browser/pos-app-browser.css @@ -9,11 +9,14 @@ pos-router { pos-navigation { max-width: var(--width-lg); margin: 0; + min-width: var(--size-32); + flex-shrink: 1; /* Ensure navigation can shrink */ } pos-add-new-thing, pos-login { - flex: 0 1 auto; /* Behält die Breite des Inhalts bei */ + flex: 0 1 auto; + flex-shrink: 0; /* Ensure those items don't shrink */ } header, @@ -51,9 +54,7 @@ footer { header { flex-wrap: nowrap; - padding: 0 var(--size-8); - margin-top: var(--size-1); - margin-bottom: var(--size-1); + padding: var(--size-1) var(--size-8); } main { @@ -61,3 +62,10 @@ main { max-height: 100vh; overflow: auto; } + +@media (max-width: 640px) { + header { + padding: var(--size-1); + justify-content: space-between; + } +} diff --git a/elements/src/components/pos-navigation/bar/pos-navigation-bar.css b/elements/src/components/pos-navigation/bar/pos-navigation-bar.css index c891dac9..49367270 100644 --- a/elements/src/components/pos-navigation/bar/pos-navigation-bar.css +++ b/elements/src/components/pos-navigation/bar/pos-navigation-bar.css @@ -10,12 +10,17 @@ section.current { gap: 0; background-color: var(--pos-input-background-color); border-radius: var(--radius-md); - button { - cursor: pointer; - flex-grow: 1; - background: none; - color: var(--pos-normal-text-color); - outline: none; - border: none; - } + width: 100%; +} + +section.current button { + cursor: pointer; + flex-grow: 1; + background: none; + color: var(--pos-normal-text-color); + outline: none; + border: none; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } From 171529bb8b4e2b225162cfc68d25c0a14fa93e59 Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Fri, 11 Jul 2025 08:09:40 +0200 Subject: [PATCH 14/31] feat(pos-navigation): position the search dialog seamlessly above the navbar --- .../pos-navigation/pos-navigation.css | 51 +++++++++++++----- .../pos-navigation/pos-navigation.spec.tsx | 53 +++++++++---------- .../pos-navigation/pos-navigation.tsx | 22 ++++---- 3 files changed, 74 insertions(+), 52 deletions(-) diff --git a/elements/src/components/pos-navigation/pos-navigation.css b/elements/src/components/pos-navigation/pos-navigation.css index c44760ca..ff968fa6 100644 --- a/elements/src/components/pos-navigation/pos-navigation.css +++ b/elements/src/components/pos-navigation/pos-navigation.css @@ -1,3 +1,7 @@ +.container { + position: relative; +} + .suggestions ol { border: 1px solid var(--pos-border-color); display: flex; @@ -35,22 +39,41 @@ --uri-color: var(--pos-subtle-text-color); } -ion-searchbar { +dialog { + position: absolute; + margin-top: calc(-1 * var(--size-8)); + padding: 0; width: 100%; -} - -form { - display: flex; - flex-direction: row; - align-items: center; -} - -.bar { - flex-grow: 1; + max-width: 100%; + min-width: 100%; + min-height: var(--size-32); + background-color: var(--pos-background-color); + color: var(--pos-normal-text-color); + border: var(--pos-border-color); + border-radius: var(--radius-md); + box-shadow: var(--shadow-md); + form { + display: flex; + flex-direction: column; + height: 100%; + width: 100%; + align-items: center; + input { + display: flex; + height: var(--size-8); + border-radius: var(--radius-md); + padding-left: var(--size-2); + width: 100%; + border: none; + outline: none; + color: var(--pos-normal-text-color); + background-color: var(--pos-input-background-color); + box-sizing: border-box; + } + } } dialog[open] { - margin-top: 0; - width: 80%; // TODO better width value - padding: 0; + display: flex; + z-index: var(--layer-top); } diff --git a/elements/src/components/pos-navigation/pos-navigation.spec.tsx b/elements/src/components/pos-navigation/pos-navigation.spec.tsx index af66af8d..48374a2c 100644 --- a/elements/src/components/pos-navigation/pos-navigation.spec.tsx +++ b/elements/src/components/pos-navigation/pos-navigation.spec.tsx @@ -9,19 +9,18 @@ describe('pos-navigation', () => { const page = await newSpecPage({ components: [PosNavigation], html: ``, + supportsShadowDom: false, }); expect(page.root).toEqualHtml(` - - - - -
- -
- -
-
+
+ + +
+ +
+
+
`); }); @@ -44,14 +43,14 @@ describe('pos-navigation', () => { expect(page.root).toEqualHtml(` - - -
-
+
+ + + -
- -
+ +
+
`); }); @@ -64,13 +63,13 @@ describe('pos-navigation', () => { }); const dialog = page.root.querySelector('dialog'); - dialog.showModal = jest.fn(); + dialog.show = jest.fn(); // when a "navigate" event is emitted fireEvent(page.root, new CustomEvent('pod-os:navigate')); // then the dialog should be shown - expect(dialog.showModal).toHaveBeenCalled(); + expect(dialog.show).toHaveBeenCalled(); }); it('navigates to entered URI when form is submitted', async () => { @@ -146,14 +145,14 @@ describe('pos-navigation', () => { it('informs navigation bar as soon as search index is available', () => { expect(page.root).toEqualHtml(` - - -
-
- -
-
-
+
+ + +
+ +
+
+
`); }); diff --git a/elements/src/components/pos-navigation/pos-navigation.tsx b/elements/src/components/pos-navigation/pos-navigation.tsx index 0b36495a..01712498 100644 --- a/elements/src/components/pos-navigation/pos-navigation.tsx +++ b/elements/src/components/pos-navigation/pos-navigation.tsx @@ -60,7 +60,7 @@ export class PosNavigation implements PodOsAware { @Listen('pod-os:navigate') openNavigationDialog() { - this.dialogRef?.showModal(); + this.dialogRef?.show(); } private clearSearchIndex() { @@ -119,13 +119,13 @@ export class PosNavigation implements PodOsAware { render() { return ( - - (this.dialogRef = el)}> -
this.onSubmit()}> -
+
+ + (this.dialogRef = el)}> + this.onSubmit()}>
) : null} -
-
-
+ +
+
); } From 218d496ca37f6dfa44b124db139326d2afb6f887 Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Mon, 14 Jul 2025 17:22:36 +0200 Subject: [PATCH 15/31] test: fix type issues --- .../{ => __test}/pos-navigation.integration.spec.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename elements/src/components/pos-navigation/{ => __test}/pos-navigation.integration.spec.tsx (92%) diff --git a/elements/src/components/pos-navigation/pos-navigation.integration.spec.tsx b/elements/src/components/pos-navigation/__test/pos-navigation.integration.spec.tsx similarity index 92% rename from elements/src/components/pos-navigation/pos-navigation.integration.spec.tsx rename to elements/src/components/pos-navigation/__test/pos-navigation.integration.spec.tsx index afcc6250..f926cf7d 100644 --- a/elements/src/components/pos-navigation/pos-navigation.integration.spec.tsx +++ b/elements/src/components/pos-navigation/__test/pos-navigation.integration.spec.tsx @@ -14,7 +14,7 @@ describe('pos-navigation', () => { const os = mockPodOS(); // and it can build a search index - const searchIndex = { search: jest.fn() } as SearchIndex; + const searchIndex = { search: jest.fn() } as unknown as SearchIndex; os.buildSearchIndex.mockResolvedValue(searchIndex); // and a search for "test" gives a result @@ -23,11 +23,11 @@ describe('pos-navigation', () => { .mockReturnValue([ { ref: 'https://result.test', - }, + } as any, ]); // and a user profile can be fetched - const profile = { fake: 'profile of the user' } as WebIdProfile; + const profile = { fake: 'profile of the user' } as unknown as WebIdProfile; os.fetchProfile.mockResolvedValue(profile); // and a page with a navigation bar @@ -69,7 +69,7 @@ async function waitUntilLoggedIn() { await new Promise((resolve, reject) => { unsubscribe = session.onChange('isLoggedIn', isLoggedIn => { if (isLoggedIn) { - resolve(); + resolve({}); } else { reject(); } From 8dce3dc06576e1ee2ad8a830223e7b4d27662313 Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Mon, 14 Jul 2025 17:30:28 +0200 Subject: [PATCH 16/31] feat: debounce search so that it waits until the user finished typing --- .../pos-navigation.integration.spec.tsx | 16 +++---- .../{ => __test}/pos-navigation.spec.tsx | 45 ++++++++----------- .../pos-navigation/__test/typeToSearch.ts | 12 +++++ .../pos-navigation/pos-navigation.tsx | 12 ++++- 4 files changed, 48 insertions(+), 37 deletions(-) rename elements/src/components/pos-navigation/{ => __test}/pos-navigation.spec.tsx (93%) create mode 100644 elements/src/components/pos-navigation/__test/typeToSearch.ts diff --git a/elements/src/components/pos-navigation/__test/pos-navigation.integration.spec.tsx b/elements/src/components/pos-navigation/__test/pos-navigation.integration.spec.tsx index f926cf7d..5ca9ff7c 100644 --- a/elements/src/components/pos-navigation/__test/pos-navigation.integration.spec.tsx +++ b/elements/src/components/pos-navigation/__test/pos-navigation.integration.spec.tsx @@ -2,11 +2,12 @@ import { SearchIndex, WebIdProfile } from '@pod-os/core'; import { newSpecPage } from '@stencil/core/testing'; import { fireEvent, getByText } from '@testing-library/dom'; import { when } from 'jest-when'; -import session from '../../store/session'; -import { mockPodOS } from '../../test/mockPodOS'; -import { PosApp } from '../pos-app/pos-app'; -import { PosRichLink } from '../pos-rich-link/pos-rich-link'; -import { PosNavigation } from './pos-navigation'; +import session from '../../../store/session'; +import { mockPodOS } from '../../../test/mockPodOS'; +import { PosApp } from '../../pos-app/pos-app'; +import { PosRichLink } from '../../pos-rich-link/pos-rich-link'; +import { PosNavigation } from '../pos-navigation'; +import { typeToSearch } from './typeToSearch'; describe('pos-navigation', () => { it('can search after login', async () => { @@ -50,10 +51,7 @@ describe('pos-navigation', () => { await page.waitForChanges(); // when the user types "test" into the navigation bar - const searchBar = page.root.querySelector('input'); - // @ts-ignore - searchBar.value = 'test'; - fireEvent(searchBar, new CustomEvent('change', { target: { value: 'test' } })); + await typeToSearch(page, 'test'); // then a search is triggered expect(searchIndex.search).toHaveBeenCalledWith('test'); diff --git a/elements/src/components/pos-navigation/pos-navigation.spec.tsx b/elements/src/components/pos-navigation/__test/pos-navigation.spec.tsx similarity index 93% rename from elements/src/components/pos-navigation/pos-navigation.spec.tsx rename to elements/src/components/pos-navigation/__test/pos-navigation.spec.tsx index 48374a2c..6d334bad 100644 --- a/elements/src/components/pos-navigation/pos-navigation.spec.tsx +++ b/elements/src/components/pos-navigation/__test/pos-navigation.spec.tsx @@ -1,8 +1,9 @@ import { newSpecPage } from '@stencil/core/testing'; import { fireEvent } from '@testing-library/dom'; -import { mockSessionStore } from '../../test/mockSessionStore'; -import { PosNavigation } from './pos-navigation'; -import { pressKey } from '../../test/pressKey'; +import { mockSessionStore } from '../../../test/mockSessionStore'; +import { PosNavigation } from '../pos-navigation'; +import { pressKey } from '../../../test/pressKey'; +import { typeToSearch } from './typeToSearch'; describe('pos-navigation', () => { it('renders navigation bar and search dialog', async () => { @@ -85,7 +86,7 @@ describe('pos-navigation', () => { page.root.addEventListener('pod-os:link', linkEventListener); // when the user enters a URI into the searchbar - await type(page, 'https://resource.test/'); + await typeToSearch(page, 'https://resource.test/'); // and then submits the form const form = page.root.querySelector('form'); @@ -158,10 +159,10 @@ describe('pos-navigation', () => { it(' searches for the typed text and shows suggestions', async () => { // when the user enters a text into the searchbar - await type(page, 'test'); + await typeToSearch(page, 'test'); // then the search is triggered - expect(mockSearchIndex.search).toHaveBeenCalledWith('test'); + expect(mockSearchIndex.search).toHaveBeenNthCalledWith(1, 'test'); // and the results are shown as suggestions let suggestions = page.root.querySelector('.suggestions'); @@ -181,13 +182,13 @@ describe('pos-navigation', () => { it('clears the suggestions when nothing is entered', async () => { // given the user entered a text into the searchbar - await type(page, 'test'); + await typeToSearch(page, 'test'); // and suggestions are shown expect(page.root.querySelectorAll('.suggestions li')).toHaveLength(2); // when the input is cleared - await type(page, ''); + await typeToSearch(page, ''); // then no suggestions are shown expect(page.root.querySelector('.suggestions')).toBeNull(); @@ -196,7 +197,7 @@ describe('pos-navigation', () => { describe('keyboard selection', () => { it('selects the first suggestion when pressing key down', async () => { // when the user enters a text into the searchbar - await type(page, 'test'); + await typeToSearch(page, 'test'); // and then presses the down arrow key await pressKey(page, 'ArrowDown'); @@ -209,7 +210,7 @@ describe('pos-navigation', () => { it('selects the second suggestion when pressing key down twice', async () => { // when the user enters a text into the searchbar - await type(page, 'test'); + await typeToSearch(page, 'test'); // and then presses the down arrow key twice await pressKey(page, 'ArrowDown'); @@ -223,7 +224,7 @@ describe('pos-navigation', () => { it('selects the first suggestion when moving down twice than up', async () => { // when the user enters a text into the searchbar - await type(page, 'test'); + await typeToSearch(page, 'test'); // and then presses the down arrow key twice await pressKey(page, 'ArrowDown'); @@ -238,7 +239,7 @@ describe('pos-navigation', () => { it('cannot move further up than top result', async () => { // when the user enters a text into the searchbar - await type(page, 'test'); + await typeToSearch(page, 'test'); // and then presses the down arrow key twice await pressKey(page, 'ArrowDown'); @@ -253,7 +254,7 @@ describe('pos-navigation', () => { it('cannot move further down than the last result', async () => { // when the user enters a text into the searchbar - await type(page, 'test'); + await typeToSearch(page, 'test'); // and then presses the down arrow key twice await pressKey(page, 'ArrowDown'); @@ -270,7 +271,7 @@ describe('pos-navigation', () => { it('does not clear suggestions when clicked on itself', async () => { // given the user entered a text into the searchbar - await type(page, 'test'); + await typeToSearch(page, 'test'); // and suggestions are shown expect(page.root.querySelectorAll('.suggestions li')).toHaveLength(2); @@ -286,7 +287,7 @@ describe('pos-navigation', () => { it('clears the suggestions when clicked elsewhere in the document', async () => { // given the user entered a text into the searchbar - await type(page, 'test'); + await typeToSearch(page, 'test'); // and suggestions are shown expect(page.root.querySelectorAll('.suggestions li')).toHaveLength(2); @@ -301,7 +302,7 @@ describe('pos-navigation', () => { it('clears the suggestions when escape is pressed', async () => { // given the user entered a text into the searchbar - await type(page, 'test'); + await typeToSearch(page, 'test'); // and suggestions are shown expect(page.root.querySelectorAll('.suggestions li')).toHaveLength(2); @@ -315,7 +316,7 @@ describe('pos-navigation', () => { it('clears the suggestions when navigating elsewhere', async () => { // given the user entered a text into the searchbar - await type(page, 'test'); + await typeToSearch(page, 'test'); // and suggestions are shown expect(page.root.querySelectorAll('.suggestions li')).toHaveLength(2); @@ -334,7 +335,7 @@ describe('pos-navigation', () => { page.root.addEventListener('pod-os:link', linkEventListener); // when the user enters a text into the searchbar - await type(page, 'test'); + await typeToSearch(page, 'test'); // and then presses the down arrow key to select the first result await pressKey(page, 'ArrowDown'); @@ -392,11 +393,3 @@ describe('pos-navigation', () => { }); }); }); - -async function type(page, text: string) { - const searchBar = page.root.querySelector('input'); - searchBar.value = text; - // @ts-ignore - fireEvent(searchBar, new CustomEvent('change', { target: { value: text } })); - await page.waitForChanges(); -} diff --git a/elements/src/components/pos-navigation/__test/typeToSearch.ts b/elements/src/components/pos-navigation/__test/typeToSearch.ts new file mode 100644 index 00000000..3c73f2f8 --- /dev/null +++ b/elements/src/components/pos-navigation/__test/typeToSearch.ts @@ -0,0 +1,12 @@ +import { fireEvent } from '@testing-library/dom'; + +export async function typeToSearch(page, text: string) { + jest.useFakeTimers(); + const searchBar = page.root.querySelector('input'); + searchBar.value = text; + // @ts-ignore + fireEvent(searchBar, new CustomEvent('change', { target: { value: text } })); + jest.advanceTimersByTime(300); // advance debounce time + jest.useRealTimers(); + await page.waitForChanges(); +} diff --git a/elements/src/components/pos-navigation/pos-navigation.tsx b/elements/src/components/pos-navigation/pos-navigation.tsx index 01712498..d76a3f18 100644 --- a/elements/src/components/pos-navigation/pos-navigation.tsx +++ b/elements/src/components/pos-navigation/pos-navigation.tsx @@ -1,5 +1,6 @@ import { PodOS, SearchIndex, Thing } from '@pod-os/core'; import { Component, Event, EventEmitter, h, Host, Listen, Prop, State, Watch } from '@stencil/core'; +import { debounceTime, Subject } from 'rxjs'; import session from '../../store/session'; import { PodOsAware, PodOsEventEmitter, subscribePodOs } from '../events/PodOsAware'; @@ -28,6 +29,9 @@ export class PosNavigation implements PodOsAware { @State() resource: Thing = null; + private readonly changeEvents = new Subject(); + private debouncedSearch = null; + @Watch('uri') @Watch('os') async getResource() { @@ -46,6 +50,11 @@ export class PosNavigation implements PodOsAware { this.clearSearchIndex(); } }); + this.debouncedSearch = this.changeEvents.pipe(debounceTime(300)).subscribe(() => this.search()); + } + + disconnectedCallback() { + this.debouncedSearch?.unsubscribe(); } @Listen('pod-os:search:index-created') @@ -73,8 +82,7 @@ export class PosNavigation implements PodOsAware { private onChange(event) { this.value = event.target.value; - // TODO add debounce - this.search(); + this.changeEvents.next(); } @Listen('click', { target: 'document' }) From 01387be2e6389ad84c286a3849b18ce4a58f3401 Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Mon, 14 Jul 2025 17:41:36 +0200 Subject: [PATCH 17/31] refactor: remove unnecessary css styles --- elements/src/components/pos-navigation/pos-navigation.css | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/elements/src/components/pos-navigation/pos-navigation.css b/elements/src/components/pos-navigation/pos-navigation.css index ff968fa6..c6f9c98f 100644 --- a/elements/src/components/pos-navigation/pos-navigation.css +++ b/elements/src/components/pos-navigation/pos-navigation.css @@ -8,9 +8,7 @@ flex-direction: column; margin: 0; padding: 0; - z-index: var(--layer-top); list-style-type: none; - box-shadow: var(--shadow-xl); } .suggestions { @@ -46,7 +44,7 @@ dialog { width: 100%; max-width: 100%; min-width: 100%; - min-height: var(--size-32); + min-height: var(--size-24); background-color: var(--pos-background-color); color: var(--pos-normal-text-color); border: var(--pos-border-color); From f87df21e680af3f10366e642f3f9c8a946e6ea6d Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Mon, 14 Jul 2025 17:55:37 +0200 Subject: [PATCH 18/31] feat: close dialog when clicked or navigated elsewhere; clear suggestions only when navigating away --- .../__test/pos-navigation.spec.tsx | 24 ++++++++++++++----- .../pos-navigation/pos-navigation.tsx | 8 ++++++- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/elements/src/components/pos-navigation/__test/pos-navigation.spec.tsx b/elements/src/components/pos-navigation/__test/pos-navigation.spec.tsx index 6d334bad..d2a5ca0b 100644 --- a/elements/src/components/pos-navigation/__test/pos-navigation.spec.tsx +++ b/elements/src/components/pos-navigation/__test/pos-navigation.spec.tsx @@ -285,7 +285,10 @@ describe('pos-navigation', () => { expect(page.root.querySelectorAll('.suggestions li')).toHaveLength(2); }); - it('clears the suggestions when clicked elsewhere in the document', async () => { + it('closes the suggestions when clicked elsewhere in the document', async () => { + const dialog = page.root.querySelector('dialog'); + dialog.close = jest.fn(); + // given the user entered a text into the searchbar await typeToSearch(page, 'test'); @@ -296,11 +299,14 @@ describe('pos-navigation', () => { page.doc.click(); await page.waitForChanges(); - // then the suggestions are cleared - expect(page.root.querySelector('.suggestions')).toBeNull(); + // then the dialog is closed + expect(dialog.close).toHaveBeenCalled(); }); - it('clears the suggestions when escape is pressed', async () => { + it('closes the suggestions when escape is pressed', async () => { + const dialog = page.root.querySelector('dialog'); + dialog.close = jest.fn(); + // given the user entered a text into the searchbar await typeToSearch(page, 'test'); @@ -310,11 +316,14 @@ describe('pos-navigation', () => { // when the user presses escape await pressKey(page, 'Escape'); - // then the suggestions are cleared - expect(page.root.querySelector('.suggestions')).toBeNull(); + // then the dialog is closed + expect(dialog.close).toHaveBeenCalled(); }); it('clears the suggestions when navigating elsewhere', async () => { + const dialog = page.root.querySelector('dialog'); + dialog.close = jest.fn(); + // given the user entered a text into the searchbar await typeToSearch(page, 'test'); @@ -327,6 +336,9 @@ describe('pos-navigation', () => { // then the suggestions are cleared expect(page.root.querySelector('.suggestions')).toBeNull(); + + // and the dialog is closed + expect(dialog.close).toHaveBeenCalled(); }); it('navigates to selected suggestion when the form is submitted', async () => { diff --git a/elements/src/components/pos-navigation/pos-navigation.tsx b/elements/src/components/pos-navigation/pos-navigation.tsx index d76a3f18..e3a21a8a 100644 --- a/elements/src/components/pos-navigation/pos-navigation.tsx +++ b/elements/src/components/pos-navigation/pos-navigation.tsx @@ -86,9 +86,15 @@ export class PosNavigation implements PodOsAware { } @Listen('click', { target: 'document' }) + closeDialog() { + this.dialogRef?.close(); + this.selectedIndex = -1; + } + @Listen('pod-os:link') clearSuggestions() { this.suggestions = []; + this.dialogRef?.close(); this.selectedIndex = -1; } @@ -100,7 +106,7 @@ export class PosNavigation implements PodOsAware { @Listen('keydown') handleKeyDown(ev: KeyboardEvent) { if (ev.key === 'Escape') { - this.clearSuggestions(); + this.closeDialog(); } else if (ev.key === 'ArrowDown') { ev.preventDefault(); this.selectedIndex = Math.min(this.selectedIndex + 1, this.suggestions.length - 1); From 8b2308796a232cc98c57dabcc557b75b9313ec21 Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Mon, 14 Jul 2025 18:19:24 +0200 Subject: [PATCH 19/31] feat(pos-navigation): search for the current resource when the dialog opens --- .../__test/pos-navigation.spec.tsx | 27 +++++++++++++++++++ .../bar/pos-navigation-bar.spec.tsx | 6 ++++- .../pos-navigation/bar/pos-navigation-bar.tsx | 2 +- .../pos-navigation/pos-navigation.tsx | 11 +++++++- 4 files changed, 43 insertions(+), 3 deletions(-) diff --git a/elements/src/components/pos-navigation/__test/pos-navigation.spec.tsx b/elements/src/components/pos-navigation/__test/pos-navigation.spec.tsx index d2a5ca0b..4c347ce9 100644 --- a/elements/src/components/pos-navigation/__test/pos-navigation.spec.tsx +++ b/elements/src/components/pos-navigation/__test/pos-navigation.spec.tsx @@ -180,6 +180,33 @@ describe('pos-navigation', () => { ); }); + it('searches for the current resource on navigate event', async () => { + const dialog = page.root.querySelector('dialog'); + dialog.show = jest.fn(); + + // when a "navigate" event is emitted + fireEvent( + page.root, + new CustomEvent('pod-os:navigate', { detail: { uri: 'https://pod.example/current-resource' } }), + ); + + // then the dialog should be shown and search for the current resource + expect(dialog.show).toHaveBeenCalled(); + expect(mockSearchIndex.search).toHaveBeenNthCalledWith(1, 'https://pod.example/current-resource'); + }); + + it('does not search on navigate event if current resource is missing', async () => { + const dialog = page.root.querySelector('dialog'); + dialog.show = jest.fn(); + + // when a "navigate" event is emitted + fireEvent(page.root, new CustomEvent('pod-os:navigate', null)); + + // then the dialog should be shown but no search is triggered + expect(dialog.show).toHaveBeenCalled(); + expect(mockSearchIndex.search).not.toHaveBeenCalled(); + }); + it('clears the suggestions when nothing is entered', async () => { // given the user entered a text into the searchbar await typeToSearch(page, 'test'); diff --git a/elements/src/components/pos-navigation/bar/pos-navigation-bar.spec.tsx b/elements/src/components/pos-navigation/bar/pos-navigation-bar.spec.tsx index 62b96f4e..dbd0bc41 100644 --- a/elements/src/components/pos-navigation/bar/pos-navigation-bar.spec.tsx +++ b/elements/src/components/pos-navigation/bar/pos-navigation-bar.spec.tsx @@ -84,8 +84,9 @@ describe('pos-navigation-bar', () => { }); }); - it('emits navigation event on button click', async () => { + it('emits navigation event with current resource on button click', async () => { const mockThing = { + uri: 'https://test.pod/resource/1234567890', label: () => 'Test Label', }; @@ -103,6 +104,9 @@ describe('pos-navigation-bar', () => { expect(onNavigate).toHaveBeenCalledWith( expect.objectContaining({ type: 'pod-os:navigate', + detail: expect.objectContaining({ + uri: 'https://test.pod/resource/1234567890', + }), }), ); }); diff --git a/elements/src/components/pos-navigation/bar/pos-navigation-bar.tsx b/elements/src/components/pos-navigation/bar/pos-navigation-bar.tsx index 566dd462..d1357952 100644 --- a/elements/src/components/pos-navigation/bar/pos-navigation-bar.tsx +++ b/elements/src/components/pos-navigation/bar/pos-navigation-bar.tsx @@ -13,7 +13,7 @@ export class PosNavigationBar { @Event({ eventName: 'pod-os:navigate' }) navigate: EventEmitter; private onClick() { - this.navigate.emit(); + this.navigate.emit(this.current); } render() { diff --git a/elements/src/components/pos-navigation/pos-navigation.tsx b/elements/src/components/pos-navigation/pos-navigation.tsx index e3a21a8a..c347ce3d 100644 --- a/elements/src/components/pos-navigation/pos-navigation.tsx +++ b/elements/src/components/pos-navigation/pos-navigation.tsx @@ -5,6 +5,10 @@ import { debounceTime, Subject } from 'rxjs'; import session from '../../store/session'; import { PodOsAware, PodOsEventEmitter, subscribePodOs } from '../events/PodOsAware'; +interface NavigateEvent { + detail: Thing | null; +} + @Component({ tag: 'pos-navigation', shadow: true, @@ -68,7 +72,12 @@ export class PosNavigation implements PodOsAware { } @Listen('pod-os:navigate') - openNavigationDialog() { + openNavigationDialog(e: NavigateEvent) { + this.resource = e.detail; + if (e.detail) { + this.value = e.detail.uri; + this.search(); + } this.dialogRef?.show(); } From b8f1ac38492618b2f6b68e2e4689ff6802a7ca6d Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Mon, 14 Jul 2025 18:28:03 +0200 Subject: [PATCH 20/31] feat(pos-make-findable): align board radius with navigation bar --- elements/src/components/pos-make-findable/pos-make-findable.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elements/src/components/pos-make-findable/pos-make-findable.css b/elements/src/components/pos-make-findable/pos-make-findable.css index 009aa7a7..81bb97b6 100644 --- a/elements/src/components/pos-make-findable/pos-make-findable.css +++ b/elements/src/components/pos-make-findable/pos-make-findable.css @@ -12,7 +12,7 @@ button.main { width: var(--size-8); align-items: center; justify-content: center; - border-radius: var(--radius-xs); + border-radius: var(--radius-md); color: var(--pos-subtle-text-color); border: var(--size-px) dashed var(--pos-subtle-text-color); background-color: var(--pos-background-color); From 9bb189d232f2faea438cc21a0fceb2094b20c092 Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Mon, 14 Jul 2025 18:45:59 +0200 Subject: [PATCH 21/31] feat(pos-navigation): full width mobile navigation bar & larger font size --- .../pos-navigation/pos-navigation.css | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/elements/src/components/pos-navigation/pos-navigation.css b/elements/src/components/pos-navigation/pos-navigation.css index c6f9c98f..7ec96426 100644 --- a/elements/src/components/pos-navigation/pos-navigation.css +++ b/elements/src/components/pos-navigation/pos-navigation.css @@ -75,3 +75,22 @@ dialog[open] { display: flex; z-index: var(--layer-top); } + +@media (max-width: 640px) { + .container { + position: unset; + } + dialog { + margin-top: var(--size-1); + top: 0; + width: 99dvw; + max-width: unset; + min-width: unset; + form { + input { + height: var(--size-10); + font-size: var(--scale-fluid-2); + } + } + } +} From 0d88a50b4c73c4cc06099f9356a11198ff4d91a1 Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Mon, 14 Jul 2025 19:01:54 +0200 Subject: [PATCH 22/31] refactor(pos-navigation): make state private and clarify meaning of value state / uri prop --- elements/src/components.d.ts | 6 ++++ .../__test/pos-navigation.spec.tsx | 2 +- .../pos-navigation/pos-navigation.tsx | 31 +++++++++++-------- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/elements/src/components.d.ts b/elements/src/components.d.ts index 8c2d79c6..7f5bda7d 100644 --- a/elements/src/components.d.ts +++ b/elements/src/components.d.ts @@ -98,6 +98,9 @@ export namespace Components { "uri": string; } interface PosNavigation { + /** + * Initial value of the navigation bar + */ "uri": string; } interface PosNavigationBar { @@ -1059,6 +1062,9 @@ declare namespace LocalJSX { interface PosNavigation { "onPod-os:init"?: (event: PosNavigationCustomEvent) => void; "onPod-os:link"?: (event: PosNavigationCustomEvent) => void; + /** + * Initial value of the navigation bar + */ "uri"?: string; } interface PosNavigationBar { diff --git a/elements/src/components/pos-navigation/__test/pos-navigation.spec.tsx b/elements/src/components/pos-navigation/__test/pos-navigation.spec.tsx index 4c347ce9..70b9a6e2 100644 --- a/elements/src/components/pos-navigation/__test/pos-navigation.spec.tsx +++ b/elements/src/components/pos-navigation/__test/pos-navigation.spec.tsx @@ -39,7 +39,7 @@ describe('pos-navigation', () => { }, }; - await page.rootInstance.getResource('https://pod.example/resource'); + await page.rootInstance.updateResource('https://pod.example/resource'); await page.waitForChanges(); expect(page.root).toEqualHtml(` diff --git a/elements/src/components/pos-navigation/pos-navigation.tsx b/elements/src/components/pos-navigation/pos-navigation.tsx index c347ce3d..a88b406e 100644 --- a/elements/src/components/pos-navigation/pos-navigation.tsx +++ b/elements/src/components/pos-navigation/pos-navigation.tsx @@ -15,38 +15,43 @@ interface NavigateEvent { styleUrl: 'pos-navigation.css', }) export class PosNavigation implements PodOsAware { - @State() os: PodOS; + @State() private os: PodOS; private dialogRef?: HTMLDialogElement; @Event({ eventName: 'pod-os:init' }) subscribePodOs: PodOsEventEmitter; + + /** + * Initial value of the navigation bar + */ @Prop() uri: string = ''; - @State() value: string = this.uri; + /** + * Current value of the input field + */ + @State() private inputValue: string = this.uri; @Event({ eventName: 'pod-os:link' }) linkEmitter: EventEmitter; @State() searchIndex?: SearchIndex = undefined; - @State() suggestions = []; + @State() private suggestions = []; - @State() selectedIndex = -1; + @State() private selectedIndex = -1; - @State() resource: Thing = null; + @State() private resource: Thing = null; private readonly changeEvents = new Subject(); private debouncedSearch = null; @Watch('uri') @Watch('os') - async getResource() { - // TODO should this be done on componentWillLoad? - // TODO why can this.uri be null + updateResource(): void { this.resource = this.uri ? this.os?.store.get(this.uri) : null; } componentWillLoad() { subscribePodOs(this); - this.getResource(); + this.updateResource(); session.onChange('isLoggedIn', async isLoggedIn => { if (isLoggedIn) { await this.buildSearchIndex(); @@ -75,7 +80,7 @@ export class PosNavigation implements PodOsAware { openNavigationDialog(e: NavigateEvent) { this.resource = e.detail; if (e.detail) { - this.value = e.detail.uri; + this.inputValue = e.detail.uri; this.search(); } this.dialogRef?.show(); @@ -90,7 +95,7 @@ export class PosNavigation implements PodOsAware { }; private onChange(event) { - this.value = event.target.value; + this.inputValue = event.target.value; this.changeEvents.next(); } @@ -127,7 +132,7 @@ export class PosNavigation implements PodOsAware { private search() { if (this.searchIndex) { - this.suggestions = this.value ? this.searchIndex.search(this.value) : []; + this.suggestions = this.inputValue ? this.searchIndex.search(this.inputValue) : []; } } @@ -135,7 +140,7 @@ export class PosNavigation implements PodOsAware { if (this.suggestions && this.selectedIndex > -1) { this.linkEmitter.emit(this.suggestions[this.selectedIndex].ref); } else { - this.linkEmitter.emit(this.value); + this.linkEmitter.emit(this.inputValue); } } From fb613c071f5c0e5ffc8c0d7545c87812a6447459 Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Mon, 14 Jul 2025 19:22:15 +0200 Subject: [PATCH 23/31] refactor(pos-navigation): apply focus styles for better accessibility and keyboard usage --- .../src/components/pos-make-findable/pos-make-findable.css | 3 +++ .../components/pos-navigation/bar/pos-navigation-bar.css | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/elements/src/components/pos-make-findable/pos-make-findable.css b/elements/src/components/pos-make-findable/pos-make-findable.css index 81bb97b6..23980892 100644 --- a/elements/src/components/pos-make-findable/pos-make-findable.css +++ b/elements/src/components/pos-make-findable/pos-make-findable.css @@ -35,6 +35,9 @@ button.main { transform: scale(0.99); filter: brightness(90%); } + &:focus { + outline: var(--pos-input-focus-outline); + } } .options { diff --git a/elements/src/components/pos-navigation/bar/pos-navigation-bar.css b/elements/src/components/pos-navigation/bar/pos-navigation-bar.css index 49367270..eb4e13f4 100644 --- a/elements/src/components/pos-navigation/bar/pos-navigation-bar.css +++ b/elements/src/components/pos-navigation/bar/pos-navigation-bar.css @@ -11,6 +11,9 @@ section.current { background-color: var(--pos-input-background-color); border-radius: var(--radius-md); width: 100%; + &:focus-within { + outline: var(--pos-input-focus-outline); + } } section.current button { @@ -23,4 +26,7 @@ section.current button { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + &:focus { + text-decoration: underline; + } } From 0c946c43d176c7d1480043e4c7c4146319ec812b Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Mon, 14 Jul 2025 19:45:17 +0200 Subject: [PATCH 24/31] feat(pos-navigation): add html search element for better accessibility --- .../__test/pos-navigation.spec.tsx | 42 +++++++++++-------- .../pos-navigation/bar/pos-navigation-bar.css | 7 +--- .../bar/pos-navigation-bar.spec.tsx | 24 +++++------ .../pos-navigation/bar/pos-navigation-bar.tsx | 10 ++--- .../pos-navigation/pos-navigation.css | 4 +- .../pos-navigation/pos-navigation.tsx | 10 ++--- 6 files changed, 46 insertions(+), 51 deletions(-) diff --git a/elements/src/components/pos-navigation/__test/pos-navigation.spec.tsx b/elements/src/components/pos-navigation/__test/pos-navigation.spec.tsx index 70b9a6e2..c7d9684d 100644 --- a/elements/src/components/pos-navigation/__test/pos-navigation.spec.tsx +++ b/elements/src/components/pos-navigation/__test/pos-navigation.spec.tsx @@ -14,14 +14,16 @@ describe('pos-navigation', () => { }); expect(page.root).toEqualHtml(` -
+
+ +
`); }); @@ -44,14 +46,16 @@ describe('pos-navigation', () => { expect(page.root).toEqualHtml(` -
- - -
- -
-
-
+
`); }); @@ -146,14 +150,16 @@ describe('pos-navigation', () => { it('informs navigation bar as soon as search index is available', () => { expect(page.root).toEqualHtml(` -
- - -
- -
-
-
+
`); }); diff --git a/elements/src/components/pos-navigation/bar/pos-navigation-bar.css b/elements/src/components/pos-navigation/bar/pos-navigation-bar.css index eb4e13f4..254220e9 100644 --- a/elements/src/components/pos-navigation/bar/pos-navigation-bar.css +++ b/elements/src/components/pos-navigation/bar/pos-navigation-bar.css @@ -1,11 +1,6 @@ -nav { - display: flex; - height: var(--size-8); - gap: 0; -} - section.current { display: flex; + height: var(--size-8); flex-grow: 1; gap: 0; background-color: var(--pos-input-background-color); diff --git a/elements/src/components/pos-navigation/bar/pos-navigation-bar.spec.tsx b/elements/src/components/pos-navigation/bar/pos-navigation-bar.spec.tsx index dbd0bc41..22a9b551 100644 --- a/elements/src/components/pos-navigation/bar/pos-navigation-bar.spec.tsx +++ b/elements/src/components/pos-navigation/bar/pos-navigation-bar.spec.tsx @@ -19,13 +19,11 @@ describe('pos-navigation-bar', () => { expect(page.root).toEqualHtml(` - +
+ +
`); }); @@ -39,13 +37,11 @@ describe('pos-navigation-bar', () => { expect(page.root).toEqualHtml(` - +
+ +
`); }); diff --git a/elements/src/components/pos-navigation/bar/pos-navigation-bar.tsx b/elements/src/components/pos-navigation/bar/pos-navigation-bar.tsx index d1357952..af7cc07e 100644 --- a/elements/src/components/pos-navigation/bar/pos-navigation-bar.tsx +++ b/elements/src/components/pos-navigation/bar/pos-navigation-bar.tsx @@ -18,12 +18,10 @@ export class PosNavigationBar { render() { return ( - +
+ {this.current && this.searchIndexReady && } + +
); } } diff --git a/elements/src/components/pos-navigation/pos-navigation.css b/elements/src/components/pos-navigation/pos-navigation.css index 7ec96426..c41fd051 100644 --- a/elements/src/components/pos-navigation/pos-navigation.css +++ b/elements/src/components/pos-navigation/pos-navigation.css @@ -1,4 +1,4 @@ -.container { +search { position: relative; } @@ -77,7 +77,7 @@ dialog[open] { } @media (max-width: 640px) { - .container { + search { position: unset; } dialog { diff --git a/elements/src/components/pos-navigation/pos-navigation.tsx b/elements/src/components/pos-navigation/pos-navigation.tsx index a88b406e..0e7227ec 100644 --- a/elements/src/components/pos-navigation/pos-navigation.tsx +++ b/elements/src/components/pos-navigation/pos-navigation.tsx @@ -1,5 +1,5 @@ import { PodOS, SearchIndex, Thing } from '@pod-os/core'; -import { Component, Event, EventEmitter, h, Host, Listen, Prop, State, Watch } from '@stencil/core'; +import { Component, Event, EventEmitter, h, Listen, Prop, State, Watch } from '@stencil/core'; import { debounceTime, Subject } from 'rxjs'; import session from '../../store/session'; @@ -146,8 +146,8 @@ export class PosNavigation implements PodOsAware { render() { return ( - -
+
-
- + + ); } } From 162a9eb0802e27b9168b1b860c51bc8f013b2959 Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Mon, 14 Jul 2025 20:27:47 +0200 Subject: [PATCH 25/31] feat(pos-navigation): animation & border radius --- .../pos-navigation/pos-navigation.css | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/elements/src/components/pos-navigation/pos-navigation.css b/elements/src/components/pos-navigation/pos-navigation.css index c41fd051..f82c51ba 100644 --- a/elements/src/components/pos-navigation/pos-navigation.css +++ b/elements/src/components/pos-navigation/pos-navigation.css @@ -44,7 +44,9 @@ dialog { width: 100%; max-width: 100%; min-width: 100%; - min-height: var(--size-24); + overflow: hidden; + overflow-y: scroll; + max-height: 100dvh; background-color: var(--pos-background-color); color: var(--pos-normal-text-color); border: var(--pos-border-color); @@ -59,7 +61,8 @@ dialog { input { display: flex; height: var(--size-8); - border-radius: var(--radius-md); + border-top-left-radius: var(--radius-md); + border-top-right-radius: var(--radius-md); padding-left: var(--size-2); width: 100%; border: none; @@ -74,6 +77,7 @@ dialog { dialog[open] { display: flex; z-index: var(--layer-top); + animation: slideIn 100ms ease-out; } @media (max-width: 640px) { @@ -94,3 +98,12 @@ dialog[open] { } } } + +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} From 8c8fdb2de1c77af5d11a72fd542b91bac7e1614e Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Mon, 14 Jul 2025 20:28:14 +0200 Subject: [PATCH 26/31] doc: update changelog for pos-navigation changes --- elements/CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/elements/CHANGELOG.md b/elements/CHANGELOG.md index 634fdd97..5e068933 100644 --- a/elements/CHANGELOG.md +++ b/elements/CHANGELOG.md @@ -9,7 +9,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### ⚠ BREAKING CHANGES - [pos-navigation](../docs/elements/components/pos-navigation) - - `pos-navigation-bar` has been renamed to `pos-navigation` and will undergo further major changes + - `pos-navigation-bar` has been renamed to `pos-navigation` + - `pos-navigation` is now a more complex navigation widget, not just an input field ### Fixed From eb8a9a87c6666b66ef239db8f28ccdb61276bd22 Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Mon, 14 Jul 2025 21:50:31 +0200 Subject: [PATCH 27/31] feat: improve aria label for search button --- .../pos-navigation/bar/pos-navigation-bar.spec.tsx | 4 ++-- .../components/pos-navigation/bar/pos-navigation-bar.tsx | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/elements/src/components/pos-navigation/bar/pos-navigation-bar.spec.tsx b/elements/src/components/pos-navigation/bar/pos-navigation-bar.spec.tsx index 22a9b551..8b63ebe3 100644 --- a/elements/src/components/pos-navigation/bar/pos-navigation-bar.spec.tsx +++ b/elements/src/components/pos-navigation/bar/pos-navigation-bar.spec.tsx @@ -20,7 +20,7 @@ describe('pos-navigation-bar', () => { expect(page.root).toEqualHtml(`
-
@@ -38,7 +38,7 @@ describe('pos-navigation-bar', () => { expect(page.root).toEqualHtml(`
-
diff --git a/elements/src/components/pos-navigation/bar/pos-navigation-bar.tsx b/elements/src/components/pos-navigation/bar/pos-navigation-bar.tsx index af7cc07e..779ff255 100644 --- a/elements/src/components/pos-navigation/bar/pos-navigation-bar.tsx +++ b/elements/src/components/pos-navigation/bar/pos-navigation-bar.tsx @@ -17,10 +17,14 @@ export class PosNavigationBar { } render() { + const ariaLabel = this.current ? `${this.current.label()} (Click to search or enter URI)` : 'Search or enter URI'; + return (
{this.current && this.searchIndexReady && } - +
); } From e5653ba96dd8667b6f4095df6a06ff95ba9a2840 Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Mon, 14 Jul 2025 21:50:52 +0200 Subject: [PATCH 28/31] test: adjust e2e tests to new pos-navigation --- apps/tests/accessibility.spec.ts | 13 +++++---- apps/tests/add-literal-value.spec.ts | 10 +++---- apps/tests/add-new-thing.spec.ts | 11 ++++--- apps/tests/fixtures/index.ts | 10 +++++++ apps/tests/generic.spec.ts | 9 +++--- apps/tests/ldp-container.spec.ts | 10 +++---- apps/tests/page-objects/NavigationBar.ts | 37 ++++++++++++++++++++++++ apps/tests/person.spec.ts | 12 ++++---- apps/tests/reading-while-offline.spec.ts | 13 ++++----- apps/tests/text-search.spec.ts | 33 +++++++++++---------- 10 files changed, 106 insertions(+), 52 deletions(-) create mode 100644 apps/tests/fixtures/index.ts create mode 100644 apps/tests/page-objects/NavigationBar.ts diff --git a/apps/tests/accessibility.spec.ts b/apps/tests/accessibility.spec.ts index 7cca5c17..154d4048 100644 --- a/apps/tests/accessibility.spec.ts +++ b/apps/tests/accessibility.spec.ts @@ -1,9 +1,14 @@ -import { test, expect } from "@playwright/test"; +import { expect } from "@playwright/test"; import AxeBuilder from "@axe-core/playwright"; +import { test } from "./fixtures"; + test.describe("accessibility", () => { ["light", "dark"].forEach((colorScheme: "light" | "dark") => { - test(`no contrast issues in ${colorScheme} mode`, async ({ page }) => { + test(`no contrast issues in ${colorScheme} mode`, async ({ + page, + navigationBar, + }) => { await test.step(`given the preferred color scheme is set to ${colorScheme}`, async () => { await page.emulateMedia({ colorScheme }); }); @@ -11,11 +16,9 @@ test.describe("accessibility", () => { await test.step("when viewing a typical page in PodOS Browser", async () => { await page.goto("/"); - const navigationBar = page.getByPlaceholder("Enter URI"); - await navigationBar.fill( + await navigationBar.fillAndSubmit( "http://localhost:4000/alice/public/generic/resource#it", ); - await navigationBar.press("Enter"); const heading = page.getByRole("heading"); await expect(heading).toHaveText("Something"); diff --git a/apps/tests/add-literal-value.spec.ts b/apps/tests/add-literal-value.spec.ts index 1b18a957..93d5cc07 100644 --- a/apps/tests/add-literal-value.spec.ts +++ b/apps/tests/add-literal-value.spec.ts @@ -1,17 +1,17 @@ -import { expect, test } from "@playwright/test"; +import { expect } from "@playwright/test"; import { signIn } from "./actions/signIn"; import { alice } from "./fixtures/credentials"; -test("can add a literal value", async ({ page }) => { +import { test } from "./fixtures"; + +test("can add a literal value", async ({ page, navigationBar }) => { // when opening PodOS Browser await page.goto("/"); // and navigating to a generic resource - const navigationBar = page.getByPlaceholder("Enter URI"); - await navigationBar.fill( + await navigationBar.fillAndSubmit( "http://localhost:4000/alice/public/generic/resource#it", ); - await navigationBar.press("Enter"); // then I cannot see any input to add literal values const missingAddLiteralField = page.getByPlaceholder("Add literal"); diff --git a/apps/tests/add-new-thing.spec.ts b/apps/tests/add-new-thing.spec.ts index a08f42a9..ef5d5e8a 100644 --- a/apps/tests/add-new-thing.spec.ts +++ b/apps/tests/add-new-thing.spec.ts @@ -1,17 +1,16 @@ -import { expect, test } from "@playwright/test"; +import { expect } from "@playwright/test"; import { signIn } from "./actions/signIn"; import { alice } from "./fixtures/credentials"; +import { test } from "./fixtures"; -test("can add a new thing", async ({ page }) => { +test("can add a new thing", async ({ page, navigationBar }) => { // when opening PodOS Browser await page.goto("/"); // and navigating to a container - const navigationBar = page.getByPlaceholder("Enter URI"); - await navigationBar.fill( + await navigationBar.fillAndSubmit( "http://localhost:4000/alice/acb50d31-42af-4d4c-9ead-e2d5e70d7317/", ); - await navigationBar.press("Enter"); // when signing in as the pod owner await signIn(page, alice); @@ -37,7 +36,7 @@ test("can add a new thing", async ({ page }) => { await page.keyboard.press("Enter"); // then I am at the page showing the new thing - await expect(navigationBar).toHaveValue( + expect(await navigationBar.inputValue()).toEqual( "http://localhost:4000/alice/acb50d31-42af-4d4c-9ead-e2d5e70d7317/my-new-thing#it", ); diff --git a/apps/tests/fixtures/index.ts b/apps/tests/fixtures/index.ts new file mode 100644 index 00000000..56b061eb --- /dev/null +++ b/apps/tests/fixtures/index.ts @@ -0,0 +1,10 @@ +import { test as base } from "playwright/test"; +import { NavigationBar } from "../page-objects/NavigationBar"; + +export const test = base.extend<{ + navigationBar: NavigationBar; +}>({ + navigationBar: async ({ page }, use) => { + await use(new NavigationBar(page)); + }, +}); diff --git a/apps/tests/generic.spec.ts b/apps/tests/generic.spec.ts index 4c4fdae5..383b9a74 100644 --- a/apps/tests/generic.spec.ts +++ b/apps/tests/generic.spec.ts @@ -1,17 +1,18 @@ -import { test, expect } from "@playwright/test"; +import { expect } from "@playwright/test"; + +import { test } from "./fixtures"; test("show generic information about unknown types of things", async ({ page, + navigationBar, }) => { // when opening PodOS Browser await page.goto("/"); // and navigating to a generic resource - const navigationBar = page.getByPlaceholder("Enter URI"); - await navigationBar.fill( + await navigationBar.fillAndSubmit( "http://localhost:4000/alice/public/generic/resource#it", ); - await navigationBar.press("Enter"); // then page shows a heading with the resource name const heading = page.getByRole("heading"); diff --git a/apps/tests/ldp-container.spec.ts b/apps/tests/ldp-container.spec.ts index de9b62a7..926eab87 100644 --- a/apps/tests/ldp-container.spec.ts +++ b/apps/tests/ldp-container.spec.ts @@ -1,13 +1,13 @@ -import { test, expect } from "@playwright/test"; +import { expect } from "@playwright/test"; -test("show contents of an LDP container", async ({ page }) => { +import { test } from "./fixtures"; + +test("show contents of an LDP container", async ({ page, navigationBar }) => { // when opening PodOS Browser await page.goto("/"); // and navigating to a ldp container resource - const navigationBar = page.getByPlaceholder("Enter URI"); - await navigationBar.fill("http://localhost:4000/alice/container/"); - await navigationBar.press("Enter"); + await navigationBar.fillAndSubmit("http://localhost:4000/alice/container/"); // then page shows the full container URL as heading const heading = await page.getByRole("heading", { level: 1 }); diff --git a/apps/tests/page-objects/NavigationBar.ts b/apps/tests/page-objects/NavigationBar.ts new file mode 100644 index 00000000..5d977c81 --- /dev/null +++ b/apps/tests/page-objects/NavigationBar.ts @@ -0,0 +1,37 @@ +import { Locator, Page } from "@playwright/test"; + +export class NavigationBar { + private readonly nav: Locator; + private readonly close: () => Promise; + + constructor(private page: Page) { + this.nav = page.getByRole("navigation"); + this.close = () => page.keyboard.press("Escape"); + } + + async fill(text: string) { + const input = await this.activateInput(); + await input.fill(text); + return input; + } + + async fillAndSubmit(text: string) { + const input = await this.fill(text); + await input.press("Enter"); + } + + async activateInput() { + const button = this.nav.getByRole("button", { + name: "Search or enter URI", + }); + await button.click(); + return this.page.getByPlaceholder("Search or enter URI"); + } + + async inputValue() { + const input = await this.activateInput(); + const value = await input.inputValue(); + await this.close(); + return value; + } +} diff --git a/apps/tests/person.spec.ts b/apps/tests/person.spec.ts index 9523764c..0f93cc92 100644 --- a/apps/tests/person.spec.ts +++ b/apps/tests/person.spec.ts @@ -1,13 +1,15 @@ -import { test, expect } from "@playwright/test"; +import { expect } from "@playwright/test"; -test("show name as heading for a person", async ({ page }) => { +import { test } from "./fixtures"; + +test("show name as heading for a person", async ({ page, navigationBar }) => { // when opening PodOS Browser await page.goto("/"); // and navigating to Alice's WebID - const navigationBar = page.getByPlaceholder("Enter URI"); - await navigationBar.fill("http://localhost:4000/alice/profile/card#me"); - await navigationBar.press("Enter"); + await navigationBar.fillAndSubmit( + "http://localhost:4000/alice/profile/card#me", + ); // then the heading shows Alice's name as heading const label = await page.getByRole("heading"); diff --git a/apps/tests/reading-while-offline.spec.ts b/apps/tests/reading-while-offline.spec.ts index 75b694ba..36927105 100644 --- a/apps/tests/reading-while-offline.spec.ts +++ b/apps/tests/reading-while-offline.spec.ts @@ -1,9 +1,12 @@ -import { test, expect } from "@playwright/test"; +import { expect } from "@playwright/test"; + +import { test } from "./fixtures"; test("can access cached resources while offline", async ({ page, context, browserName, + navigationBar, }) => { test.skip( browserName === "webkit", @@ -17,11 +20,9 @@ test("can access cached resources while offline", async ({ }); await test.step("and the they have visited a resource", async () => { - const navigationBar = page.getByPlaceholder("Enter URI"); - await navigationBar.fill( + await navigationBar.fillAndSubmit( "http://localhost:4000/alice/public/generic/resource#it", ); - await navigationBar.press("Enter"); const heading = page.getByRole("heading"); await expect(heading).toHaveText("Something"); @@ -36,11 +37,9 @@ test("can access cached resources while offline", async ({ }); await test.step("and visits the previously loaded resource", async () => { - const navigationBar = page.getByPlaceholder("Enter URI"); - await navigationBar.fill( + await navigationBar.fillAndSubmit( "http://localhost:4000/alice/public/generic/resource#it", ); - await navigationBar.press("Enter"); }); await test.step("then the cached content is still accessible", async () => { diff --git a/apps/tests/text-search.spec.ts b/apps/tests/text-search.spec.ts index 2a07932f..6cd10f64 100644 --- a/apps/tests/text-search.spec.ts +++ b/apps/tests/text-search.spec.ts @@ -1,24 +1,27 @@ -import { test, expect } from "@playwright/test"; +import { expect } from "@playwright/test"; import { signIn } from "./actions/signIn"; import { alice } from "./fixtures/credentials"; +import { test } from "./fixtures"; test.describe("Text search", () => { - test("finds a thing based on the label index", async ({ page }) => { + test("finds a thing based on the label index", async ({ + page, + navigationBar, + }) => { await test.step("given a user is signed in", async () => { await page.goto("/"); await signIn(page, alice); }); await test.step("when they search for a text and select the first result", async () => { - const navigationBar = page.getByPlaceholder("Search or enter URI"); - await navigationBar.fill("ometh"); + const input = await navigationBar.fill("ometh"); const result = page .getByRole("listitem") .filter({ hasText: "Something" }); await expect(result).toBeVisible(); - await navigationBar.press("ArrowDown"); - await navigationBar.press("Enter"); + await input.press("ArrowDown"); + await input.press("Enter"); }); await test.step("then the page shows the selected resource", async () => { @@ -29,26 +32,27 @@ test.describe("Text search", () => { }); }); - test("can find a thing, after making it findable", async ({ page }) => { + test("can find a thing, after making it findable", async ({ + page, + navigationBar, + }) => { await test.step("given a user is signed in", async () => { await page.goto("/"); await signIn(page, alice); }); + const input = await navigationBar.activateInput(); + await test.step("and there is no search result for 'Banana'", async () => { - const navigationBar = page.getByPlaceholder("Search or enter URI"); - await navigationBar.fill("Banana"); + await input.fill("Banana"); const result = page.getByRole("listitem").filter({ hasText: "Banana" }); await expect(result).not.toBeVisible(); }); await test.step("when they navigate to the thing", async () => { - const navigationBar = page.getByPlaceholder("Enter URI"); - await navigationBar.fill( - "http://localhost:4000/alice/make-findable/banana#it", - ); - await navigationBar.press("Enter"); + await input.fill("http://localhost:4000/alice/make-findable/banana#it"); + await input.press("Enter"); await expect(page.getByRole("heading", { name: "Banana" })).toBeVisible(); }); @@ -57,7 +61,6 @@ test.describe("Text search", () => { }); await test.step("then they will find it when retrying the search", async () => { - const navigationBar = page.getByPlaceholder("Search or enter URI"); await navigationBar.fill("Banana"); const result = page.getByRole("listitem").filter({ hasText: "Banana" }); From aeaa877c7d99d936f07d631cb44ca2bc4deef63b Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Mon, 14 Jul 2025 21:58:12 +0200 Subject: [PATCH 29/31] feat: scroll suggestions, not whole dialog --- elements/src/components/pos-navigation/pos-navigation.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/elements/src/components/pos-navigation/pos-navigation.css b/elements/src/components/pos-navigation/pos-navigation.css index f82c51ba..802090b9 100644 --- a/elements/src/components/pos-navigation/pos-navigation.css +++ b/elements/src/components/pos-navigation/pos-navigation.css @@ -13,6 +13,8 @@ search { .suggestions { width: 100%; + overflow-y: auto; + max-height: 90dvh; li { padding: 1rem; background-color: var(--pos-background-color); @@ -45,7 +47,6 @@ dialog { max-width: 100%; min-width: 100%; overflow: hidden; - overflow-y: scroll; max-height: 100dvh; background-color: var(--pos-background-color); color: var(--pos-normal-text-color); From a846802b79db0a9326c1102d3919f4b521266feb Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Mon, 14 Jul 2025 22:20:10 +0200 Subject: [PATCH 30/31] fix: unset resource if URI is invalid --- .../__test/pos-navigation.spec.tsx | 45 ++++++++++++++++++- .../pos-navigation/pos-navigation.tsx | 6 ++- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/elements/src/components/pos-navigation/__test/pos-navigation.spec.tsx b/elements/src/components/pos-navigation/__test/pos-navigation.spec.tsx index c7d9684d..efc85007 100644 --- a/elements/src/components/pos-navigation/__test/pos-navigation.spec.tsx +++ b/elements/src/components/pos-navigation/__test/pos-navigation.spec.tsx @@ -41,7 +41,6 @@ describe('pos-navigation', () => { }, }; - await page.rootInstance.updateResource('https://pod.example/resource'); await page.waitForChanges(); expect(page.root).toEqualHtml(` @@ -59,6 +58,50 @@ describe('pos-navigation', () => {
`); }); + it('updates current resource when uri changes', async () => { + const page = await newSpecPage({ + components: [PosNavigation], + html: ``, + supportsShadowDom: false, + }); + + const mockResource = { fake: 'resource' }; + page.rootInstance.os = { + store: { + get: jest.fn().mockReturnValue(mockResource), + }, + }; + + page.rootInstance.uri = 'https://pod.example/resource'; + page.rootInstance.updateResource(mockResource); + + expect(page.rootInstance.resource).toEqual(mockResource); + }); + + it('sets current resource to null when uri is invalid', async () => { + const page = await newSpecPage({ + components: [PosNavigation], + html: ``, + supportsShadowDom: false, + }); + + const mockResource = { fake: 'resource' }; + const get = jest.fn(); + get.mockImplementation(() => { + throw new Error('Invalid URI'); + }); + page.rootInstance.os = { + store: { + get, + }, + }; + + page.rootInstance.uri = 'irrelevant, since store mock throws error'; + page.rootInstance.updateResource(mockResource); + + expect(page.rootInstance.resource).toEqual(null); + }); + it('opens the dialog when navigate event is emitted', async () => { // given a page with a navigation const page = await newSpecPage({ diff --git a/elements/src/components/pos-navigation/pos-navigation.tsx b/elements/src/components/pos-navigation/pos-navigation.tsx index 0e7227ec..3d3bd4a6 100644 --- a/elements/src/components/pos-navigation/pos-navigation.tsx +++ b/elements/src/components/pos-navigation/pos-navigation.tsx @@ -46,7 +46,11 @@ export class PosNavigation implements PodOsAware { @Watch('uri') @Watch('os') updateResource(): void { - this.resource = this.uri ? this.os?.store.get(this.uri) : null; + try { + this.resource = this.uri ? this.os?.store.get(this.uri) : null; + } catch { + this.resource = null; + } } componentWillLoad() { From bc81f0deb9096615682f79f344be883645e46a66 Mon Sep 17 00:00:00 2001 From: Angelo Veltens Date: Mon, 14 Jul 2025 22:22:51 +0200 Subject: [PATCH 31/31] doc: update component readmes --- docs/elements/apps/pos-app-browser/readme.md | 5 +- .../pos-container-contents/readme.md | 13 +++-- .../components/pos-make-findable/readme.md | 4 +- .../components/pos-navigation/bar/readme.md | 43 ++++++++++++++++ .../components/pos-navigation/readme.md | 49 +++++++++++++++++++ 5 files changed, 104 insertions(+), 10 deletions(-) create mode 100644 docs/elements/components/pos-navigation/bar/readme.md create mode 100644 docs/elements/components/pos-navigation/readme.md diff --git a/docs/elements/apps/pos-app-browser/readme.md b/docs/elements/apps/pos-app-browser/readme.md index 4945b766..70fa11c6 100644 --- a/docs/elements/apps/pos-app-browser/readme.md +++ b/docs/elements/apps/pos-app-browser/readme.md @@ -48,13 +48,12 @@ graph TD; pos-add-new-thing --> pos-new-thing-form pos-dialog --> ion-icon pos-new-thing-form --> pos-select-term - pos-navigation --> pos-make-findable - pos-navigation --> ion-searchbar + pos-navigation --> pos-navigation-bar pos-navigation --> pos-rich-link + pos-navigation-bar --> pos-make-findable pos-make-findable --> pos-resource pos-make-findable --> pos-label pos-resource --> ion-progress-bar - ion-searchbar --> ion-icon pos-rich-link --> pos-resource pos-rich-link --> pos-label pos-rich-link --> pos-description diff --git a/docs/elements/components/pos-container-contents/readme.md b/docs/elements/components/pos-container-contents/readme.md index 8a0c015e..4328d3a9 100644 --- a/docs/elements/components/pos-container-contents/readme.md +++ b/docs/elements/components/pos-container-contents/readme.md @@ -9,7 +9,6 @@ | Event | Description | Type | | ----------------- | ----------- | ------------------ | -| `pod-os:link` | | `CustomEvent` | | `pod-os:resource` | | `CustomEvent` | @@ -17,18 +16,22 @@ ### Used by - - [pos-container-contents](.) + - [pos-app-ldp-container](../../apps/pos-app-ldp-container) ### Depends on -- ion-icon +- [pos-resource](../pos-resource) +- [pos-container-item](.) ### Graph ```mermaid graph TD; - pos-container-item --> ion-icon + pos-container-contents --> pos-resource pos-container-contents --> pos-container-item - style pos-container-item fill:#f9f,stroke:#333,stroke-width:4px + pos-resource --> ion-progress-bar + pos-container-item --> ion-icon + pos-app-ldp-container --> pos-container-contents + style pos-container-contents fill:#f9f,stroke:#333,stroke-width:4px ``` ---------------------------------------------- diff --git a/docs/elements/components/pos-make-findable/readme.md b/docs/elements/components/pos-make-findable/readme.md index bcf92853..3324ada6 100644 --- a/docs/elements/components/pos-make-findable/readme.md +++ b/docs/elements/components/pos-make-findable/readme.md @@ -23,7 +23,7 @@ ### Used by - - [pos-navigation](../pos-navigation) + - [pos-navigation-bar](../pos-navigation/bar) ### Depends on @@ -36,7 +36,7 @@ graph TD; pos-make-findable --> pos-resource pos-make-findable --> pos-label pos-resource --> ion-progress-bar - pos-navigation --> pos-make-findable + pos-navigation-bar --> pos-make-findable style pos-make-findable fill:#f9f,stroke:#333,stroke-width:4px ``` diff --git a/docs/elements/components/pos-navigation/bar/readme.md b/docs/elements/components/pos-navigation/bar/readme.md new file mode 100644 index 00000000..edcf56fb --- /dev/null +++ b/docs/elements/components/pos-navigation/bar/readme.md @@ -0,0 +1,43 @@ + + + + +## Properties + +| Property | Attribute | Description | Type | Default | +| ------------------ | -------------------- | ----------- | --------- | ----------- | +| `current` | -- | | `Thing` | `undefined` | +| `searchIndexReady` | `search-index-ready` | | `boolean` | `undefined` | + + +## Events + +| Event | Description | Type | +| ----------------- | ----------- | ------------------ | +| `pod-os:navigate` | | `CustomEvent` | + + +## Dependencies + +### Used by + + - [pos-navigation](..) + +### Depends on + +- [pos-make-findable](../../pos-make-findable) + +### Graph +```mermaid +graph TD; + pos-navigation-bar --> pos-make-findable + pos-make-findable --> pos-resource + pos-make-findable --> pos-label + pos-resource --> ion-progress-bar + pos-navigation --> pos-navigation-bar + style pos-navigation-bar fill:#f9f,stroke:#333,stroke-width:4px +``` + +---------------------------------------------- + +*Built with [StencilJS](https://stenciljs.com/)* diff --git a/docs/elements/components/pos-navigation/readme.md b/docs/elements/components/pos-navigation/readme.md new file mode 100644 index 00000000..e2652cb9 --- /dev/null +++ b/docs/elements/components/pos-navigation/readme.md @@ -0,0 +1,49 @@ + + + + +## Properties + +| Property | Attribute | Description | Type | Default | +| -------- | --------- | ----------------------------------- | -------- | ------- | +| `uri` | `uri` | Initial value of the navigation bar | `string` | `''` | + + +## Events + +| Event | Description | Type | +| ------------- | ----------- | ------------------ | +| `pod-os:init` | | `CustomEvent` | +| `pod-os:link` | | `CustomEvent` | + + +## Dependencies + +### Used by + + - [pos-app-browser](../../apps/pos-app-browser) + +### Depends on + +- [pos-navigation-bar](bar) +- [pos-rich-link](../pos-rich-link) + +### Graph +```mermaid +graph TD; + pos-navigation --> pos-navigation-bar + pos-navigation --> pos-rich-link + pos-navigation-bar --> pos-make-findable + pos-make-findable --> pos-resource + pos-make-findable --> pos-label + pos-resource --> ion-progress-bar + pos-rich-link --> pos-resource + pos-rich-link --> pos-label + pos-rich-link --> pos-description + pos-app-browser --> pos-navigation + style pos-navigation fill:#f9f,stroke:#333,stroke-width:4px +``` + +---------------------------------------------- + +*Built with [StencilJS](https://stenciljs.com/)*