waibu-bootstrap 2.2.3 → 2.4.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.
Files changed (26) hide show
  1. package/extend/waibuMpa/theme/component/method/after-build-tag/background.js +1 -1
  2. package/extend/waibuMpa/theme/component/method/after-build-tag/border.js +1 -1
  3. package/extend/waibuMpa/theme/component/method/after-build-tag/dim.js +1 -1
  4. package/extend/waibuMpa/theme/component/method/after-build-tag/display.js +1 -1
  5. package/extend/waibuMpa/theme/component/method/after-build-tag/flex.js +1 -1
  6. package/extend/waibuMpa/theme/component/method/after-build-tag/font.js +1 -1
  7. package/extend/waibuMpa/theme/component/method/after-build-tag/gutter.js +1 -1
  8. package/extend/waibuMpa/theme/component/method/after-build-tag/link.js +1 -1
  9. package/extend/waibuMpa/theme/component/method/after-build-tag/margin-padding.js +1 -1
  10. package/extend/waibuMpa/theme/component/method/after-build-tag/position.js +2 -2
  11. package/extend/waibuMpa/theme/component/method/after-build-tag/rounded.js +1 -1
  12. package/extend/waibuMpa/theme/component/method/after-build-tag/text.js +1 -1
  13. package/extend/waibuMpa/theme/component/widget/_lib.js +2 -2
  14. package/extend/waibuMpa/theme/component/widget/app-launcher.js +2 -2
  15. package/extend/waibuMpa/theme/component/widget/breadcrumb-item.js +1 -1
  16. package/extend/waibuMpa/theme/component/widget/breadcrumb.js +2 -2
  17. package/extend/waibuMpa/theme/component/widget/carousel.js +1 -1
  18. package/extend/waibuMpa/theme/component/widget/collapse.js +2 -1
  19. package/extend/waibuMpa/theme/component/widget/form-input.js +1 -1
  20. package/extend/waibuMpa/theme/component/widget/form-select-country.js +3 -6
  21. package/extend/waibuMpa/theme/component/widget/form-select-ext.js +45 -50
  22. package/extend/waibuMpa/theme/component/widget/form.js +1 -1
  23. package/extend/waibuMpa/theme/component/widget/grid-col.js +1 -1
  24. package/extend/waibuStatic/asset/css/widget.css +34 -0
  25. package/package.json +1 -1
  26. package/wiki/CHANGES.md +12 -0
@@ -5,7 +5,7 @@ const variants = ['subtle', 'secondary', 'tertiary']
5
5
 
