@harmolody/ui 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # @harmolody/ui
2
+
3
+ Tailwind-first Angular 22.2 dynamic forms and data tables, extracted from Harmolody Admin. Built with standalone components, signal inputs/outputs, Reactive Forms, and OnPush change detection.
4
+
5
+ Install the package into a compatible Angular 22.2+ app. Source and full examples: [codibyte-co/harmolody-ui](https://github.com/codibyte-co/harmolody-ui).
6
+
7
+ ```sh
8
+ npm install @harmolody/ui
9
+ npm install tailwindcss @tailwindcss/postcss postcss
10
+ ```
11
+
12
+ Enable the PostCSS plugin in `.postcssrc.json`:
13
+
14
+ ```json
15
+ { "plugins": { "@tailwindcss/postcss": {} } }
16
+ ```
17
+
18
+ In global `src/styles.css`:
19
+
20
+ ```css
21
+ @import 'tailwindcss';
22
+ @import '@harmolody/ui/theme.css';
23
+ @source '../node_modules/@harmolody/ui';
24
+ ```
25
+
26
+ Adjust `@source` relative to your CSS file. It is required to generate utilities used by the installed library.
27
+
28
+ ```ts
29
+ import {
30
+ DynamicFormComponent,
31
+ TableComponent,
32
+ type FormConfig,
33
+ type TableConfig,
34
+ } from '@harmolody/ui';
35
+ ```
36
+
37
+ ```html
38
+ <hui-dynamic-form #form [config]="formConfig" (formSubmit)="save($event); form.finishSubmit()" />
39
+ <hui-table [config]="tableConfig" />
40
+ ```
41
+
42
+ Import both components in the host's `imports`. For async submissions, call `finishSubmit()` after completion, including failure paths. Static forms require no providers. API-backed fields need `provideHttpClient()`; internal table links need `provideRouter(routes)`. The table emits sorting/pagination events; the host supplies sorted/page data. Use `TableCellDirective` and `huiTableCell` for custom cells, and `provideHarmolodyUi()` for optional translations, icon overrides, and media selection.
43
+
44
+ Theme variables use `--hui-*` (for example `--hui-primary`, `--hui-surface`, `--hui-border`). The workspace includes complete guides, a working showcase, and unit/browser/packed-consumer verification. Detailed guides ship under `docs/` in the package.
45
+
46
+ License: `UNLICENSED`; no open-source license is granted.
@@ -0,0 +1,56 @@
1
+ # Adopt in Harmolody Admin
2
+
3
+ The extraction leaves `harmolody_admin` unchanged. Migrate one feature first, then replace imports gradually. The current admin starts at Angular 22.1.x; this library targets 22.2.1, so align its Angular/CLI/compiler versions before installing.
4
+
5
+ 1. Build and pack this workspace: `npm run pack`.
6
+ 2. In the admin, install `../harmolody_ui/artifacts/harmolody-ui-0.1.0.tgz`.
7
+ 3. Add `@source '../node_modules/@harmolody/ui';` in the admin's global CSS. If retaining the existing semantic Tailwind theme, map existing colors to `--hui-*` variables or integrate the library's theme carefully to avoid duplicate semantic definitions.
8
+ 4. Replace shared component imports with package imports. Keep config names and Reactive Forms behavior. Rename selectors and the cell directive.
9
+ 5. Supply localization/media adapters using host services in a provider factory; configure explicit table link generators.
10
+ 6. Run the admin's existing tests and production build, inspect real dialogs and data pages, then remove old shared files only after all consumers migrate.
11
+
12
+ | Old | Package equivalent |
13
+ | ----------------------------------------- | ----------------------------------------------- |
14
+ | `@shared/components/form-builder` | `@harmolody/ui` |
15
+ | `@shared/components/table` | `@harmolody/ui` |
16
+ | `@shared/components/forms` | Supporting control exports from `@harmolody/ui` |
17
+ | `<app-dynamic-form>` | `<hui-dynamic-form>` |
18
+ | `<app-table>` | `<hui-table>` |
19
+ | `appTableCell` | `huiTableCell` |
20
+ | `<app-table-column-selector>` | `<hui-table-column-selector>` |
21
+ | Implicit artist/user/etc. route discovery | Column `linkGenerator` callback |
22
+ | Built-in admin media dialog | Host `selectMedia` adapter |
23
+
24
+ For Angular DI integrations:
25
+
26
+ ```ts
27
+ import { inject } from '@angular/core';
28
+ import { HARMOLODY_UI_CONFIG, HARMOLODY_MEDIA_SELECTOR } from '@harmolody/ui';
29
+
30
+ providers: [
31
+ {
32
+ provide: HARMOLODY_UI_CONFIG,
33
+ useFactory: () => {
34
+ const localization = inject(LocalizationService); // Your admin service
35
+ return {
36
+ translate: (text: string, params?: Record<string, string | number>) =>
37
+ localization.translate(text, params),
38
+ };
39
+ },
40
+ },
41
+ {
42
+ provide: HARMOLODY_MEDIA_SELECTOR,
43
+ useFactory: () => {
44
+ const dialog = inject(DialogService); // Your admin service
45
+ return async (request: MediaSelectorRequest) => {
46
+ const result = await dialog.open(MediaSelectorDialogComponent, dialogOptions, request);
47
+ return result.action === 'confirm' ? result.data.fileUrl : undefined;
48
+ };
49
+ },
50
+ },
51
+ ];
52
+ ```
53
+
54
+ The example assumes your existing dialog contract and imports; adapt options to the admin implementation. Do not additionally call `provideHarmolodyUi()` after these token providers because it would replace them.
55
+
56
+ Behavior changes to review: validation is restored when conditional required fields reappear; submission remains locked until `finishSubmit()`; configured disabled fields stay disabled afterwards; column selector bulk actions preserve pinned columns. These fixes live only in the extracted package. Initial record replacement and general schema removal still require a new form instance.
@@ -0,0 +1,116 @@
1
+ # Dynamic form
2
+
3
+ ```ts
4
+ import { DynamicFormComponent, type FormConfig, type FieldConfig } from '@harmolody/ui';
5
+ ```
6
+
7
+ Import `DynamicFormComponent` in a standalone component and render `<hui-dynamic-form>`. `config` is required; `initialValues` is an optional object applied when the component is created. Treat a form instance as one schema and one edit session: recreate it for a different record or substantially different schema. Adding fields to `config` is supported without losing existing values; deleting controls or replacing validators through a new schema is not a general schema reconciliation API.
8
+
9
+ ## Fields
10
+
11
+ Supported types: `text`, `email`, `password`, `number`, `tel`, `url`, `textarea`, `select`, `radio`, `checkbox`, `switch`, `autocomplete`, `search`, `date`, `datetime`, `month`, `year`, `file`, `image-selector`, `array`.
12
+
13
+ | Property | Meaning |
14
+ | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ |
15
+ | `key`, `label`, `type` | Stable flat control key, accessible label, control type |
16
+ | `defaultValue`, `required`, `disabled`, `hidden` | Initial state; disabled values still appear in submitted raw values |
17
+ | `placeholder`, `hint`, `helper`, `description`, `note` | Supporting text; notes support `noteVariant` |
18
+ | `validation` | `required`, `email`, `minLength`, `maxLength`, `min`, `max`, `pattern`, `custom` rules |
19
+ | `options` | `{label, value, disabled?}` items for choices |
20
+ | `multiple`, `searchable`, `optionPresentation`, `chipLayout` | Multiple choices, dropdown search, dropdown/chips, inline/wrap chips |
21
+ | `optionsFromApi` | GET/POST option source; needs host `provideHttpClient()` |
22
+ | `dependsOn` | `{field, clearOnChange?, refreshOn?}` or an array of dependencies |
23
+ | `showWhen`, `hideWhen` | `{field, operator, value?, values?}` or an array; `showWhen` conditions combine with AND, `hideWhen` with OR |
24
+ | `fullWidth`, `columnSpan`, `containerClass`, `cssClass` | Grid positioning and custom classes; grid supports 1–3 columns |
25
+ | `min`, `max`, `step`, `rows`, `minDate`, `maxDate` | Number, textarea, date constraints |
26
+ | `accept`, `multiple`, `maxSize`, `maxFiles` | File selection constraints; values are local file metadata, uploading belongs to the host |
27
+ | `arrayFields`, `minItems`, `maxItems`, `itemLabel` | Repeating groups and bounds; `itemLabel` accepts `{index}` |
28
+
29
+ Email and numeric fields get corresponding validators automatically. Register custom validators before rendering via `inject(FormBuilderService).registerCustomValidator('uniqueName', validatorFn)` and reference the name in `{type: 'custom', validator: 'uniqueName'}`. Rule `message` is retained in the model but the current field error display uses the built-in error formatter; custom rule messages are not a guaranteed override.
30
+
31
+ ```ts
32
+ const config: FormConfig = {
33
+ layout: 'grid',
34
+ columns: 2,
35
+ fieldLayout: 'column',
36
+ validationStrategy: 'submit',
37
+ fieldsets: [{ key: 'profile', legend: 'Profile', fields: ['name', 'genre'] }],
38
+ fields: [
39
+ { key: 'name', label: 'Name', type: 'text', required: true },
40
+ { key: 'genre', label: 'Genre', type: 'select', options: [{ label: 'Folk', value: 'folk' }] },
41
+ { key: 'active', label: 'Active', type: 'switch', defaultValue: false },
42
+ {
43
+ key: 'website',
44
+ label: 'Website',
45
+ type: 'url',
46
+ required: true,
47
+ showWhen: { field: 'active', operator: 'eq', value: true },
48
+ },
49
+ {
50
+ key: 'credits',
51
+ label: 'Credits',
52
+ type: 'array',
53
+ maxItems: 5,
54
+ arrayFields: [{ key: 'name', label: 'Credit name', type: 'text', required: true }],
55
+ },
56
+ ],
57
+ submitButton: { label: 'Save' },
58
+ showCancelButton: { hidden: true },
59
+ };
60
+ ```
61
+
62
+ `fieldLayout: 'row'` places labels beside controls on wider screens. Fieldsets group presentation while values remain flat. Fields outside fieldsets appear afterwards. Hidden fields keep their values and have validation suspended; visible fields regain validators.
63
+
64
+ ## Inputs, outputs, methods
65
+
66
+ | API | Payload / behavior |
67
+ | ------------------------------------------- | ------------------------------------------------------------------------------------------- |
68
+ | `config`, `initialValues` | Form schema and initial record |
69
+ | `formSubmit` | Valid raw values; disables form and holds submit state until `finishSubmit()` |
70
+ | `valueChange` | Current enabled control values |
71
+ | `fieldChange` | `{values, selectedObjects}` when a choice field has `emitSelectedObject: true` |
72
+ | `reset`, `cancel` | Void events |
73
+ | `actionClick` | `{action, formValue, isValid}` for a custom button action |
74
+ | `fieldActionClick` | `{fieldKey, formValue}` for a choice field action |
75
+ | `finishSubmit()` | Releases submission lock, restores configured disabled controls |
76
+ | `validate()` | Shows errors and returns validity |
77
+ | `getRawValue<T>()` | Reads all values, including disabled controls |
78
+ | `setFieldValue(key, value)` | Programmatic field update |
79
+ | `getField(key)` | Gets a rendered dynamic field |
80
+ | `reloadFieldOptions(key)` | Reloads a field's API options; set `cache: false` for fresh requests |
81
+ | `reloadFieldOptionsAndSetValue(key, value)` | Legacy convenience method; uses a 500ms delay and should not be relied on for slow requests |
82
+
83
+ ```ts
84
+ async save(values: Record<string, unknown>): Promise<void> {
85
+ try {
86
+ await this.repository.save(values);
87
+ } finally {
88
+ this.form().finishSubmit();
89
+ }
90
+ }
91
+ ```
92
+
93
+ Built-in Cancel, Reset, Submit buttons appear unless individually hidden. The default Submit button is disabled while invalid. Use a custom submit action without `disableOnInvalid` to allow users to attempt submission and display validation errors. `actions` replaces them with buttons ordered by `order`; `type: 'submit'` and `'reset'` use the standard handlers. Other buttons emit `actionClick`. Variants: `primary`, `secondary`, `outline`, `danger`, `success`, `link`. The reset method clears current values; it does not restore the original record.
94
+
95
+ ## API-backed options
96
+
97
+ ```ts
98
+ // Host application providers
99
+ provideHttpClient()
100
+
101
+ // FormConfig
102
+ apiConfig: { baseUrl: 'https://api.example.com' },
103
+ fields: [{
104
+ key: 'artistId', label: 'Artist', type: 'autocomplete',
105
+ optionsFromApi: {
106
+ url: '/artists', method: 'GET', searchParam: 'query', cache: false,
107
+ map: { data: 'items', label: 'name', value: 'id' },
108
+ dynamicFilters: [{ field: 'genreId', operator: 'eq', valueFrom: 'genreId', whenNotEmpty: true }],
109
+ },
110
+ dependsOn: { field: 'genreId', clearOnChange: true },
111
+ }]
112
+ ```
113
+
114
+ Use host HTTP interceptors for authentication. Field `baseUrl` overrides the form URL. GET filters become query parameters; POST filters stay in a `filters` array. Request/response transform callbacks support other backend shapes. Cache keys include API configuration, headers, and mapping; `OptionsLoaderService.clearCache(url?)` clears all or matching URL entries. Failed requests currently produce empty options and log an error; the host owns notifications and retry policy. Static forms require no HTTP provider.
115
+
116
+ Nested arrays inside `arrayFields` are not supported as a recursive array builder. Most initial schema properties are construction-time settings. Properties such as named `onChange`/`onBlur` strings are legacy metadata; handle the emitted Angular events instead. Per-field `emitSelectedObject` controls `fieldChange`; do not rely on the legacy global `emitSelectedObjects` flag alone.
@@ -0,0 +1,56 @@
1
+ # Publishing @harmolody/ui
2
+
3
+ The public package name is `@harmolody/ui`; source is hosted at https://github.com/codibyte-co/harmolody-ui. npm and GitHub permissions are managed separately. The publishing npm account must have write access to the `@harmolody` scope.
4
+
5
+ The package keeps `UNLICENSED`, reserving rights rather than granting an open-source license. Repository links, declarations, partial Angular compilation, Tailwind theme exports, usage documentation, and an explicit package file list are included.
6
+
7
+ ## Authenticate
8
+
9
+ ```sh
10
+ npm login --registry https://registry.npmjs.org
11
+ npm whoami --registry https://registry.npmjs.org
12
+ ```
13
+
14
+ Complete browser login or two-factor verification directly with npm when prompted. Do not commit npm tokens or credentials.
15
+
16
+ ## Validate and preview a release
17
+
18
+ ```sh
19
+ npm ci
20
+ npm run release:check
21
+ ```
22
+
23
+ This runs unit tests, production builds, tarball generation, an independent consumer installation/build, and an npm publish dry run. It uploads nothing. For browser checks also run `npm run test:e2e`; install Chromium with `npx playwright install chromium`, or use `PLAYWRIGHT_CHANNEL=chrome` with installed Google Chrome.
24
+
25
+ ## Publish
26
+
27
+ ```sh
28
+ npm run publish:npm
29
+ ```
30
+
31
+ The command validates first, then publishes only `dist/ui` with public access to https://registry.npmjs.org. The root workspace is private. npm may require two-factor/browser approval; complete the CLI prompt. Publishing a version is permanent: the same name/version cannot be published again.
32
+
33
+ ## Next versions
34
+
35
+ ```sh
36
+ npm version patch --prefix projects/ui --no-git-tag-version
37
+ # Update CHANGELOG.md, then validate and commit the source
38
+ npm run release:check
39
+ git add .
40
+ git commit -m "Release next version"
41
+ git push origin main
42
+ npm run publish:npm
43
+ ```
44
+
45
+ Use minor/major increments intentionally for new features or breaking APIs. Choose a patch version higher than the registry version if a release already exists. The authoritative version is `projects/ui/package.json`; building copies it into `dist/ui`.
46
+
47
+ ## Verify the published package
48
+
49
+ ```sh
50
+ npm view @harmolody/ui version --registry https://registry.npmjs.org
51
+ npm install @harmolody/ui
52
+ ```
53
+
54
+ Consumers must add the Tailwind theme import and source registration described in the README. The metadata links the npm package to GitHub; it does not configure GitHub Actions or automatic publishing. A future CI workflow can use npm trusted publishing after configuring the repository/workflow relationship on npm.
55
+
56
+ References: [scoped public publishing](https://docs.npmjs.com/creating-and-publishing-scoped-public-packages/), [trusted publishing](https://docs.npmjs.com/trusted-publishers/).
@@ -0,0 +1,77 @@
1
+ # Styling and application adapters
2
+
3
+ ## Tailwind 4
4
+
5
+ Use these imports in the consumer's global `src/styles.css`:
6
+
7
+ ```css
8
+ @import 'tailwindcss';
9
+ @import '@harmolody/ui/theme.css';
10
+ @source '../node_modules/@harmolody/ui';
11
+ ```
12
+
13
+ Install `tailwindcss`, `@tailwindcss/postcss`, `postcss` and enable the PostCSS plugin as shown in the root README. The library is intentionally Tailwind-first. The package includes an uncompiled theme entry point and utility classes in its compiled Angular templates; your app generates the utilities. Importing only the theme will not generate all the classes unless the package source is registered.
14
+
15
+ ## Theme tokens
16
+
17
+ Override `--hui-*` CSS variables on `:root` or on a container surrounding the components:
18
+
19
+ ```css
20
+ :root {
21
+ --hui-primary: #2563eb;
22
+ --hui-primary-foreground: #ffffff;
23
+ --hui-background: #f8fafc;
24
+ --hui-surface: #ffffff;
25
+ --hui-border: #cbd5e1;
26
+ }
27
+ [data-theme='dark'] {
28
+ --hui-background: #0e0b16;
29
+ --hui-surface: #151225;
30
+ --hui-surface-hover: #1f1b2e;
31
+ --hui-foreground: #f9fafb;
32
+ --hui-text-secondary: #cbd5e1;
33
+ --hui-text-muted: #94a3b8;
34
+ --hui-muted: #1f1b2e;
35
+ --hui-border: #2a2540;
36
+ color-scheme: dark;
37
+ }
38
+ ```
39
+
40
+ Available tokens: `background`, `background-soft`, `surface`, `surface-hover`, `foreground`, `primary`, `primary-foreground`, `primary-soft`, `muted`, `text-secondary`, `text-muted`, `text-disabled`, `border`, `destructive`, all prefixed `--hui-`. Semantic utilities such as `bg-surface`, `text-primary`, `border-border` resolve through these tokens. Theme imports define global semantic utility names; review overlaps with an existing app theme. Some extracted controls retain explicit violet utility classes from the admin; token overrides do not recolor every legacy control. Extend with host classes where necessary. The package does not switch dark mode automatically.
41
+
42
+ ## Optional providers
43
+
44
+ Basic static forms and tables work without `provideHarmolodyUi()`. English labels and built-in icons are defaults. Configure application integrations in `app.config.ts`:
45
+
46
+ ```ts
47
+ import { provideHarmolodyUi } from '@harmolody/ui';
48
+ import { provideHttpClient } from '@angular/common/http';
49
+ import { provideRouter } from '@angular/router';
50
+
51
+ providers: [
52
+ provideHttpClient(), // Required only for API-backed options
53
+ provideRouter(routes), // Required for internal table links
54
+ provideHarmolodyUi({
55
+ translate: (text, parameters) => myTranslator.translate(text, parameters),
56
+ icons: {
57
+ 'custom-star': {
58
+ path: 'M12 2l3 7 7 1-5 5 1 7-6-4-6 4 1-7-5-5 7-1z',
59
+ fill: 'currentColor',
60
+ stroke: 'none',
61
+ },
62
+ },
63
+ selectMedia: async ({ mediaType, allowUpload }) => {
64
+ const result = await myMediaDialog.open({ mediaType, allowUpload });
65
+ return result.confirmed ? result.fileUrl : undefined;
66
+ },
67
+ }),
68
+ ];
69
+ ```
70
+
71
+ These are example host services, not imports provided by the package. Create the adapter inside a provider factory if your application integration needs Angular `inject()`; do not call `inject()` later inside an async callback outside an injection context.
72
+
73
+ Localization receives the original label and optional interpolation parameters. Without an adapter, `{count}` style placeholders are interpolated. Translation callbacks should read the host's language signal if labels need to react to language changes.
74
+
75
+ Icon overrides use the built-in `SvgIcon` structure (`path`, `viewBox?`, `stroke?`, `fill?`, `strokeWidth?`). Icons without an explicit `ariaLabel` are decorative and hidden from assistive technology; supply `ariaLabel` for meaningful standalone icons. Definitions override same-name built-ins; missing names yield no SVG. Avoid accepting untrusted SVG definitions as configuration.
76
+
77
+ Media selection returns a URL string for selection, `null` for clearing, and `undefined` for cancellation. The Browse button is disabled without a media adapter; `editableInput: true` supports direct URL entry. Storage, uploads, authentication, and dialogs belong to the host project.
package/docs/table.md ADDED
@@ -0,0 +1,93 @@
1
+ # Table
2
+
3
+ ```ts
4
+ import {
5
+ TableComponent,
6
+ TableCellDirective,
7
+ TableColumnSelectorComponent,
8
+ type TableConfig,
9
+ type TableColumn,
10
+ type TableSort,
11
+ } from '@harmolody/ui';
12
+ ```
13
+
14
+ `<hui-table [config]="config()" />` renders the supplied rows. **The table emits sorting and pagination requests; the host sorts, fetches, and slices data.** It does not silently paginate or sort the provided array. The showcase demonstrates client-side handling; server-driven projects can pass a page returned by their API.
15
+
16
+ ```ts
17
+ interface Artist {
18
+ id: string;
19
+ name: string;
20
+ active: boolean;
21
+ }
22
+ const config: TableConfig<Artist> = {
23
+ rowKey: 'id',
24
+ columns: [
25
+ { key: 'name', label: 'Artist', sortable: true },
26
+ { key: 'active', label: 'Status', type: 'template' },
27
+ ],
28
+ data: [{ id: '1', name: 'May', active: true }],
29
+ pagination: { page: 1, pageSize: 10, total: 48, pageSizeOptions: [10, 20, 50] },
30
+ selection: { mode: 'multiple', selectedRows: [] },
31
+ actions: [{ label: 'Edit', icon: 'edit', onClick: (row) => console.log(row.id) }],
32
+ stickyHeader: true,
33
+ maxHeight: '480px',
34
+ hoverable: true,
35
+ };
36
+ ```
37
+
38
+ ## Custom cells
39
+
40
+ ```html
41
+ <hui-table [config]="config()">
42
+ <ng-template huiTableCell="active" let-value let-row="row" let-index="index" let-column="column">
43
+ <span>{{ row.active ? 'Active' : 'Draft' }}</span>
44
+ </ng-template>
45
+ </hui-table>
46
+ ```
47
+
48
+ Import `TableCellDirective` alongside the table. Template context: `$implicit` cell value, `row`, `index`, `column`. You can also supply `column.template` or render a standalone component with `type: 'component'`, `component`, and `componentInputs`.
49
+
50
+ ## Configuration
51
+
52
+ | Property | Behavior |
53
+ | --------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
54
+ | `columns`, `data` | Visible columns and currently displayed rows |
55
+ | `rowKey` | Stable property or `(row) => string \| number`; always provide it for selection/reorder |
56
+ | `sort` | Controlled `{column, direction: 'asc' \| 'desc' \| null}` |
57
+ | `pagination` | 1-based page, page size, total record count |
58
+ | `selection` | `none`, `single`, `multiple`; selected row objects from current page |
59
+ | `actions` | Label/icon/callback, `hidden(row)`, `disabled(row)`, variant, submenu, divider |
60
+ | `loading`, `emptyMessage` | Loading and empty states |
61
+ | `stickyHeader`, `maxHeight` | Sticky header, 600px default max height; disable sticky for natural page layout |
62
+ | `showHeader`, `showHeaderOnlyOnScroll` | Header visibility options |
63
+ | `bordered`, `striped`, `hoverable`, `compact`, `showRowNumbers` | Presentation |
64
+ | `rowReorder` | `{enabled, disabled?, onDisabledAttempt?, onReorder?}`; emits reordered current-page rows |
65
+ | `rowClass`, `onRowClick` | Custom row classes and click handler |
66
+
67
+ Column options: dotted `key` lookup (e.g. `profile.name`), `label`, `sortable`, `align`, `headerAlign`, `width`, `type` (`text`, `image`, `icon`, `link`, `component`, `template`), `render(value, row, index)`, `cellClass`, `hideable`, `hideOnScreens`. Image columns support fallback and classes; icon columns accept static or callback names/classes/labels. Render callbacks produce text, not raw HTML.
68
+
69
+ Links are explicit: use `linkGenerator(row)`, optional `linkTarget`, and `onClick(row)`. Internal links use Angular Router; configure `provideRouter(routes)` in the host when using them. The extracted library no longer guesses Harmolody entity routes from column names.
70
+
71
+ ## Events
72
+
73
+ | Output | Payload |
74
+ | ----------------- | ------------------------------------------------------------ |
75
+ | `sortChange` | `{column, direction}`; cycles ascending, descending, cleared |
76
+ | `pageChange` | Requested 1-based page |
77
+ | `pageSizeChange` | Requested size; host resets page to 1 |
78
+ | `selectionChange` | Selected displayed row objects |
79
+ | `rowReorder` | `{row, previousIndex, currentIndex, data}` |
80
+
81
+ Equivalent config callbacks are also supported (`onSort`, `onPageChange`, `onPageSizeChange`, selection `onSelectionChange`, reorder `onReorder`). Choose outputs or callbacks for a given action to avoid duplicate API requests. Update config and data immutably because components use OnPush and signals. Selection is page-local; manage cross-page selection in the host. Sorting headers support Enter/Space and expose `aria-sort`.
82
+
83
+ ## Column selector
84
+
85
+ ```html
86
+ <hui-table-column-selector
87
+ [allColumns]="columns"
88
+ [visibleColumnKeys]="visibleKeys()"
89
+ (visibleColumnsChange)="visibleKeys.set($event)"
90
+ />
91
+ ```
92
+
93
+ Filter the table's `columns` using `visibleKeys`. `hideable: false` pins a column. The component never persists preferences automatically; the host may save keys in its user settings.
@@ -0,0 +1,22 @@
1
+ # Validation
2
+
3
+ ```sh
4
+ npm ci
5
+ npm run build:lib
6
+ npm test
7
+ npm run build:example
8
+ npm run pack
9
+ npm run check:package
10
+ npx playwright install chromium
11
+ npm run test:e2e
12
+ ```
13
+
14
+ Library unit tests cover existing form controls, array initialization, config additions, options loading, choice presentations, actions, custom table cells, and row reordering. Additional extraction tests cover application adapters, conditional validation, submission locking, and disabled fields. The showcase unit test consumes `dist/ui` through `@harmolody/ui`; build the library first.
15
+
16
+ Browser tests exercise actual invalid and valid submissions, keyboard sorting, page changes, selection, row actions, themes, responsive layout, and computed Tailwind grid styles.
17
+
18
+ `scripts/check-package.mjs` creates a temporary consumer workspace outside this repository, installs the tarball using its own `node_modules`, and compiles an Angular app with Tailwind scanning the **installed npm package**. This catches exports, template compilation, peers, and theme/source packaging errors that workspace aliases can hide. The temporary consumer is retained and its path printed for inspection.
19
+
20
+ The package checker runs an installation and production build; it requires registry access and a supported Node version. It does not publish. Browser checks use Playwright Chromium; install it once with the command above. No backend credentials are needed.
21
+
22
+ On a machine with Google Chrome installed, you can skip the Playwright Chromium download and use `PLAYWRIGHT_CHANNEL=chrome npm run test:e2e`. The default uses Playwright's downloaded Chromium. In restrictive macOS sandboxes, Angular's native Tailwind compilation and headless browser launch may require running these checks in a normal terminal.