Upgrade to 1.0
View as MarkdownThe steps below cover the changes most projects run into. Every detail, with the betas listed one by one, is in the changelog.
The Composer and npm packages share one version. A 1.0 template with a 0.x client fails to hydrate.
Update the packages
Permalink to heading "Update the packages"1.0 needs TYPO3 14, PHP 8.3 and typo3fluid/fluid 5.2 or newer.
composer require jramke/fluid-primitives:"^1.0@beta"
npm install fluid-primitives@beta
Templates
Permalink to heading "Templates"Name refs in camelCase
Permalink to heading "Name refs in camelCase"ui:ref names are camelCase, like part names everywhere else. The rendered attribute is data-<component>-<part> with the root id as its value:
<!-- 0.x -->
<button {ui:ref(name: 'next-trigger')}>Next</button>
<!-- 1.0, renders data-carousel-next-trigger="«rootId»" -->
<button {ui:ref(name: 'nextTrigger')}>Next</button>
ui:ref renders no data-scope and data-part. Update CSS that selects your own parts by those attributes: [data-scope=carousel][data-part=container] becomes [data-carousel-container].
A per-item value goes into its own value argument, which also renders data-value: {ui:ref(name: 'item', value: item.value, data: refData)}. See ui:ref.
Forward props with as
Permalink to heading "Forward props with as"ui:useProps binds the imported props to a variable, and spreadProps takes that variable:
<!-- 0.x -->
<ui:useProps name="primitives:select.root" />
<primitives:select.root spreadProps="{true}">...</primitives:select.root>
<!-- 1.0 -->
<ui:useProps name="primitives:select.root" as="rootProps" />
<primitives:select.root spreadProps="{rootProps}">...</primitives:select.root>
See ui:useProps.
Mount a component yourself with autoMount
Permalink to heading "Mount a component yourself with autoMount"controlled="{true}" becomes autoMount="{false}":
<ui:dialog.root autoMount="{false}" rootId="cookie-settings">...</ui:dialog.root>
Mount it on the client with mount(), see Mount on the client.
Offer asChild in your own components
Permalink to heading "Offer asChild in your own components"A component accepts asChild when its template marks the target tag with {ui:asChild()}. The primitives do this already. For your own components, add it next to {ui:attributes()}:
<button {ui:attributes()} {ui:asChild()}>
<f:slot />
</button>
See Composition.
Move constants for entry files to the global context
Permalink to heading "Move constants for entry files to the global context"ui:hydrationData is gone. Expose constants like icons or labels with a getter on the global context and read them with getGlobal(). Static markup that only your script uses can also live in the entry file.
Read the slot into a variable
Permalink to heading "Read the slot into a variable"ui:slotFilled is gone. Store the slot and test the variable, which renders the slot once:
<f:variable name="note" value="{f:slot() -> f:spaceless()}" />
<f:if condition="{note}">
<p class="text-sm">{note}</p>
</f:if>
Pass the namespace to ui:context
Permalink to heading "Pass the namespace to ui:context"name takes the component as you write its tag: <ui:context name="ui:dialog" as="dialogContext" />.
Update the Select markup
Permalink to heading "Update the Select markup"- Wrap the items in the new
select.listpart, the element withrole="listbox". Move scroll styles such asoverflow-y-autofrom the content to the list. See Select. defaultValuetakes a string or a list of strings, and an integer throws. Pass a uid as a string, for example from a context getter. The same goes for Combobox.
Rename the Field parts
Permalink to heading "Rename the Field parts"field.descriptionisfield.helperText.field.errorisfield.errorText.
See Field.
Mark HTML from ui:call as raw
Permalink to heading "Mark HTML from ui:call as raw"ui:call escapes the text it returns. Wrap it in f:format.raw where the method returns HTML.
Name templates .fluid.html
Permalink to heading "Name templates .fluid.html"Recommended: rename component templates to *.fluid.html, like the primitives. typo3 fluid:analyze checks only files with that extension, which makes it a quick way to find templates that no longer parse.
Mount on the client
Permalink to heading "Mount on the client"| 0.x | 1.0 |
|---|---|
mount('select', callback) |
mountAll('ui:select', callback) |
mountControlled('dialog', id, cb) |
mount('ui:dialog', id, cb) |
getHydrationData('carousel', id) |
getHydrationData('ui:carousel', id) |
Every lookup takes the namespaced name, as you write the tag. A wrapper like ui:navigationMenu.root registers under ui:navigationMenu, a primitive you use directly under primitives:<name>.
import { mountAll } from 'fluid-primitives';
import { Select } from 'fluid-primitives/select';
mountAll('ui:select', ({ props }) => new Select(props).init());
Mount components with mountAll() or mount(), not with new Select(getHydrationData(...).props). The mount functions convert client props such as a Select collection into the objects the machine expects, and the raw hydration data fails with findMany is not a function. See Mounting.
Find parts with the hydrator
Permalink to heading "Find parts with the hydrator"hydrator.getElement(part)ishydrator.query(part),getElementsisqueryAll.- Part names are camelCase:
hydrator.query('nextTrigger'). new ComponentHydrator(name, rootId, doc)takes noidsargument. Inside a mount callback,createHydrator()builds one for you.- In a component class, use
this.hydrator.query()in place ofthis.getElement().
See ComponentHydrator.
Call init() on every component
Permalink to heading "Call init() on every component"The constructor stores the props, init() builds the hydrator, machine and api. Call init() before you read api or hydrator. A class that overrides init() returns this, so a mount callback can return new Accordion(props).init().
Jramke\FluidPrimitives\Domain\Model\ListCollectionisJramke\FluidPrimitives\Domain\Dto\ListCollection. The client receives only value, label, disabled and group of each item. Read other keys fromitem.originalin templates.BaseContextisDefaultContext, which is final and internal. ExtendAbstractComponentContextfor your own contexts.globalsis a reserved prop name, and a context class cannot declaregetGlobals().AjaxValidationTraitisJsonValidationErrorsTrait. A form posts withextbase.post(), andonSubmitreceives noposthelper. See Forms.- Template errors throw
MissingContextException,MissingPropExceptionandInvalidPropValueException. CatchJramke\FluidPrimitives\Exception\ExceptionInterface.
Generate TypeScript types
Permalink to heading "Generate TypeScript types"Optional: jramke/fluid-types types the props of mountAll() and mount() per component. See Generating TypeScript types.
composer require --dev jramke/fluid-types
vendor/bin/typo3 typescript:generate
Add the output to your tsconfig.json with a glob. A plain "types.generated" entry reads as a file name because of the dot, and TypeScript skips the folder:
{
"include": ["src", "types.generated/*.d.ts"]
}
Check the result
Permalink to heading "Check the result"- Run
typo3 fluid:analyzeandtsc --noEmit. - Open each page type in the Development context.
devWarn()messages start with[fluid-primitives]and point at a mount without hydration data or a component mounted twice.