Date: Wed, 5 Jul 2023 08:40:25 -0400
Subject: [PATCH 17/86] toucan-core: Add optionKey and onFilter
---
docs/components/select/demo/single.md | 7 ++--
.../src/components/form/controls/select.hbs | 2 +-
.../src/components/form/controls/select.ts | 40 ++++++++++---------
3 files changed, 26 insertions(+), 23 deletions(-)
diff --git a/docs/components/select/demo/single.md b/docs/components/select/demo/single.md
index f6129315..5ec0b68e 100644
--- a/docs/components/select/demo/single.md
+++ b/docs/components/select/demo/single.md
@@ -8,7 +8,7 @@
@contentClass='z-10'
@selected={{this.selected}}
@selectedLabel={{this.selected.label}}
- @filterBy='label'
+ @optionKey='label'
placeholder='Colors'
as |select|
>
@@ -36,9 +36,10 @@
@options={{this.options}}
@contentClass='z-10'
@selected={{this.selected3}}
+ @optionKey='label'
@selectedLabel={{this.selected3.label}}
- @filterBy={{this.onFilterBy}}
- placeholder='Colors w/ Search'
+ @onFilter={{this.onFilterBy}}
+ placeholder='Colors w/ Filtering'
as |select|
>
diff --git a/packages/ember-toucan-core/src/components/form/controls/select.hbs b/packages/ember-toucan-core/src/components/form/controls/select.hbs
index b45156d0..5d9c1197 100644
--- a/packages/ember-toucan-core/src/components/form/controls/select.hbs
+++ b/packages/ember-toucan-core/src/components/form/controls/select.hbs
@@ -19,7 +19,7 @@
readonly={{@isReadOnly}}
role="combobox"
spellcheck="false"
- value={{@selectedLabel}}
+ value={{this.selected}}
{{on
"blur"
(if
diff --git a/packages/ember-toucan-core/src/components/form/controls/select.ts b/packages/ember-toucan-core/src/components/form/controls/select.ts
index 89a24553..89fb49bc 100644
--- a/packages/ember-toucan-core/src/components/form/controls/select.ts
+++ b/packages/ember-toucan-core/src/components/form/controls/select.ts
@@ -54,6 +54,8 @@ export interface ToucanFormSelectControlComponentSignature {
*/
options?: unknown[];
+ optionKey?: string;
+
/**
* Sets the placeholder value.
*/
@@ -69,19 +71,7 @@ export interface ToucanFormSelectControlComponentSignature {
*/
selectedLabel?: string;
- /**
- * By default, the filter functionality works by comparing strings; however,
- * consumers may want to adjust the lookup functionality on their own. This
- * is especially true when dealing with an array of objects rather than an
- * array of strings.
- *
- * Provide a string that will be used to do a lookup on `@options` using
- * the string provide as an object-key lookup. This is commonly used when
- * `@options` takes the shape of an array of objects.
- *
- * Provide a function that returns an array to write your own custom filtering logic.
- */
- filterBy?: string | ((input: string) => Promise);
+ onFilter?: (input: string) => Promise;
};
Blocks: {
default: [
@@ -174,6 +164,18 @@ export default class ToucanFormSelectControlComponent extends Component option.toLowerCase().startsWith(value.toLowerCase())
);
}
- if (typeof filterBy === 'string') {
+ if (!onFilter && optionKey) {
filteredOptions = optionsArgument?.filter((option) =>
- ((option as Record)[filterBy] as string)
+ ((option as Record)[optionKey] as string)
?.toLowerCase()
?.startsWith(value.toLowerCase())
);
}
- if (typeof filterBy === 'function') {
- filteredOptions = await filterBy(value);
+ if (onFilter) {
+ filteredOptions = await onFilter(value);
}
this.filteredOptions = filteredOptions;
From b9a74d35f5fa31cb40d409c2815498f82ebb63e2 Mon Sep 17 00:00:00 2001
From: Tony Ward <8069555+ynotdraw@users.noreply.github.com>
Date: Wed, 5 Jul 2023 08:56:50 -0400
Subject: [PATCH 18/86] toucan-core: Fix filteredOptions logic
---
.../ember-toucan-core/src/components/form/controls/select.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/ember-toucan-core/src/components/form/controls/select.ts b/packages/ember-toucan-core/src/components/form/controls/select.ts
index 89fb49bc..0ab9b2e0 100644
--- a/packages/ember-toucan-core/src/components/form/controls/select.ts
+++ b/packages/ember-toucan-core/src/components/form/controls/select.ts
@@ -352,7 +352,7 @@ export default class ToucanFormSelectControlComponent extends Component 0) {
this.activeIndex = 0;
}
}
From 8532fee349c3b4b37672363bbe2bfe0448c3365a Mon Sep 17 00:00:00 2001
From: Tony Ward <8069555+ynotdraw@users.noreply.github.com>
Date: Wed, 5 Jul 2023 08:58:48 -0400
Subject: [PATCH 19/86] toucan-core: Use ember-is-equal
---
.../src/components/form/controls/select.hbs | 2 +-
.../src/components/form/controls/select.ts | 6 +-----
2 files changed, 2 insertions(+), 6 deletions(-)
diff --git a/packages/ember-toucan-core/src/components/form/controls/select.hbs b/packages/ember-toucan-core/src/components/form/controls/select.hbs
index 5d9c1197..6ff5231e 100644
--- a/packages/ember-toucan-core/src/components/form/controls/select.hbs
+++ b/packages/ember-toucan-core/src/components/form/controls/select.hbs
@@ -71,7 +71,7 @@
(ensure-safe-component this.Option)
isActive=(this.isEqual index this.activeIndex)
isDisabled=@isDisabled
- isSelected=(this.checkSelection @selected option)
+ isSelected=(this.isEqual @selected option)
isReadOnly=@isReadOnly
onClick=(fn this.onChange index)
onMouseover=(fn this.onOptionMouseover index)
diff --git a/packages/ember-toucan-core/src/components/form/controls/select.ts b/packages/ember-toucan-core/src/components/form/controls/select.ts
index 0ab9b2e0..01a613d6 100644
--- a/packages/ember-toucan-core/src/components/form/controls/select.ts
+++ b/packages/ember-toucan-core/src/components/form/controls/select.ts
@@ -192,10 +192,6 @@ export default class ToucanFormSelectControlComponent extends Component
Date: Wed, 5 Jul 2023 09:37:09 -0400
Subject: [PATCH 20/86] toucan-core: Reset input to selection on blur
---
.../src/components/form/controls/select.hbs | 6 +-
.../src/components/form/controls/select.ts | 77 ++++++++++++++++++-
2 files changed, 78 insertions(+), 5 deletions(-)
diff --git a/packages/ember-toucan-core/src/components/form/controls/select.hbs b/packages/ember-toucan-core/src/components/form/controls/select.hbs
index 6ff5231e..bd8c9d59 100644
--- a/packages/ember-toucan-core/src/components/form/controls/select.hbs
+++ b/packages/ember-toucan-core/src/components/form/controls/select.hbs
@@ -19,13 +19,17 @@
readonly={{@isReadOnly}}
role="combobox"
spellcheck="false"
- value={{this.selected}}
+ value={{this.inputValue}}
{{on
"blur"
(if
this.isDisabledOrReadOnlyOrWithoutOptions this.noop this.closePopover
)
}}
+ {{on
+ "blur"
+ (if this.isDisabledOrReadOnlyOrWithoutOptions this.noop this.resetValue)
+ }}
{{on
"click"
(if
diff --git a/packages/ember-toucan-core/src/components/form/controls/select.ts b/packages/ember-toucan-core/src/components/form/controls/select.ts
index 01a613d6..fc52433a 100644
--- a/packages/ember-toucan-core/src/components/form/controls/select.ts
+++ b/packages/ember-toucan-core/src/components/form/controls/select.ts
@@ -107,6 +107,7 @@ export interface ToucanFormSelectControlComponentSignature {
export default class ToucanFormSelectControlComponent extends Component {
@tracked activeIndex: number | null = null;
+ @tracked inputValue: string | undefined;
@tracked isPopoverOpen = false;
@tracked filteredOptions: unknown[] | undefined;
@@ -212,10 +213,37 @@ export default class ToucanFormSelectControlComponent extends Component)[optionKey];
+
+ this.inputValue = option;
+ }
+
+ this.args.onChange?.(this.options[this.activeIndex]);
}
@action
@@ -343,10 +371,12 @@ export default class ToucanFormSelectControlComponent extends Component
Date: Wed, 5 Jul 2023 10:03:05 -0400
Subject: [PATCH 21/86] toucan-core: Handle tab+backspace keyboard events
---
docs/components/select/demo/single.md | 2 ++
.../src/components/form/controls/select.ts | 12 ++++++++++++
2 files changed, 14 insertions(+)
diff --git a/docs/components/select/demo/single.md b/docs/components/select/demo/single.md
index 5ec0b68e..f030f829 100644
--- a/docs/components/select/demo/single.md
+++ b/docs/components/select/demo/single.md
@@ -134,6 +134,8 @@ export default class extends Component {
@action
onFilterBy(input) {
+ console.log(`filtering with the value "${input}"`);
+
if (input.length > 0) {
return this.options.filter((option) =>
option.label.toLowerCase().startsWith(input.toLowerCase())
diff --git a/packages/ember-toucan-core/src/components/form/controls/select.ts b/packages/ember-toucan-core/src/components/form/controls/select.ts
index fc52433a..2b2f75e3 100644
--- a/packages/ember-toucan-core/src/components/form/controls/select.ts
+++ b/packages/ember-toucan-core/src/components/form/controls/select.ts
@@ -253,6 +253,18 @@ export default class ToucanFormSelectControlComponent extends Component
Date: Wed, 5 Jul 2023 10:54:54 -0400
Subject: [PATCH 22/86] toucan-core: Remove unused component args
---
.../src/components/form/controls/select.ts | 10 ----------
1 file changed, 10 deletions(-)
diff --git a/packages/ember-toucan-core/src/components/form/controls/select.ts b/packages/ember-toucan-core/src/components/form/controls/select.ts
index 2b2f75e3..05ab46b1 100644
--- a/packages/ember-toucan-core/src/components/form/controls/select.ts
+++ b/packages/ember-toucan-core/src/components/form/controls/select.ts
@@ -56,21 +56,11 @@ export interface ToucanFormSelectControlComponentSignature {
optionKey?: string;
- /**
- * Sets the placeholder value.
- */
- placeholder?: string;
-
/**
* The currently selected option. If `@options` is an array of strings, provide a string. If `@options` is an array of objects, pass the entire object.
*/
selected?: string | Record | undefined;
- /**
- * Sets what is shown in the input field.
- */
- selectedLabel?: string;
-
onFilter?: (input: string) => Promise;
};
Blocks: {
From 8221150cf41968d7889059007e4573391ccfcceb Mon Sep 17 00:00:00 2001
From: Tony Ward <8069555+ynotdraw@users.noreply.github.com>
Date: Wed, 5 Jul 2023 11:12:28 -0400
Subject: [PATCH 23/86] toucan-core: Fix up SelectField component
---
.../components/select-field/demo/base-demo.md | 61 ++++++++++++-------
.../src/components/form/fields/select.hbs | 10 +--
.../src/components/form/fields/select.ts | 47 +++++++++-----
3 files changed, 77 insertions(+), 41 deletions(-)
diff --git a/docs/components/select-field/demo/base-demo.md b/docs/components/select-field/demo/base-demo.md
index bafc5b15..6e8f7054 100644
--- a/docs/components/select-field/demo/base-demo.md
+++ b/docs/components/select-field/demo/base-demo.md
@@ -3,14 +3,18 @@
@label='Label'
@hint='Type "blue" into the field'
@error={{this.errorMessage}}
- @onChange={{this.handleChange}}
- @popoverClass="z-10"
- placeholder="Colors"
+ @onChange={{this.onChange}}
+ @options={{this.options}}
+ @contentClass='z-10'
+ @selected={{this.selected}}
+ @selectedLabel={{this.selected.label}}
+ @optionKey='label'
+ placeholder='Colors'
as |select|
>
- {{#each this.colors as |color|}}
-
- {{/each}}
+
+ {{select.option.label}}
+
```
@@ -20,40 +24,51 @@ import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';
export default class extends Component {
- colors = [
+ @tracked selected;
+ @tracked errorMessage;
+
+ options = [
{
- label: "Blue",
- name: "blue",
+ label: 'Blue',
+ name: 'blue',
},
{
- label: "Green",
- name: "green",
+ label: 'Green',
+ name: 'green',
},
{
- label: "Yellow",
- name: "yellow",
+ label: 'Yellow',
+ name: 'yellow',
},
{
- label: "Orange",
- name: "orange",
+ label: 'Orange',
+ name: 'orange',
},
{
- label: "Red",
- name: "red",
+ label: 'Red',
+ name: 'red',
},
{
- label: "Purple",
- name: "purple",
+ label: 'Purple',
+ name: 'purple',
},
{
- label: "Teal",
- name: "teal",
+ label: 'Teal',
+ name: 'teal',
},
];
@action
- onChange(values: string[]) {
- console.log(values)
+ onChange(option) {
+ this.selected = option;
+ console.log(option);
+
+ if (option.label !== 'Blue') {
+ this.errorMessage = 'Please select "Blue"';
+ return;
+ }
+
+ this.errorMessage = null;
}
}
```
diff --git a/packages/ember-toucan-core/src/components/form/fields/select.hbs b/packages/ember-toucan-core/src/components/form/fields/select.hbs
index c7ec373a..ba3a957c 100644
--- a/packages/ember-toucan-core/src/components/form/fields/select.hbs
+++ b/packages/ember-toucan-core/src/components/form/fields/select.hbs
@@ -40,13 +40,15 @@
id={{field.id}}
aria-describedby="{{if @hint field.hintId}} {{if @error field.errorId}}"
aria-invalid={{if @error "true"}}
+ @contentClass={{@contentClass}}
+ @hasError={{this.hasError}}
@isDisabled={{@isDisabled}}
- @isMultiple={{@isMultiple}}
@isReadOnly={{@isReadOnly}}
@onChange={{@onChange}}
- @hasError={{this.hasError}}
- @initialSelectedValues={{@initialSelectedValues}}
- @popoverClass={{@popoverClass}}
+ @onFilter={{@onFilter}}
+ @optionKey={{@optionKey}}
+ @options={{@options}}
+ @selected={{@selected}}
...attributes
as |select|
>
diff --git a/packages/ember-toucan-core/src/components/form/fields/select.ts b/packages/ember-toucan-core/src/components/form/fields/select.ts
index 2f1282e3..4533fa41 100644
--- a/packages/ember-toucan-core/src/components/form/fields/select.ts
+++ b/packages/ember-toucan-core/src/components/form/fields/select.ts
@@ -9,6 +9,12 @@ import type { ToucanFormSelectControlComponentSignature } from '../controls/sele
export interface ToucanFormSelectFieldComponentSignature {
Element: HTMLInputElement;
Args: {
+ /**
+ * A CSS class to add to this component's content container.
+ * Commonly used to specify a `z-index`.
+ */
+ contentClass?: string;
+
/**
* Provide a string or array of strings to this argument to render an error message and apply error styling to the Field.
*/
@@ -19,21 +25,11 @@ export interface ToucanFormSelectFieldComponentSignature {
*/
hint?: string;
- /**
- * Sets the values to be selected on render.
- */
- initialSelectedValues?: string[];
-
/**
* Sets the disabled attribute on the input.
*/
isDisabled?: boolean;
- /**
- * Set to allow multiple option to be selected at once.
- */
- isMultiple?: boolean;
-
/**
* Sets the readonly attribute of the input.
*/
@@ -47,19 +43,42 @@ export interface ToucanFormSelectFieldComponentSignature {
/**
* The function called when a new selection is made.
*/
- onChange?: (values: string[]) => void;
+ onChange?: ToucanFormSelectControlComponentSignature['Args']['onChange'];
/**
- * A CSS class to add to the popover.
- * Commonly used to specify a `z-index`.
+ * The function called when a user types into the combobox textbox.
+ *
+ * Typically used for making a request to the server and populating
+ * `@options` with the results.
+ */
+ onFilter?: ToucanFormSelectControlComponentSignature['Args']['onFilter'];
+
+ /**
+ * When `@options` is an array of objects, `@selected` is also an object.
+ * The `@optionKey` is used to determine which key of the object should
+ * be used for both filtering and displayed the selected value in the
+ * textbox.
+ */
+ optionKey?: ToucanFormSelectControlComponentSignature['Args']['optionKey'];
+
+ /**
+ * `@options` forms the content of this component.
+ *
+ * To support a variety of data shapes, `@options` is typed as `unknown[]` and treated as though it were opaque.
+ * `@options` is simply iterated over then passed back to you as a block parameter (`select.option`).
*/
- popoverClass?: string;
+ options?: ToucanFormSelectControlComponentSignature['Args']['options'];
/**
* A test selector for targeting the root element of the field.
* In this case, the wrapping div element.
*/
rootTestSelector?: string;
+
+ /**
+ * The currently selected option. If `@options` is an array of strings, provide a string. If `@options` is an array of objects, pass the entire object and use `@optionKey`.
+ */
+ selected?: ToucanFormSelectControlComponentSignature['Args']['selected'];
};
Blocks: {
default: ToucanFormSelectControlComponentSignature['Blocks']['default'];
From 9aaefd61b97dd8da147391daf6b35b97c43c29b4 Mon Sep 17 00:00:00 2001
From: Tony Ward <8069555+ynotdraw@users.noreply.github.com>
Date: Wed, 5 Jul 2023 11:14:55 -0400
Subject: [PATCH 24/86] toucan-core: Add type="text"
---
.../ember-toucan-core/src/components/form/controls/select.hbs | 1 +
1 file changed, 1 insertion(+)
diff --git a/packages/ember-toucan-core/src/components/form/controls/select.hbs b/packages/ember-toucan-core/src/components/form/controls/select.hbs
index bd8c9d59..1bdd8ffb 100644
--- a/packages/ember-toucan-core/src/components/form/controls/select.hbs
+++ b/packages/ember-toucan-core/src/components/form/controls/select.hbs
@@ -19,6 +19,7 @@
readonly={{@isReadOnly}}
role="combobox"
spellcheck="false"
+ type="text"
value={{this.inputValue}}
{{on
"blur"
From e8ab8f4accb7183266e2bba3d62ec15b01e1af81 Mon Sep 17 00:00:00 2001
From: Tony Ward <8069555+ynotdraw@users.noreply.github.com>
Date: Wed, 5 Jul 2023 11:15:06 -0400
Subject: [PATCH 25/86] toucan-core: JSDoc for select control
---
.../src/components/form/controls/select.ts | 17 ++++++++++++++---
1 file changed, 14 insertions(+), 3 deletions(-)
diff --git a/packages/ember-toucan-core/src/components/form/controls/select.ts b/packages/ember-toucan-core/src/components/form/controls/select.ts
index 05ab46b1..3b724746 100644
--- a/packages/ember-toucan-core/src/components/form/controls/select.ts
+++ b/packages/ember-toucan-core/src/components/form/controls/select.ts
@@ -39,13 +39,20 @@ export interface ToucanFormSelectControlComponentSignature {
*/
isReadOnly?: boolean;
- // PR: callback type doesn't fit here as well as elsewhere. different events here. they don't make as much sense to pass along to the consumer. ditch callback type altogether?
/**
* Called when the user makes a selection.
* It is called with the selected option (derived from `@options`) as its only argument.
*/
onChange?: (option: unknown) => void;
+ /**
+ * The function called when a user types into the combobox textbox.
+ *
+ * Typically used for making a request to the server and populating
+ * `@options` with the results.
+ */
+ onFilter?: (input: string) => Promise
;
+
/**
* `@options` forms the content of this component.
*
@@ -54,14 +61,18 @@ export interface ToucanFormSelectControlComponentSignature {
*/
options?: unknown[];
+ /**
+ * When `@options` is an array of objects, `@selected` is also an object.
+ * The `@optionKey` is used to determine which key of the object should
+ * be used for both filtering and displayed the selected value in the
+ * textbox.
+ */
optionKey?: string;
/**
* The currently selected option. If `@options` is an array of strings, provide a string. If `@options` is an array of objects, pass the entire object.
*/
selected?: string | Record | undefined;
-
- onFilter?: (input: string) => Promise;
};
Blocks: {
default: [
From 195cdecc37cfb839da4dad34afefb7ed9c17523e Mon Sep 17 00:00:00 2001
From: Tony Ward <8069555+ynotdraw@users.noreply.github.com>
Date: Wed, 5 Jul 2023 11:19:14 -0400
Subject: [PATCH 26/86] toucan-core: Add lock icon
---
.../src/components/form/fields/select.hbs | 12 +++++++++++-
.../src/components/form/fields/select.ts | 7 +++++++
2 files changed, 18 insertions(+), 1 deletion(-)
diff --git a/packages/ember-toucan-core/src/components/form/fields/select.hbs b/packages/ember-toucan-core/src/components/form/fields/select.hbs
index ba3a957c..8fea3d7c 100644
--- a/packages/ember-toucan-core/src/components/form/fields/select.hbs
+++ b/packages/ember-toucan-core/src/components/form/fields/select.hbs
@@ -13,12 +13,21 @@
)
)
}}
-
+ {{!-- TODO: Need to pass @isDisabled={{@isDisabled}} after a rebase --}}
+
{{#if (has-block "label")}}
{{yield to="label"}}
{{else}}
{{@label}}
{{/if}}
+
+ {{#if this.isReadOnlyOrDisabled}}
+
+ {{/if}}
{{/if}}
@@ -27,6 +36,7 @@
(hash blockExists=(has-block "hint") argName="hint" arg=@hint)
)
}}
+ {{!-- TODO: Need to pass @isDisabled={{@isDisabled}} after a rebase --}}
{{#if (has-block "hint")}}
{{yield to="hint"}}
diff --git a/packages/ember-toucan-core/src/components/form/fields/select.ts b/packages/ember-toucan-core/src/components/form/fields/select.ts
index 4533fa41..2a958ef5 100644
--- a/packages/ember-toucan-core/src/components/form/fields/select.ts
+++ b/packages/ember-toucan-core/src/components/form/fields/select.ts
@@ -1,6 +1,7 @@
import Component from '@glimmer/component';
import assertBlockOrArgumentExists from '../../../-private/assert-block-or-argument-exists';
+import LockIcon from '../../../-private/icons/lock';
import type { AssertBlockOrArg } from '../../../-private/assert-block-or-argument-exists';
import type { ErrorMessage } from '../../../-private/types';
@@ -88,6 +89,8 @@ export interface ToucanFormSelectFieldComponentSignature {
}
export default class ToucanFormInputFieldComponent extends Component {
+ LockIcon = LockIcon;
+
assertBlockOrArgumentExists = ({
blockExists,
argName,
@@ -99,4 +102,8 @@ export default class ToucanFormInputFieldComponent extends Component
Date: Wed, 5 Jul 2023 11:22:54 -0400
Subject: [PATCH 27/86] docs: Rename select single to base-demo
---
docs/components/select/demo/{single.md => base-demo.md} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename docs/components/select/demo/{single.md => base-demo.md} (100%)
diff --git a/docs/components/select/demo/single.md b/docs/components/select/demo/base-demo.md
similarity index 100%
rename from docs/components/select/demo/single.md
rename to docs/components/select/demo/base-demo.md
From 9062d64cde58e92b6c15fec31245f2f209378aa2 Mon Sep 17 00:00:00 2001
From: Tony Ward <8069555+ynotdraw@users.noreply.github.com>
Date: Wed, 5 Jul 2023 12:12:50 -0400
Subject: [PATCH 28/86] docs: Update documentation with latest args
---
docs/components/select-field/index.md | 2 +-
docs/components/select/demo/base-demo.md | 9 +-
docs/components/select/index.md | 250 ++++++++++++++++++
.../form/controls/select/option.hbs | 1 +
.../src/components/form/controls/select.ts | 5 +-
5 files changed, 255 insertions(+), 12 deletions(-)
diff --git a/docs/components/select-field/index.md b/docs/components/select-field/index.md
index 30cdbd1a..daeb9cb9 100644
--- a/docs/components/select-field/index.md
+++ b/docs/components/select-field/index.md
@@ -1,4 +1,4 @@
-# Select field
+# Select Field
Provides a Toucan-styled select with filtering that builds on top of the Field component.
diff --git a/docs/components/select/demo/base-demo.md b/docs/components/select/demo/base-demo.md
index f030f829..2279de83 100644
--- a/docs/components/select/demo/base-demo.md
+++ b/docs/components/select/demo/base-demo.md
@@ -1,13 +1,10 @@
-
-
```hbs template
-
### SelectField with `:label` and `:hint` blocks