Style Context Tutorial
Last updated
Last updated
Use the style context to create or modify Bike outline styles.
.
Entry point style/main.ts
Fun custom styles, but complex!
Use to define custom stylesheets for Bike’s outline editor.
Import bike/style context API using import { SYMBOL } from 'bike/style'
.
Styles are powerful, but also quite complex.
This tutorial will show you how styles work and what they can do. If you decide to create your own style, you should also see the default outline style that's in the Bike extension kit.
Each outline style is an ordered list of rules, organized into layer groups. A rule is composed of a relative and a callback function. The callback function is passed the editor state and a style object to modify. The purpose of layer groups is to allow rules to be inserted into (or included from) existing outline styles.
To style an outline element:
A default style object is created.
A list of the rules that match the element is created.
The style object is passed to each matching rule and may be modified.
Through this process, the default style object is transformed into a specific style.
The order that you define your rules is important, because each rule can read the current state of the style object when deciding what styles it will apply. Generally, you want generic style rules listed first and refinements listed later.
The rule callbacks must be pure functions. Given the same editor state and style state, they must always generate the same end style state. They should only read values from the editor and style parameters when deciding what style state to set.
Styles are not inherited from parents as they are in CSS. Instead, you should create a generic rule that matches all elements and sets defaults there. Put this generic rule in the base layer of your theme and then follow it with more specific rules.
Tutorial assumes that you have run the npm run watch
command. Your extension should automatically build and install when you save changes.
We will start by creating an empty outline style in style/main.ts
:
Save and then select your style: Bike > Window > Style Sheets > Tutorial.
Notice that your outline editor now shows no indentation or formatting. It also doesn't show selection, etc. Outline styles are responsible for defining the visual state of the outline editor, and this style has no rules.
In style/main.ts
show outline structure:
Save, and you should see your outline structure again.
Note that when adding rules we always add them to a layer. This helps to organize them, and makes it possible to modify and include existing styles. Layers are ordered by when they are first used. Now in our outline style the rules in the base
layer will now always be processed first, since that's the first layer that we have used.
Replace style/main.ts
to see the structure more clearly:
The visual structure of your outline is now apparent. Rows (blue border) contain text (green border) and potentially other child rows.
The blue and green rectangles are created by attaching decorations to the row and to the row's text. Decorations can also be attached to runs of the row's text. Decorations are attached to the underlying text layout, but they don't affect that layout. If you need to make space for a decoration, add padding to the element you are decorating.
Notice that when you select text in the outline editor, you can't see any selection marks.
In style/main.ts
, add a selection rule:
Save, and now you should see selection marks when you select within a single paragraph. They will disappear when you select multiple paragraphs, but we'll fix that eventually.
How would you even know about that @view-selected-range
attribute we just used? This is where the outline path explorer is useful. Select Window > Outline Path Explorer. Then make sure that "Show View Attributes" is selected. Then make some selections.
You should see the @view-selected-range
attribute show up in the outline path explorer when you select a range of text. You can also type .@view-selected-range
into the outline path explorer’s search field, and then the selected range of text will be highlighted green.
You can use the outline path explorer to find attributes to use when styling, and you can also see which elements will be selected by your outline paths when the editor is in various states.
In our current selection rule, we are setting the run's background color. This is setting Core Text’s background color attribute. This works, but it's more flexible to use decorations.
In style/main.ts
, replace the selection layer with:
This is similar to how we used decorations to draw boxes around rows and row text. One difference is that in this case, we set the zPosition
property. We want to make sure that the text selection draws behind the run's text.
In Bike, there are two selection modes–text selection mode and block selection mode. So far, we are only styling text selections. When you extend the selection beyond a single paragraph, Bike starts using block selection mode.
In style/main.ts
, replace the selection layer to also show block selections:
A few interesting things are happening here.
For this example, I am reusing the "background" decoration and changing its style. If I give the background rounded corners in my first rule, then the block selection will also have rounded corners. Alternatively, I could have created a new decoration with a new ID to indicate block selection.
In style/main.ts
, add support for bold and italic text:
While these rules are simple, you can also add decorations to text runs, just like you can add them to rows and rows’ text. Try creating a rule to show text with the @highlight
attribute. You can add/remove that attribute from text by using Format > Highlight.
So far we've used decorations as simple backgrounds, but they can do more. They can be placed and sized. They can contain images, symbols, and text. Let's use decorations to show a checkbox.
In style/main.ts
start by adding this rule:
With that rule in place, when you create a task in your outline, it will have a red background.
In style/main.ts
, modify the above rule to show an image instead:
We are creating a SF Symbol configuration. Then we use that configuration to create an image. Finally, we assign that image to the decorations’ contents. When you save this rule, you should see an empty checkbox centered over all task rows.
In style/main.ts
, position the checkbox in a better location:
Decorations have x
, y
, width
, and height
properties of type LayoutValue
. You get layout values from the passed-in layout
parameter. These are logical values that are resolved later in the layout process to position the decoration.
In style/main.ts
, add a new rule for "done" tasks that shows a checkmark:
Note that in this rule we don't need to redo all the mark positioning work. This is because we are using the same 'mark' decoration layer. It is already positioned correctly. We only need to change the image content to show the check.
Each rule callback takes two parameters—editor and style object. So far, we've just been modifying the style object. We can also read from values from the editor that we can use in our style rules.
For example, you might add these lines to the first match-all .*
rule:
The editor's theme contains user prefered values. Now when you View > Text Size > Zoom In/Out, the text in your editor. In addition to theme, the editor also includes settings and system state such as isKey
or isTyping
.
Creating a full style that supports all of Bike's features is quite complex. Here are some ways to offload that work with existing outline styles, such as the default style that ships with Bike:
It may be that you don't need to create a whole new style; maybe you just want to add a few rules to an existing style(s). You can do this using the defineOutlineStyleModifier
API. This allows you to insert rules into specific layers of existing outline styles.
It may be that you do want to create a whole new style, but you want to include rules into that style from other outline styles. For example, maybe you want to include the "run-formatting" rules from the standard style.
You can include rules from other styles like this:
Last, you can copy an existing style. Rename it, and make modifications.
The matching outline path is calling the selection
function with a block
parameter value. This function will return true if the current row has block selection. This and other outline path functions are documented in .
Study the extensions that come with the .
Read through the API's documentation in the .
Ask questions in the .