Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Documentation checks
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
jobs:
docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm audit --audit-level=high
- run: npm run build
- run: npm run check:search
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,31 @@ Documentation portal for [forceCalendar](https://forcecalendar.org), built with

All content is MDX under `content/docs/`:

- **Getting started** — installation, quick start
- **Getting started** — installation, quick start, migration, release status
- **`core/`** — engine guides: calendar, events, recurrence, timezones, ICS, search, state, performance
- **`api/`** — per-class API reference (Calendar, EventStore, RecurrenceEngine, RRuleParser, ICSParser/Handler, StateManager, …)
- **`interface/`** — Web Components, views, theming, event bus
- **`salesforce/`** — LWC integration, Apex controller, Locker Service, deployment
- **`salesforce/`** — LWC integration, Apex controller, LWS, permissions, deployment
- **`agent/`** — current headless calendar APIs and integrator-owned data/access boundaries
- **`security/`** — security model and remediation history

Navigation order is controlled by `meta.json` files alongside the content.

## Development

```bash
npm install
npm ci
npm test # API coverage, internal links/navigation, and runnable Core examples
npm run dev # http://localhost:3000
npm run build
```

## Dependency maintenance

The deployment remains on Next.js 15.5.27. Compatible transitive patches are locked, with a PostCSS 8.5.28 override because Next 15 pins an older PostCSS release. CI audits high/critical advisories as well as building the docs. Audit results are time-scoped, not a security certification.

The public search endpoint at `app/api/search/route.ts` indexes the same MDX source as the sidebar. Verify searches after deployment when changing source configuration.

## Contributing

Documentation fixes are the easiest way to contribute to forceCalendar — edit the MDX under `content/docs/` and open a PR. See the [contributing guide](https://github.com/forceCalendar/.github/blob/main/CONTRIBUTING.md).
Expand Down
5 changes: 5 additions & 0 deletions app/api/search/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { source } from '@/lib/source';
import { createFromSource } from 'fumadocs-core/search/server';

// The built-in search dialog requests /api/search. Index only public docs.
export const { GET } = createFromSource(source);
3 changes: 3 additions & 0 deletions app/docs/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ export default async function Page(props: PageProps) {
<DocsBody>
<MDX components={getMDXComponents()} />
</DocsBody>
<footer className="mt-8 border-t pt-4 text-sm text-fd-muted-foreground">
forceCalendar by <a href="https://dhanawada.org" className="underline underline-offset-4">N. R. Dhanawada</a>
</footer>
</DocsPage>
);
}
Expand Down
2 changes: 1 addition & 1 deletion app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const metadata: Metadata = {
template: '%s | forceCalendar',
},
description:
'Enterprise-grade calendar infrastructure for Salesforce and strict-CSP environments. Zero dependencies.',
'Calendar engine, Web Components, framework adapters, and Salesforce LWS integration.',
};

