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
18 changes: 10 additions & 8 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ about: Create a report to help us improve
title: ''
labels: ''
assignees: ''

---

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:

1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
Expand All @@ -24,15 +24,17 @@ A clear and concise description of what you expected to happen.
If applicable, add screenshots to help explain your problem.

**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]

- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]

**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]

- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]

**Additional context**
Add any other context about the problem here.
1 change: 0 additions & 1 deletion .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''

---

**Is your feature request related to a problem? Please describe.**
Expand Down
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
# @formio/bootstrap

## 4.0.2

### Patch Changes

- 799bfe5: preserve apostrophes in html5 option values
- c40ad3e: FIO-7954: refactored and fixed translations
- 6063d69: Prevent announcing clickable input labels when in read-only mode

## 4.0.2-api99.0

### Patch Changes

- 799bfe5: preserve apostrophes in html5 option values
- c40ad3e: FIO-7954: refactored and fixed translations
- 6063d69: Prevent announcing clickable input labels when in read-only mode

## 4.0.1

### Patch Changes
Expand Down
62 changes: 62 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# @formio/bootstrap

## Identity

- **Path:** `packages/bootstrap`. **Published as:** `@formio/bootstrap` on npm (MIT).
- **OSS sync:** YES — `ossRepo: github.com/formio/bootstrap`, `srcPath: "."`. Everything in `src/` ships publicly on release.
- **Module system:** TypeScript, dual CJS + ESM build (`tsconfig.cjs.json` / `tsconfig.mjs.json`), UMD bundles in `dist/`. **Lint:** TSLint (deprecated), not ESLint.
- **Era:** mature, near-static template pack. `.ts` files are thin barrels; the substance is ~220 precompiled `.ejs` markup files.
- **Purpose:** the **template pack** (markup only) that `@formio/js` uses to render forms under Bootstrap 3 / 4 / 5. It supplies HTML; the renderer owns all behavior.

## Floor — immutable musts