6
6
  function background ({ key, params }) {
7
7
  const { uniq } = this.app.lib._
8
- const attrs = this.app.waibuMpa.attrToArray(params.attr[key])
8
+ const attrs = this.app.waibu.attrToArray(params.attr[key])
9
9
  const bgColors = ['body', 'black', 'white', 'transparent', ...colors]
10
10
  for (const attr of attrs) {
11
11
  const [item, val] = attr.split(':')
@@ -5,7 +5,7 @@ const variants = ['subtle', 'secondary', 'tertiary']
5
5
  function border ({ tag, key, params }) {
6
6
  if (['table'].includes(tag)) return
7
7
  const { uniq } = this.app.lib._
8
- const attrs = this.app.waibuMpa.attrToArray(params.attr[key])
8
+ const attrs = this.app.waibu.attrToArray(params.attr[key])
9
9
  const borderColors = ['body', 'black', 'white', ...colors]
10
10
  let hasSide
11
11
  for (const attr of attrs) {
@@ -2,7 +2,7 @@ import { dims } from './_lib.js'
2
2
 
3
3
  function dim ({ key, params }) {
4
4
  const { uniq } = this.app.lib._
5
- const attrs = this.app.waibuMpa.attrToArray(params.attr[key])
5
+ const attrs = this.app.waibu.attrToArray(params.attr[key])
6
6
  for (const attr of attrs) {
7
7
  const [item, val] = attr.split(':')
8
8
  for (const value of uniq((val ?? '').split(','))) {
@@ -2,7 +2,7 @@ import { breakpoints, displays } from './_lib.js'
2
2
 
3
3
  function display ({ key, params }) {
4
4
  const { uniq, isEmpty } = this.app.lib._
5
- const attrs = this.app.waibuMpa.attrToArray(params.attr[key])
5
+ const attrs = this.app.waibu.attrToArray(params.attr[key])
6
6
  let canHaveGap
7
7
  for (const attr of attrs) {
8
8
  const [item, val] = attr.split(':')
@@ -19,7 +19,7 @@ function flex ({ key, params }) {
19
19
  params.attr.class.push('d-flex')
20
20
  return
21
21
  }
22
- const attrs = this.app.waibuMpa.attrToArray(params.attr[key])
22
+ const attrs = this.app.waibu.attrToArray(params.attr[key])
23
23
  const inline = attrs.includes('inline')
24
24
  let hasFlex = false
25
25
  for (const attr of attrs) {
@@ -2,7 +2,7 @@ import { levels, weights, fstyles, parseSimple } from './_lib.js'
2
2
 
3
3
  function font ({ key, params }) {
4
4
  const { uniq } = this.app.lib._
5
- const attrs = this.app.waibuMpa.attrToArray(params.attr[key])
5
+ const attrs = this.app.waibu.attrToArray(params.attr[key])
6
6
  for (const attr of attrs) {
7
7
  const [item, val] = attr.split(':')
8
8
  for (const value of uniq((val ?? '').split(','))) {
@@ -5,7 +5,7 @@ const prefixes = ['x', 'y', '']
5
5
 
6
6
  function gutter ({ key, params }) {
7
7
  const { isString } = this.app.lib._
8
- const { attrToArray } = this.app.waibuMpa
8
+ const { attrToArray } = this.app.waibu
9
9
  if (isString(params.attr.gutter)) {
10
10
  const items = attrToArray(params.attr.gutter)
11
11
  for (const item of items) {
@@ -5,7 +5,7 @@ const ovariants = ['hover']
5
5
 
6
6
  function link ({ key, params }) {
7
7
  const { uniq } = this.app.lib._
8
- const attrs = this.app.waibuMpa.attrToArray(params.attr[key])
8
+ const attrs = this.app.waibu.attrToArray(params.attr[key])
9
9
  const linkColors = ['body', 'black', 'white', ...colors]
10
10
  for (const attr of attrs) {
11
11
  const [item, val] = attr.split(':')
@@ -4,7 +4,7 @@ const sides = ['x', 'y', 'all', ...aligns]
4
4
  const sizes = ['0', 'auto', ...widths]
5
5
 
6
6
  function marginPadding ({ key, params }) {
7
- for (const item of this.app.waibuMpa.attrToArray(params.attr[key])) {
7
+ for (const item of this.app.waibu.attrToArray(params.attr[key])) {
8
8
  let [side, size, bp] = item.split('-')
9
9
  if (bp && !breakpoints.includes(bp)) bp = undefined
10
10
  if (sides.includes(side) && (!size || sizes.includes(size))) {
@@ -4,7 +4,7 @@ const zes = ['n1', '0', '1', '2', '3']
4
4
  function position ({ key, params }) {
5
5
  const { without } = this.app.lib._
6
6
  if (params.attr[key].includes(':')) {
7
- const attrs = this.app.waibuMpa.attrToArray(params.attr[key])
7
+ const attrs = this.app.waibu.attrToArray(params.attr[key])
8
8
  for (const attr of attrs) {
9
9
  const [item, val] = attr.split(':')
10
10
  /*
@@ -19,7 +19,7 @@ function position ({ key, params }) {
19
19
  }
20
20
  }
21
21
  } else {
22
- const [type, arrangeStart, arrangeEnd, translateMiddle] = this.app.waibuMpa.attrToArray(params.attr[key])
22
+ const [type, arrangeStart, arrangeEnd, translateMiddle] = this.app.waibu.attrToArray(params.attr[key])
23
23
  if (positions.includes(type)) {
24
24
  params.attr.class.push(`position-${type}`)
25
25
  if (without(positions, 'static').includes(type) && arrangeStart &&
@@ -7,7 +7,7 @@ function rounded ({ key, params }) {
7
7
  params.attr.class.push('rounded')
8
8
  return
9
9
  }
10
- const attrs = this.app.waibuMpa.attrToArray(params.attr[key])
10
+ const attrs = this.app.waibu.attrToArray(params.attr[key])
11
11
  let hasType
12
12
  for (const attr of attrs) {
13
13
  const [item, val] = attr.split(':')
@@ -8,7 +8,7 @@ const variants = ['emphasis', 'secondary', 'tertiary']
8
8
 
9
9
  function text ({ key, params }) {
10
10
  const { uniq } = this.app.lib._
11
- const attrs = this.app.waibuMpa.attrToArray(params.attr[key])
11
+ const attrs = this.app.waibu.attrToArray(params.attr[key])
12
12
  const textColors = ['body', 'black', 'white', ...colors]
13
13
  for (const attr of attrs) {
14
14
  const [item, val] = attr.split(':')
@@ -147,7 +147,7 @@ export async function buildFormSelect (group, params) {
147
147
  attr.class.push('form-select')
148
148
  let html = params.html
149
149
  if (sizes.includes(attr.size)) attr.class.push(`form-select-${attr.size}`)
150
- if (attr.options) html = this.component.buildOptions({ attr, html: '' })
150
+ if (attr.options) html = this.component.buildOptions({ attr })
151
151
  else {
152
152
  const items = []
153
153
  $(`<div>${trim(html ?? '')}</div>`).find('option').each(function () {
@@ -156,7 +156,7 @@ export async function buildFormSelect (group, params) {
156
156
  html = items.join('\n')
157
157
  }
158
158
  attr = omit(attr, ['size', 'type', 'options', 'value'])
159
- return await this.component.buildTag({ tag: 'select', attr, html })
159
+ return await this.component.buildTag({ tag: attr.tag ?? 'select', attr, html })
160
160
  }
161
161
 
162
162
  export async function buildFormRange (group, params) {
@@ -10,8 +10,8 @@ async function appLauncher () {
10
10
 
11
11
  build = async () => {
12
12
  const { locals } = this.component
13
- const { routePath } = this.app.waibu
14
- const { groupAttrs, attrToArray } = this.app.waibuMpa
13
+ const { routePath, attrToArray } = this.app.waibu
14
+ const { groupAttrs } = this.app.waibuMpa
15
15
  const menu = this.params.attr.menu ?? 'pages'
16
16
  const group = groupAttrs(this.params.attr, ['trigger'])
17
17
  let launcher = `<c:drawer id="${this.params.attr.id}" t:title="Modules" no-padding style="${menu === 'pages' ? 'width:350px' : ''}">\n`
@@ -10,7 +10,7 @@ async function breadcrumbItem () {
10
10
 
11
11
  build = async () => {
12
12
  const { omit } = this.app.lib._
13
- const { attrToArray } = this.app.waibuMpa
13
+ const { attrToArray } = this.app.waibu
14
14
  if (this.params.attr.href) {
15
15
  if (this.params.attr.hrefRebuild) {
16
16
  this.params.attr.hrefRebuild = attrToArray(this.params.attr.hrefRebuild)
@@ -38,8 +38,8 @@ async function breadcrumb () {
38
38
 
39
39
  build = async () => {
40
40
  const { isString, omit } = this.app.lib._
41
- const { routePath } = this.app.waibu
42
- const { urlToBreadcrumb, attrToArray } = this.app.waibuMpa
41
+ const { routePath, attrToArray } = this.app.waibu
42
+ const { urlToBreadcrumb } = this.app.waibuMpa
43
43
  let divider = ''
44
44
  if (this.params.attr.noDivider) divider = ' style="--bs-breadcrumb-divider: \'\';"'
45
45
  else if (isString(this.params.attr.divider)) divider = ` style="--bs-breadcrumb-divider: '${this.params.attr.divider}';"`
@@ -14,7 +14,7 @@ async function carousel () {
14
14
  }
15
15
 
16
16
  build = async () => {
17
- const { attrToArray } = this.app.waibuMpa
17
+ const { attrToArray } = this.app.waibu
18
18
  const { $ } = this.component
19
19
  let activeItem = 0
20
20
  $(this.params.html).children().each(function (idx) {
@@ -11,7 +11,8 @@ async function collapse () {
11
11
  build = async () => {
12
12
  const { generateId } = this.app.lib.aneka
13
13
  const { merge, isString } = this.app.lib._
14
- const { attrToArray, groupAttrs } = this.app.waibuMpa
14
+ const { attrToArray } = this.app.waibu
15
+ const { groupAttrs } = this.app.waibuMpa
15
16
  const items = []
16
17
  const { $ } = this.component
17
18
  const me = this
@@ -3,7 +3,7 @@ import { sizes } from '../method/after-build-tag/_lib.js'
3
3
 
4
4
  export async function handleInput ({ handler, group, params } = {}) {
5
5
  const { trim, filter, has, omit, pull, find, get } = this.app.lib._
6
- const { attrToArray } = this.app.waibuMpa
6
+ const { attrToArray } = this.app.waibu
7
7
  const { $ } = this.component
8
8
  const addons = []
9
9
  const isLabel = has(this.params.attr, 'label')
@@ -1,13 +1,10 @@
1
- import { inlineCss, css, scripts } from './form-select-ext.js'
1
+ import { css, scripts } from './form-select-ext.js'
2
2
 
3
3
  async function formSelectCountry () {
4
4
  return class FormSelectCountry extends this.app.baseClass.MpaWidget {
5
- static css = [...super.css, css]
6
-
5
+ static css = [...super.css, ...css]
7
6
  static scripts = [...super.scripts, scripts]
8
7
 
9
- static inlineCss = inlineCss
10
-
11
8
  constructor (options) {
12
9
  super(options)
13
10
  this.params.noTag = true
@@ -16,7 +13,7 @@ async function formSelectCountry () {
16
13
  build = async () => {
17
14
  const { readConfig } = this.app.bajo
18
15
  const { map } = this.app.lib._
19
- const { base64JsonEncode } = this.app.waibuMpa
16
+ const { base64JsonEncode } = this.app.waibu
20
17
  const countries = await readConfig('bajoCommonDb:/extend/dobo/fixture/country.json', { ignoreError: true, defValue: [] })
21
18
  this.params.attr.options = base64JsonEncode(map(countries, c => {
22
19
  return { value: c.id, text: c.name.replaceAll('\'', '') }
@@ -1,67 +1,36 @@
1
1
  import { buildFormSelect } from './_lib.js'
2
2
  import { build } from './form-input.js'
3
3
 
4
- // TODO: load default values from remote
5
- export const inlineCss = `
6
- .ts-dropdown {
7
- min-width: 242px;
8
- }
9
- .ts-control {
10
- padding-top: 0px;
11
- }
12
- .ts-wrapper {
13
- margin-left: calc(var(--bs-border-width) * -1) !important;
14
- border-top-left-radius: var(--bs-border-radius) !important;
15
- border-bottom-left-radius: var(--bs-border-radius) !important;
16
- white-space: nowrap !important;
17
- }
18
- .ts-control, .ts-control input, .ts-dropdown {
19
- color: inherit;
20
- }
21
- .form-floating > .ts-wrapper {
22
- padding-top: 1.625rem !important;
23
- }
24
-
25
- .ts-wrapper.focus {
26
- border-color: #86b7fe;
27
- outline: 0;
28
- box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
29
- }
30
-
31
- .focus .ts-control {
32
- box-shadow: none;
33
- border: none;
34
- box-shadow: none;
35
- }
36
- `
37
- export const css = 'waibuExtra.virtual:/tom-select/css/tom-select.bootstrap5.min.css'
4
+ export const css = [
5
+ 'waibuExtra.virtual:/tom-select/css/tom-select.bootstrap5.min.css',
6
+ 'waibuBootstrap.asset:/css/widget.css'
7
+ ]
38
8
  export const scripts = 'waibuExtra.virtual:/tom-select/js/tom-select.complete.min.js'
39
9
 
40
10
  async function formSelectExt () {
41
11
  return class FormSelectExt extends this.app.baseClass.MpaWidget {
42
- static css = [...super.css, css]
12
+ static css = [...super.css, ...css]
43
13
  static scripts = [...super.scripts, scripts]
44
- static inlineCss = inlineCss
45
14
 
46
15
  build = async () => {
47
16
  const { generateId } = this.app.lib.aneka
48
17
  const { omit, merge, has } = this.app.lib._
49
18
  const { routePath } = this.app.waibu
50
- const { jsonStringify, base64JsonDecode, groupAttrs } = this.app.waibuMpa
19
+ const { jsonStringify, groupAttrs } = this.app.waibuMpa
20
+ const { base64JsonDecode } = this.app.waibu
51
21
  const { req } = this.component
52
22
  let apiKey = ''
53
23
  if (req.user && this.app.sumba) apiKey = await this.app.sumba.getApiKeyFromUserId(req.user.id)
54
24
  const xref = this.params.attr['x-ref'] ?? 'select'
55
25
  this.params.attr.id = this.params.attr.id ?? generateId('alpha')
56
26
  this.params.attr['x-ref'] = xref
57
- this.params.attr['x-data'] = `{
58
- instance: null
59
- }`
27
+ const xData = ['instance: null', 'value: null']
60
28
  const plugins = ['drag_drop']
61
29
  if (!this.params.attr.noDropdownInput) plugins.push('dropdown_input')
62
30
  if (this.params.attr.removeBtn) plugins.push('remove_button')
63
31
  if (this.params.attr.clearBtn) plugins.push('clear_button')
64
32
  if (this.params.attr.optgroupColumns) plugins.push('optgroup_columns')
33
+ if (this.params.attr.noCaret) this.params.attr.class.push('no-caret') // TODO: no caret remove caret on ALL instances, need to make it instance specific
65
34
  let options = []
66
35
  if (this.params.attr.options) {
67
36
  try {
@@ -92,7 +61,7 @@ async function formSelectExt () {
92
61
  if (group.remote.apiKey === true) fetchOpts.headers.Authorization = `Bearer ${apiKey}` // TODO: get it from wmpa
93
62
  else fetchOpts.headers.Authorization = `Bearer ${group.remote.apiKey}`
94
63
  }
95
- opts = `{
64
+ opts = opts.slice(0, -1) + `,
96
65
  searchField: '${group.remote.searchField}',
97
66
  labelField: '${group.remote.labelField}',
98
67
  valueField: '${group.remote.valueField}',
@@ -117,26 +86,52 @@ async function formSelectExt () {
117
86
  }
118
87
  }`
119
88
  }
120
-
121
- this.params.attr['@load.window'] = `
89
+ let text = `onLoad () {
122
90
  const opts = ${opts}
123
- instance = new TomSelect($refs.${xref}, opts)
91
+ this.instance = new TomSelect($refs.${xref}, opts)
124
92
  const val = $refs.${xref}.dataset.value
93
+ if (!_.isEmpty(val)) {
94
+ this.value = val.split('|')
95
+ }
125
96
  `
97
+ if (this.params.attr.valueStore) {
98
+ const [store, key] = this.params.attr.valueStore.split(':')
99
+ text += `
100
+ this.value = Alpine.store('${store}').${key}
101
+ this.instance.on('change', value => {
102
+ this.value = _.cloneDeep(value)
103
+ Alpine.store('${store}').${key} = this.value
104
+ })
105
+ `
106
+ }
126
107
  if (group.remote) {
127
- this.params.attr['@load.window'] += `
128
- if (!_.isEmpty(val)) {
129
- fetch('${group.remote.url}?query=${group.remote.valueField}:' + val, ${jsonStringify(fetchOpts, true)})
108
+ text += `
109
+ if (!_.isEmpty(this.value)) {
110
+ if (!_.isArray(this.value)) this.value = [this.value]
111
+ let query = '${group.remote.valueField}:['
112
+ let q = ''
113
+ for (const v of this.value) {
114
+ q += ',' + v
115
+ }
116
+ query += q.slice(1) + ']'
117
+ fetch('${group.remote.url}?query=' + query, ${jsonStringify(fetchOpts, true)})
130
118
  .then(resp => resp.json())
131
119
  .then(json => {
132
120
  if (json.data.length === 0) return
133
- const opt = _.pick(json.data[0], ['${group.remote.valueField}', '${group.remote.labelField}'])
134
- instance.addOption(opt)
135
- instance.setValue(opt.${group.remote.valueField})
121
+ for (const d of json.data) {
122
+ const opt = _.pick(d, ['${group.remote.valueField}', '${group.remote.labelField}'])
123
+ this.instance.addOption(opt)
124
+ }
125
+ this.instance.setValue(json.data.map(item => item.${group.remote.valueField}))
136
126
  })
137
127
  }
138
128
  `
139
129
  }
130
+ text += '}'
131
+ xData.push(text)
132
+ this.params.attr['x-data'] = `{ ${xData.join(',\n')} }`
133
+ // this.params.attr['@load.window'] = 'onLoad()'
134
+ this.params.attr['x-init'] = 'onLoad()'
140
135
  this.params.attr.options = options
141
136
  this.params.attr = omit(this.params.attr, ['noDropdownInput', 'removeBtn', 'clearBtn', 'c-opts', 'remoteUrl', 'remoteSearchField', 'remoteLabelField', 'remoteValueField'])
142
137
  await build.call(this, buildFormSelect, this.params)
@@ -10,7 +10,7 @@ async function form () {
10
10
  this.component.locals.form = this.component.locals.form ?? {}
11
11
  const { pascalCase } = this.app.lib.aneka
12
12
  const { isEmpty, omit, has } = this.app.lib._
13
- const { attrToArray } = this.app.waibuMpa
13
+ const { attrToArray } = this.app.waibu
14
14
  const { groupAttrs } = this.app.waibuMpa
15
15
  if (!has(this.params.attr, 'autocomplete')) this.params.attr.autocomplete = 'off'
16
16
  if (!has(this.params.attr, 'method')) this.params.attr.method = 'POST'
@@ -22,7 +22,7 @@ async function gridCol () {
22
22
 
23
23
  build = async () => {
24
24
  const { map, without, isString } = this.app.lib._
25
- const { attrToArray } = this.app.waibuMpa
25
+ const { attrToArray } = this.app.waibu
26
26
  if (this.params.attr.break) {
27
27
  const ext = breakpoints.includes(this.params.attr.break) ? `d-none d-${this.params.attr.break}-block` : ''
28
28
  this.params.attr.class.push('w-100', ext)
@@ -0,0 +1,34 @@
1
+ .ts-dropdown {
2
+ min-width: 242px;
3
+ }
4
+ .ts-control {
5
+ padding-top: 0px;
6
+ }
7
+ .ts-wrapper {
8
+ margin-left: calc(var(--bs-border-width) * -1) !important;
9
+ border-top-left-radius: var(--bs-border-radius) !important;
10
+ border-bottom-left-radius: var(--bs-border-radius) !important;
11
+ white-space: nowrap !important;
12
+ }
13
+ .ts-control, .ts-control input, .ts-dropdown {
14
+ color: inherit;
15
+ }
16
+ .form-floating > .ts-wrapper {
17
+ padding-top: 1.625rem !important;
18
+ }
19
+
20
+ .ts-wrapper.focus {
21
+ border-color: #86b7fe;
22
+ outline: 0;
23
+ box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
24
+ }
25
+
26
+ .focus .ts-control {
27
+ box-shadow: none;
28
+ border: none;
29
+ box-shadow: none;
30
+ }
31
+
32
+ .form-select.no-caret {
33
+ --bs-form-select-bg-img: none !important;
34
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waibu-bootstrap",
3
- "version": "2.2.3",
3
+ "version": "2.4.0",
4
4
  "description": "Bootstrap suport for Waibu Framework",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/wiki/CHANGES.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changes
2
2
 
3
+ ## 2026-02-18
4
+
5
+ - [2.4.0] Update attribute functions from ```waibu```
6
+ - [2.4.0] Bug fix on ```FormSelectExt``` 's static css
7
+ - [2.4.0] Bug fix on ```FormSelectCountry``` 's static css
8
+
9
+ ## 2026-02-15
10
+
11
+ - [2.3.0] Add capability to use custom tag on ```select```
12
+ - [2.3.0] Now you can hide caret on ```<c:form-select-ext />``` with ```no-caret``` attribute
13
+ - [2.3.0] Add ```value-store="name:key"``` to read/write value to Alpine's store
14
+
3
15
  ## 2026-02-11
4
16
 
5
17
  - [2.2.3] Bug fix on widget attribute ```rounded```