export default function Layout({ children }: { children: ReactNode }) {
Expand Down
154 changes: 125 additions & 29 deletions content/docs/adapters/react.mdx
Original file line number Diff line number Diff line change
@@ -1,50 +1,146 @@
---
title: React
description: Use forceCalendar in React and Next.js with @forcecalendar/react
description: React 0.3.1 props, typed events, snapshots, refs, SSR, and read-only integration.
---

`@forcecalendar/react` is a thin adapter around the `<forcecal-main>` Web Component: props map to attributes, DOM events map to callbacks. It has no dependencies beyond its peers.

## Install
`@forcecalendar/react` **0.3.1** maps props, callbacks and a ref to the Interface Web Component. Install React 18 or later alongside the documented package set:

```bash
npm install @forcecalendar/react @forcecalendar/core @forcecalendar/interface
npm install @forcecalendar/react@0.3.1 @forcecalendar/core@2.5.7 @forcecalendar/interface@1.9.0
```

## Use
## Use in React or Next.js

```tsx
import { ForceCalendar } from '@forcecalendar/react';
'use client';
import { useState } from 'react';
import { ForceCalendar, type CalendarEvent } from '@forcecalendar/react';

export default function Schedule() {
const [events] = useState<CalendarEvent[]>([{
id: 'planning', title: 'Planning',
start: '2026-10-05T09:00:00Z', end: '2026-10-05T10:00:00Z',
}]);
return <ForceCalendar events={events} date="2026-10-05" height="600px"
onEventUpdated={({ event }) => console.log('Persist local edit:', event.id)} />;
}
```

In Next.js App Router, put interactive usage behind a client boundary as shown. Server rendering can emit the tag; registration runs in an effect. Imperative methods still need `whenReady()`. No `ssr: false` wrapper is required just to avoid registering custom elements on the server.

The examples below use application-provided `fetchEvents` when loading a backend; implement it with authentication, error handling and stale-response protection. UI callbacks alone do not persist edits.

## Props

| Prop | Type | Description |
| --- | --- | --- |
| `view` | `'month' \| 'week' \| 'day'` | Initial/controlled view. |
| `date` | `Date \| string` | Date the view is centred on (`Date` is serialised to ISO). |
| `locale` | `string` | BCP 47 locale, e.g. `en-AU`. |
| `timezone` | `string` | IANA time zone. |
| `weekStartsOn` | `0..6` | First day of the week (0 = Sunday). |
| `height` | `string` | CSS height of the calendar. |
| `theme` | `'slds' \| string` | Named theme preset applied as `--fc-*` custom properties. |
| `events` | `CalendarEvent[]` | Complete snapshot of events, applied through `setEvents()` (see below). |
| `removeMissingEvents` | `boolean` (default `true`) | Whether events absent from `events` are removed from the calendar. |
| `className`, `style` | | Applied to the element (`style` as an object; a string would wipe the theme tokens). |
| any other HTML attribute | `id`, `role`, `aria-*`, `data-*`, `tabIndex`, `onClick`, ... | Passed straight through to `<forcecal-main>`. |

### Callbacks

Each callback receives the `detail` of the corresponding `calendar-*` DOM event.

| Callback | DOM event | `detail` |
| --- | --- | --- |
| `onEventAdded` | `calendar-event-added` | `{ event }` |
| `onEventUpdated` | `calendar-event-updated` | `{ event }` |
| `onEventDeleted` | `calendar-event-deleted` | `{ eventId }` |
| `onEventsSet` | `calendar-events-set` | `{ events, added, updated, removed, unchanged }` |
| `onDateSelect` | `calendar-date-select` | `{ date }` |
| `onViewChange` | `calendar-view-change` | `{ view }` |
| `onNavigate` | `calendar-navigate` | `{ action, date }` |
| `onRangeChange` | `calendar-range-change` | `{ start, end, view, date }` |
| `onRangeSelect` | `calendar-range-select` | `{ start, end }` |

`onRangeChange` also reports the initial visible window right after the element is ready, so you can fetch data for it without waiting for the user to navigate. The legacy `calendar-event-add`/`-update`/`-remove` aliases are not mapped; listen to them on the element directly if you need them.

### The `events` prop

`events` is the declarative form of the element's `setEvents()`: pass the complete list and the calendar reconciles it — unchanged events are kept, changed ones replaced, new ones added and (unless `removeMissingEvents={false}`) missing ones removed. The view re-renders at most once and a single `onEventsSet` call describes the change set.

- A snapshot load does **not** emit per-event `onEventAdded` / `onEventDeleted` callbacks, so handlers that persist user edits are not triggered by your own data.
- The snapshot is re-applied whenever the array identity changes. Inline arrays (`events={[...]}`) therefore re-run the reconciliation on every render; it is cheap when nothing changed (everything lands in `unchanged`) but `onEventsSet` still fires, so wrap the array in `useMemo` or keep it in state.
- `events` is never rendered as an attribute: it is applied imperatively after mount, and is buffered by the element if it is still loading.

## Imperative handle

The ref exposes the element and all of its methods. Methods are no-ops that log a warning until the element is mounted and `@forcecalendar/interface` has loaded, so await `whenReady()` first when calling them outside an event handler.

```tsx
import { useEffect, useRef } from 'react';
import { ForceCalendar, type ForceCalendarHandle } from '@forcecalendar/react';

export function Agenda() {
const calendar = useRef<ForceCalendarHandle>(null);

useEffect(() => {
let cancelled = false;
calendar.current?.whenReady().then(async () => {
if (cancelled) return;
const range = calendar.current?.getVisibleRange();
if (!range) return;
const rows = await fetchEvents(range.start, range.end);
calendar.current?.setEvents(rows, { removeMissing: true });
});
return () => {
cancelled = true;
};
}, []);

export default function Scheduling() {
return (
<ForceCalendar
view="month"
timezone="America/New_York"
height="600px"
onDateSelect={({ date }) => console.log('selected', date)}
onEventAdded={detail => console.log('added', detail)}
/>
<>
<button onClick={() => calendar.current?.previous()}>Previous</button>
<button onClick={() => calendar.current?.today()}>Today</button>
<button onClick={() => calendar.current?.next()}>Next</button>
<button onClick={() => calendar.current?.setView('week')}>Week</button>
<ForceCalendar ref={calendar} height="600px" />
</>
);
}
```

## Props
| Member | Signature |
| --- | --- |
| `element` | `ForceCalendarElement \| null` — the underlying `<forcecal-main>` |
| `whenReady()` | `Promise<void>` — resolves once the element is defined |
| `addEvent(event)` | `CalendarEvent \| null` |
| `updateEvent(id, updates)` | `CalendarEvent \| null` |
| `deleteEvent(id)` | `boolean` |
| `getEvents()` | `CalendarEvent[]` |
| `setEvents(events, { removeMissing? })` | `EventsSetResult \| null` |
| `getVisibleRange()` | `{ start, end } \| null` (`end` inclusive) |
| `setView(view)`, `setDate(date)`, `next()`, `previous()`, `today()` | `void` |

## Types

The package exports `ForceCalendarProps`, `ForceCalendarHandle`, `CalendarEvent`, `CalendarView`, `VisibleRange`, `EventsSetOptions`, `EventsSetResult`, `ForceCalendarElement`, `ForceCalendarEventMap` and one `*Detail` type per callback. It also declares `forcecal-main` in `JSX.IntrinsicElements`, so the raw element is type-checked in JSX.

The global `HTMLElementTagNameMap` mapping belongs to `@forcecalendar/interface` 1.7 and newer. Import its element type when using the raw DOM API. The adapter keeps its exported structural types, including plain `CalendarEvent` inputs, without redeclaring the global mapping:

```ts
import type { ForceCalendarElement } from '@forcecalendar/interface';

const element: ForceCalendarElement = document.createElement('forcecal-main');
```

| Prop | Type | Maps to |
|---|---|---|
| `view` | `'month' \| 'week' \| 'day'` | `view` attribute |
| `date` | `Date \| string` | `date` attribute (Dates are serialized to ISO) |
| `locale` | `string` | `locale` attribute |
| `timezone` | `string` | `timezone` attribute (IANA name) |
| `weekStartsOn` | `0–6` | `week-starts-on` attribute |
| `height` | `string` | `height` attribute |
| `className`, `style` | — | passed through |
With interface 1.6, use the adapter's exported type explicitly for direct DOM access: `document.querySelector<ForceCalendarElement>('forcecal-main')`. The React component and its ref keep the same types across supported interface versions.

## Callbacks

`onEventAdded`, `onEventUpdated`, `onEventDeleted`, `onDateSelect`, `onViewChange`, `onNavigate` — each receives the `detail` object of the corresponding `calendar-*` DOM event.

## Next.js / SSR
### Read-only calendars

The adapter is SSR-safe out of the box: the custom elements are registered client-side only (inside `useEffect`), so server rendering emits the tag without touching browser APIs. No `dynamic(() => ..., { ssr: false })` workaround is needed.
Pass the boolean `readOnly` prop to disable interactive editing (requires
`@forcecalendar/interface >= 1.8.0`). `false` or omission keeps editing enabled.
The adapter maps this to the `readonly` boolean attribute consistently during
server rendering, lazy element registration, and later prop changes.
Programmatic event methods remain available in read-only mode.
Loading
Loading