- **A `.ejs` change is almost never done in one tree.** `src/templates/{bootstrap3,bootstrap4,bootstrap5}/` are three hand-maintained copies. Mirror the edit across all three — swapping `sr-only` (BS3/BS4) ↔ `visually-hidden` (BS5), and noting BS3 lacks some anchors and is partially frozen. See [`bootstrap/subframework-parity-01`](../../docs/gotchas/bootstrap.md#subframework-parity-01--bootstrap345-are-three-hand-maintained-copies-the-intra-package-axis).
- **Don't delete a hidden announcement span or rename a `ref="..."`.** The renderer writes screen-reader announcements into those spans by `ref` name, with optional chaining — so a deletion or rename silently no-ops. The `ref`-name contract itself is seam invariant 1 ([template-markup-contract](../../docs/cross-cutting/template-markup-contract.md)); the bootstrap anchors are [`bootstrap/announce-anchors-01`](../../docs/gotchas/bootstrap.md#announce-anchors-01--the-hidden-announcement-spans-are-renderer-driven-dont-delete-or-rename-them) (marker `G-BS01`).
- **Use lodash-template delimiters, never stock EJS.** `{{ }}` interpolate, `{% %}` evaluate, `{{{ }}}` raw HTML, context var `ctx`. `<% %>` produces literal text. See [`bootstrap/ejs-precompile-delimiters-01`](../../docs/gotchas/bootstrap.md#ejs-precompile-delimiters-01--templates-use-lodash-template-delimiters-not-stock-ejs).
- **A new template is invisible until imported in `templates/<fw>/index.ts`** — registration is by explicit import, not filesystem scan. And the renderer may look it up by a string-constructed name (e.g. `wizardHeader<Setting>`). See [`bootstrap/template-name-and-registration-01`](../../docs/gotchas/bootstrap.md#template-name-and-registration-01--the-directory-name-is-the-renderers-lookup-key-and-the-barrel-must-import-it).
- **No secrets / internal-only / license-gated markup in `src/`** — it ships publicly. `GOTCHA(G-NNN)` markers in templates stay numeric (no topic phrases); in `.ejs` use `{% /* GOTCHA(G-NNN) */ %}`.

## Ceiling — emerging patterns

- **Pattern: a component is a directory of `form.ejs` (+ optional `html.ejs`) plus a 3-line `index.ts` barrel that default-exports `{ form, html }`. Example:** [`src/templates/bootstrap5/input/index.ts`](./src/templates/bootstrap5/input/index.ts).
- **Pattern: a screen-reader announcement anchor is a hidden span with a `ref` + `aria-live`, written into by the renderer. Example:** [`src/templates/bootstrap5/input/form.ejs:68`](./src/templates/bootstrap5/input/form.ejs) (`ref="announceMessage"`, carries marker `G-BS01`).

## Blast radius

**5 dependents, tier: medium** (headline consumer `@formio/js`, which also deep-imports `@formio/bootstrap/components`). See [`/docs/dependencies/bootstrap.md`](../../docs/dependencies/bootstrap.md).

## Test & build

```sh
pnpm -F @formio/bootstrap build # rm -rf lib; tsc cjs+mjs; gulp templates (precompile .ejs); webpack; libpackage.js
pnpm -F @formio/bootstrap lint # tslint -p .
pnpm -F @formio/bootstrap check-types # tsc --noEmit
```

**There is no test suite here** — `src/test.spec.ts` is a placeholder and there is no `test` script. "Green" locally = `build` + `lint` + `check-types`. Behavioral verification of a markup change happens **downstream in `@formio/js`** (renderer integration tests):

```sh
pnpm -F @formio/js test # exercises this package's markup
```

See [`bootstrap/no-test-suite-01`](../../docs/gotchas/bootstrap.md#no-test-suite-01--there-is-no-test-suite-verify-in-formiojs).

## Hot paths & gotchas

See [`/docs/gotchas/bootstrap.md`](../../docs/gotchas/bootstrap.md). Entries: `bootstrap/subframework-parity-01`, `bootstrap/announce-anchors-01` (G-BS01), `bootstrap/template-name-and-registration-01`, `bootstrap/wizard-header-variants-01` (G-BS02), `bootstrap/ejs-precompile-delimiters-01`, `bootstrap/no-test-suite-01`.

## Cross-cutting triggers

- **Editing any `*.ejs` (markup, `ref` names, element ids, `aria-*` wiring, shared id formats like the fieldset legend `l-<id>-legend`)** → read [`/docs/cross-cutting/template-markup-contract.md`](../../docs/cross-cutting/template-markup-contract.md) first. The same markup is reimplemented in `@formio/uswds` and `@formio/standard-template`, and read back by `@formio/js`; a one-sided change is a silent a11y/behavior regression (FIO-11126, FIO-10942).
- **Editing a `wizardHeader*` / `wizardNav` template** → check what `wizard/form.ejs` already renders around it before adding a landmark, `id`, or progressbar. See [`bootstrap/wizard-header-variants-01`](../../docs/gotchas/bootstrap.md#wizard-header-variants-01--header-plus-nav-both-render-duplicate-landmarks-are-the-recurring-bug) (FIO-11049, FIO-11511).
- **Adding/renaming an `exports` sub-path (`./components`, `./bootstrap5`, …)** → `@formio/js` deep-imports `@formio/bootstrap/components`; a rename breaks it at import resolution. See [dependencies](../../docs/dependencies/bootstrap.md#deep-coupling-inventory).
- **Any publishable change** → add a changeset (`@formio/bootstrap`, plus any sibling template pack you also touched — one entry per published package). **`major` bump** → coordinate the OSS release.

## References

- Repo-wide: [`/CLAUDE.md`](../../CLAUDE.md), [`/STANDARDS.md`](../../STANDARDS.md)
- Architecture: [`/docs/architecture/bootstrap.md`](../../docs/architecture/bootstrap.md)
- Dependencies: [`/docs/dependencies/bootstrap.md`](../../docs/dependencies/bootstrap.md)
- Gotchas: [`/docs/gotchas/bootstrap.md`](../../docs/gotchas/bootstrap.md)
- Seam: [`/docs/cross-cutting/template-markup-contract.md`](../../docs/cross-cutting/template-markup-contract.md)
- Downstream consumer: [`packages/formio.js/CLAUDE.md`](../formio.js/CLAUDE.md)
10 changes: 6 additions & 4 deletions Readme.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# Form.io Bootstrap Templates

This repository will change the rendering of forms in formio.js so that it uses html and classes compatible with [Bootstrap 3](https://getbootstrap.com/docs/3.4/), [Bootstrap 4](https://getbootstrap.com/docs/4.6/getting-started/introduction/), and [Bootstrap 5](https://getbootstrap.com/docs/5.3/getting-started/introduction/) frameworks.

Official Documentation
--------------------------
## Official Documentation

For the latest documentation, release information, and guides, always refer to the official Form.io Help Documentation available here:

**[https://help.form.io](https://help.form.io/dev/css-frameworks)**
Expand All @@ -14,6 +15,7 @@ import bootstrap3 from '@formio/bootstrap/bootstrap3';
import { Formio } from 'formiojs';
Formio.use(bootstrap3);
```

### Using Bootstrap 4

```javascript
Expand All @@ -32,10 +34,10 @@ Formio.use(bootstrap5);

## Script


```javascript
Formio.Templates.framework = "bootstrap3"
Formio.Templates.framework = 'bootstrap3';
```

If icon is not show

```javascript
Expand Down
14 changes: 10 additions & 4 deletions libpackage.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
const fs = require('fs');
const path = require('path');
fs.writeFileSync(path.join(__dirname, 'lib', 'cjs', 'package.json'), `{
fs.writeFileSync(
path.join(__dirname, 'lib', 'cjs', 'package.json'),
`{
"type": "commonjs"
}`);
fs.writeFileSync(path.join(__dirname, 'lib', 'mjs', 'package.json'), `{
}`,
);
fs.writeFileSync(
path.join(__dirname, 'lib', 'mjs', 'package.json'),
`{
"type": "module"
}`);
}`,
);
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@formio/bootstrap",
"version": "4.0.1",
"version": "4.0.2",
"description": "Bootstrap templates for Form.io forms",
"main": "lib/cjs/bootstrap5.js",
"module": "lib/mjs/bootstrap5.js",
Expand Down Expand Up @@ -42,7 +42,9 @@
"scripts": {
"watch": "tsc -w",
"build": "rm -rf ./lib && tsc --project tsconfig.cjs.json && tsc --project tsconfig.mjs.json && gulp templates && webpack && node ./libpackage.js",
"lint": "tslint -p ."
"lint": "tslint -p .",
"check-types": "tsc --noEmit",
"lint:fix": "tslint -p . --fix"
},
"pre-commit": [
"lint",
Expand Down
5 changes: 4 additions & 1 deletion src/bootstrap3.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import bootstrap3 from './templates/bootstrap3';
import translations from './translations';

export default {
framework: 'bootstrap3',
templates: {
bootstrap3
bootstrap3,
},
translations
};
7 changes: 5 additions & 2 deletions src/bootstrap4.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import bootstrap4 from './templates/bootstrap4';
import translations from './translations';

export default {
framework: 'bootstrap4',
templates: {
bootstrap4
bootstrap4,
},
};
translations
};
7 changes: 5 additions & 2 deletions src/bootstrap5.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import bootstrap5 from './templates/bootstrap5';
import translations from './translations';

export default {
framework: 'bootstrap5',
templates: {
bootstrap5
bootstrap5,
},
};
translations
};
2 changes: 1 addition & 1 deletion src/ejs.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
declare module '*.ejs' {
const value: string;
export default value
export default value;
}
18 changes: 9 additions & 9 deletions src/templates/bootstrap3/builderEditForm/form.ejs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
<div class="row">
<div class="col col-sm-6">
<p class="lead">{{ctx.t(ctx.componentInfo.title, { _userInput: true })}} {{ctx.t('Component')}}</p>
<p class="lead">{{ctx.t(ctx.componentInfo.title, { _userInput: true })}} {{ctx.t('component')}}</p>
</div>
{% if (ctx.helplinks) { %}
<div class="col col-sm-6">
<div class="pull-right" style="margin-right: 20px; margin-top: 10px">
<a href="{{ctx.t(ctx.helplinks + ctx.componentInfo.documentation)}}" target="_blank">
<i class="{{ctx.iconClass('new-window')}}"> {{ctx.t('Help')}}</i>
<i class="{{ctx.iconClass('new-window')}}"> {{ctx.t('help')}}</i>
</a>
</div>
</div>
Expand All @@ -19,17 +19,17 @@
</div>
{% if (!ctx.preview) { %}
<div style="margin-top: 10px;">
<button class="btn btn-success" style="margin-right: 10px;" ref="saveButton">{{ctx.t('Save')}}</button>
<button class="btn btn-default" style="margin-right: 10px;" ref="cancelButton">{{ctx.t('Cancel')}}</button>
<button class="btn btn-danger" ref="removeButton">{{ctx.t('Remove')}}</button>
<button class="btn btn-success" style="margin-right: 10px;" ref="saveButton">{{ctx.t('save')}}</button>
<button class="btn btn-default" style="margin-right: 10px;" ref="cancelButton">{{ctx.t('cancel')}}</button>
<button class="btn btn-danger" ref="removeButton">{{ctx.t('remove')}}</button>
</div>
{% } %}
</div>
{% if (ctx.preview) { %}
<div class="col col-sm-6">
<div class="panel panel-default preview-panel">
<div class="panel-heading">
<h3 class="panel-title">{{ctx.t('Preview')}}</h3>
<h3 class="panel-title">{{ctx.t('preview')}}</h3>
</div>
<div class="panel-body">
<div class="component-preview" ref="preview">
Expand All @@ -43,9 +43,9 @@
</div>
{% } %}
<div style="margin-top: 10px;">
<button class="btn btn-success" style="margin-right: 10px;" ref="saveButton">{{ctx.t('Save')}}</button>
<button class="btn btn-default" style="margin-right: 10px;" ref="cancelButton">{{ctx.t('Cancel')}}</button>
<button class="btn btn-danger" ref="removeButton">{{ctx.t('Remove')}}</button>
<button class="btn btn-success" style="margin-right: 10px;" ref="saveButton">{{ctx.t('save')}}</button>
<button class="btn btn-default" style="margin-right: 10px;" ref="cancelButton">{{ctx.t('cancel')}}</button>
<button class="btn btn-danger" ref="removeButton">{{ctx.t('remove')}}</button>
</div>
</div>
{% } %}
Expand Down
2 changes: 1 addition & 1 deletion src/templates/bootstrap3/builderPlaceholder/form.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
data-noattach="true"
data-position="{{ctx.position}}"
>
{{ctx.t('Drag and Drop a form component')}}
{{ctx.t('dragAndDropComponent')}}
</div>
2 changes: 1 addition & 1 deletion src/templates/bootstrap3/builderSidebar/form.ejs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<div class="builder-sidebar{{ctx.scrollEnabled ? ' builder-sidebar_scroll' : ''}}" ref="sidebar">
<input class="form-control builder-sidebar_search" type="search" ref="sidebar-search" placeholder="{{ctx.t('Search field(s)')}}" />
<input class="form-control builder-sidebar_search" type="search" ref="sidebar-search" placeholder="{{ctx.t('searchFields')}}" />
<div id="{{ctx.groupId}}" class="panel-group" ref="sidebar-groups">
{% ctx.groups.forEach(function(group) { %}
{{ group }}
Expand Down
2 changes: 1 addition & 1 deletion src/templates/bootstrap3/builderSidebarGroup/form.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
{% }) %}
{{ctx.subgroups.join('')}}
{% } else { %}
<div>{{ctx.t('No Matches Found')}}</div>
<div>{{ctx.t('noMatchesFound')}}</div>
{% } %}
</div>
</div>
Expand Down
18 changes: 9 additions & 9 deletions src/templates/bootstrap3/column-menu/html.ejs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
<div ref="menu">
<ul class="dropdown-menu checkbox-menu allow-focus" ref="dropdown">
{% if (ctx.options.sortable && ctx.component && ctx.component.sortable) { %}
<li class="dropdown-item" ref="sortAscending">↑ Sort Ascending</li>
<li class="dropdown-item" ref="sortDescending">↓ Sort Descending</li>
<li class="dropdown-item" ref="sortAscending">↑ {{ctx.t('sortAscending')}}</li>
<li class="dropdown-item" ref="sortDescending">↓ {{ctx.t('sortDescending')}}</li>
<li ref="rootItem" role="separator" class="divider"></li>
{% } %}
<li ref="rootItem" class="dropdown-item">
Columns <span class="arrow-right">›</span>
{{ctx.t('columns')}} <span class="arrow-right">›</span>
<ul ref="nestedDropdown" class="dropdown-menu checkbox-menu allow-focus">
{% ctx.components.forEach(function(comp) { %}
<li class="dropdown-item">
Expand All @@ -18,7 +18,7 @@
type="checkbox"
class="form-check-input">
<span class="form-check-label">
{{ comp.label }}
{{ ctx.t(comp.label, { _userInput: true }) }}
</span>
</label>
</li>
Expand All @@ -28,15 +28,15 @@
{% if (ctx.options.filterable && ctx.component && ctx.component.filterable) { %}
<li ref="rootItem" role="separator" class="divider"></li>
<li ref="rootItem" class="dropdown-item">
Filter <span class="arrow-right">›</span>
{{ctx.t('filter')}} <span class="arrow-right">›</span>
<ul ref="nestedDropdown" class="dropdown-menu">
<form ref="filterForm" class="px-3 py-2">
Show items with value that:
{{ctx.t('showItemsWithValue')}}
<div class="form-group">
<select ref="filterType" class="form-control">
{% ctx.filterTypes.forEach(function(filterType) { %}
{% if (!(ctx.hideFilters.includes(ctx.component.key) && (['startsWith', 'contain', 'notContain', 'endsWith', 'matches' ].includes(filterType.type)))) { %}
<option value="{{ filterType.type }}">{{ filterType.label }}</option>
<option value="{{ filterType.type }}">{{ ctx.t(filterType.label) }}</option>
{% } %}
{% }); %}
</select>
Expand All @@ -45,8 +45,8 @@
<input type="text" ref="filterInput" name="{{ ctx.component && ctx.component.key }}" class="form-control" placeholder="Filter">
</div>
<div class="form-row btn-toolbar">
<button ref="filterButton" type="button" class="btn btn-primary">Filter</button>
<button ref="clearBtn" type="button" class="btn btn-secondary">Clear</button>
<button ref="filterButton" type="button" class="btn btn-primary">{{ctx.t('filter')}}</button>
<button ref="clearBtn" type="button" class="btn btn-secondary">{{ctx.t('clear')}}</button>
</div>
</form>
</ul>
Expand Down
4 changes: 2 additions & 2 deletions src/templates/bootstrap3/column-menu/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
import html from './html.ejs';
export default { html };
import html from './html.ejs';
export default { html };
Loading
Loading