@threadlabs/looma 0.28.4 → 0.28.6
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.
- package/components/ui-editor-image-toolbar/ui-editor-image-toolbar.html +96 -0
- package/components/ui-editor-image-toolbar/ui-editor-image-toolbar.js +56 -0
- package/components/ui-editor-mention-menu-item/ui-editor-mention-menu-item.html +3 -2
- package/dist/index.js +34 -33
- package/package.json +3 -2
- package/styles/ui-action-bar.css +13 -6
- package/styles/ui-affordance-scope.css +12 -5
- package/styles/ui-avatar-group.css +17 -10
- package/styles/ui-avatar.css +23 -16
- package/styles/ui-badge.css +78 -71
- package/styles/ui-breadcrumb-item.css +12 -5
- package/styles/ui-breadcrumbs.css +10 -3
- package/styles/ui-button.css +79 -72
- package/styles/ui-callout.css +29 -22
- package/styles/ui-card.css +25 -18
- package/styles/ui-checkbox.css +45 -37
- package/styles/ui-cluster.css +34 -27
- package/styles/ui-combobox.css +46 -39
- package/styles/ui-container.css +20 -13
- package/styles/ui-context-menu.css +17 -10
- package/styles/ui-cover.css +12 -5
- package/styles/ui-description-item.css +10 -3
- package/styles/ui-description-list.css +20 -13
- package/styles/ui-dialog.css +44 -35
- package/styles/ui-disclosure.css +32 -25
- package/styles/ui-editable.css +15 -8
- package/styles/ui-editor-image-toolbar.css +1 -0
- package/styles/ui-editor-insert-table-grid.css +11 -4
- package/styles/ui-editor-mention-menu-item.css +17 -8
- package/styles/ui-editor-mention-menu.css +14 -7
- package/styles/ui-editor-slash-menu-group.css +10 -3
- package/styles/ui-editor-slash-menu-item.css +15 -8
- package/styles/ui-editor-slash-menu.css +15 -8
- package/styles/ui-editor-surface.css +10 -3
- package/styles/ui-editor-table-context-menu.css +17 -10
- package/styles/ui-editor-table-overlay.css +17 -10
- package/styles/ui-editor-table-toolbar.css +22 -15
- package/styles/ui-editor-toolbar.css +18 -11
- package/styles/ui-floating-action-button.css +21 -14
- package/styles/ui-form-field.css +20 -13
- package/styles/ui-grid.css +21 -14
- package/styles/ui-icon-button.css +80 -73
- package/styles/ui-icon.css +14 -7
- package/styles/ui-image.css +25 -18
- package/styles/ui-input-group.css +34 -27
- package/styles/ui-input.css +42 -35
- package/styles/ui-list-item.css +33 -26
- package/styles/ui-list.css +22 -15
- package/styles/ui-listbox.css +22 -14
- package/styles/ui-menu-group.css +10 -3
- package/styles/ui-menu-item.css +29 -22
- package/styles/ui-menu.css +25 -18
- package/styles/ui-meter.css +36 -29
- package/styles/ui-nav-item.css +26 -19
- package/styles/ui-page-header.css +12 -5
- package/styles/ui-popover.css +27 -20
- package/styles/ui-radio-group.css +14 -6
- package/styles/ui-radio.css +24 -17
- package/styles/ui-reel.css +28 -21
- package/styles/ui-scroll-area.css +22 -15
- package/styles/ui-search-result-row.css +28 -21
- package/styles/ui-search-shell.css +24 -17
- package/styles/ui-section.css +14 -7
- package/styles/ui-select.css +33 -26
- package/styles/ui-separator.css +17 -10
- package/styles/ui-sidebar.css +35 -28
- package/styles/ui-spinner.css +32 -23
- package/styles/ui-stack.css +29 -21
- package/styles/ui-status-message.css +14 -7
- package/styles/ui-switch.css +29 -22
- package/styles/ui-switcher.css +27 -20
- package/styles/ui-table.css +14 -7
- package/styles/ui-tabs.css +24 -17
- package/styles/ui-text.css +35 -28
- package/styles/ui-textarea.css +37 -30
- package/styles/ui-toast-region.css +74 -63
- package/styles/ui-toast.css +21 -14
- package/styles/ui-tooltip.css +38 -31
- package/styles/ui-top-bar.css +19 -12
- package/styles/ui-tree-item.css +79 -71
- package/styles/ui-tree.css +18 -11
- package/vanilla/UiActionBar.d.ts +1 -1
- package/vanilla/UiActionBar.js +1 -1
- package/vanilla/UiAffordanceScope.d.ts +1 -1
- package/vanilla/UiAffordanceScope.js +1 -1
- package/vanilla/UiAvatar.d.ts +1 -1
- package/vanilla/UiAvatar.js +1 -1
- package/vanilla/UiAvatarGroup.d.ts +1 -1
- package/vanilla/UiAvatarGroup.js +1 -1
- package/vanilla/UiBadge.d.ts +1 -1
- package/vanilla/UiBadge.js +1 -1
- package/vanilla/UiBreadcrumbItem.d.ts +1 -1
- package/vanilla/UiBreadcrumbItem.js +1 -1
- package/vanilla/UiBreadcrumbs.d.ts +1 -1
- package/vanilla/UiBreadcrumbs.js +1 -1
- package/vanilla/UiButton.d.ts +1 -1
- package/vanilla/UiButton.js +19 -57
- package/vanilla/UiCallout.d.ts +1 -1
- package/vanilla/UiCallout.js +25 -73
- package/vanilla/UiCard.d.ts +1 -1
- package/vanilla/UiCard.js +1 -1
- package/vanilla/UiCheckbox.d.ts +1 -1
- package/vanilla/UiCheckbox.js +1 -1
- package/vanilla/UiCluster.d.ts +1 -1
- package/vanilla/UiCluster.js +1 -1
- package/vanilla/UiCombobox.d.ts +1 -1
- package/vanilla/UiCombobox.js +60 -166
- package/vanilla/UiContainer.d.ts +1 -1
- package/vanilla/UiContainer.js +1 -1
- package/vanilla/UiContextMenu.d.ts +1 -1
- package/vanilla/UiContextMenu.js +1 -1
- package/vanilla/UiCover.d.ts +1 -1
- package/vanilla/UiCover.js +1 -1
- package/vanilla/UiDescriptionItem.d.ts +1 -1
- package/vanilla/UiDescriptionItem.js +1 -1
- package/vanilla/UiDescriptionList.d.ts +1 -1
- package/vanilla/UiDescriptionList.js +1 -1
- package/vanilla/UiDialog.d.ts +1 -1
- package/vanilla/UiDialog.js +4 -10
- package/vanilla/UiDisclosure.d.ts +1 -1
- package/vanilla/UiDisclosure.js +1 -1
- package/vanilla/UiEditable.d.ts +1 -1
- package/vanilla/UiEditable.js +7 -19
- package/vanilla/UiEditorImageToolbar.d.ts +19 -0
- package/vanilla/UiEditorImageToolbar.js +171 -0
- package/vanilla/UiEditorInsertTableGrid.d.ts +1 -1
- package/vanilla/UiEditorInsertTableGrid.js +1 -1
- package/vanilla/UiEditorMentionMenu.d.ts +1 -1
- package/vanilla/UiEditorMentionMenu.js +17 -37
- package/vanilla/UiEditorMentionMenuItem.d.ts +1 -1
- package/vanilla/UiEditorMentionMenuItem.js +4 -10
- package/vanilla/UiEditorSlashMenu.d.ts +1 -1
- package/vanilla/UiEditorSlashMenu.js +5 -4
- package/vanilla/UiEditorSlashMenuGroup.d.ts +1 -1
- package/vanilla/UiEditorSlashMenuGroup.js +1 -1
- package/vanilla/UiEditorSlashMenuItem.d.ts +1 -1
- package/vanilla/UiEditorSlashMenuItem.js +7 -19
- package/vanilla/UiEditorSurface.d.ts +1 -1
- package/vanilla/UiEditorSurface.js +1 -1
- package/vanilla/UiEditorTableContextMenu.d.ts +1 -1
- package/vanilla/UiEditorTableContextMenu.js +6 -14
- package/vanilla/UiEditorTableOverlay.d.ts +1 -1
- package/vanilla/UiEditorTableOverlay.js +9 -25
- package/vanilla/UiEditorTableToolbar.d.ts +1 -1
- package/vanilla/UiEditorTableToolbar.js +25 -77
- package/vanilla/UiEditorToolbar.d.ts +1 -1
- package/vanilla/UiEditorToolbar.js +1 -1
- package/vanilla/UiFloatingActionButton.d.ts +1 -1
- package/vanilla/UiFloatingActionButton.js +1 -1
- package/vanilla/UiFormField.d.ts +1 -1
- package/vanilla/UiFormField.js +1 -1
- package/vanilla/UiGrid.d.ts +1 -1
- package/vanilla/UiGrid.js +1 -1
- package/vanilla/UiIcon.d.ts +1 -1
- package/vanilla/UiIcon.js +15 -47
- package/vanilla/UiIconButton.d.ts +1 -1
- package/vanilla/UiIconButton.js +4 -10
- package/vanilla/UiImage.d.ts +1 -1
- package/vanilla/UiImage.js +1 -1
- package/vanilla/UiInput.d.ts +1 -1
- package/vanilla/UiInput.js +1 -1
- package/vanilla/UiInputGroup.d.ts +1 -1
- package/vanilla/UiInputGroup.js +1 -1
- package/vanilla/UiList.d.ts +1 -1
- package/vanilla/UiList.js +1 -1
- package/vanilla/UiListItem.d.ts +1 -1
- package/vanilla/UiListItem.js +1 -1
- package/vanilla/UiListbox.d.ts +1 -1
- package/vanilla/UiListbox.js +4 -10
- package/vanilla/UiMenu.d.ts +1 -1
- package/vanilla/UiMenu.js +1 -1
- package/vanilla/UiMenuGroup.d.ts +1 -1
- package/vanilla/UiMenuGroup.js +4 -10
- package/vanilla/UiMenuItem.d.ts +1 -1
- package/vanilla/UiMenuItem.js +4 -10
- package/vanilla/UiMeter.d.ts +1 -1
- package/vanilla/UiMeter.js +13 -33
- package/vanilla/UiNavItem.d.ts +1 -1
- package/vanilla/UiNavItem.js +1 -1
- package/vanilla/UiPageHeader.d.ts +1 -1
- package/vanilla/UiPageHeader.js +1 -1
- package/vanilla/UiPopover.d.ts +1 -1
- package/vanilla/UiPopover.js +1 -1
- package/vanilla/UiRadio.d.ts +1 -1
- package/vanilla/UiRadio.js +1 -1
- package/vanilla/UiRadioGroup.d.ts +1 -1
- package/vanilla/UiRadioGroup.js +1 -1
- package/vanilla/UiReel.d.ts +1 -1
- package/vanilla/UiReel.js +1 -1
- package/vanilla/UiScrollArea.d.ts +1 -1
- package/vanilla/UiScrollArea.js +1 -1
- package/vanilla/UiSearchResultRow.d.ts +1 -1
- package/vanilla/UiSearchResultRow.js +1 -1
- package/vanilla/UiSearchShell.d.ts +1 -1
- package/vanilla/UiSearchShell.js +1 -1
- package/vanilla/UiSection.d.ts +1 -1
- package/vanilla/UiSection.js +1 -1
- package/vanilla/UiSelect.d.ts +1 -1
- package/vanilla/UiSelect.js +1 -1
- package/vanilla/UiSeparator.d.ts +1 -1
- package/vanilla/UiSeparator.js +1 -1
- package/vanilla/UiSidebar.d.ts +1 -1
- package/vanilla/UiSidebar.js +1 -1
- package/vanilla/UiSpinner.d.ts +1 -1
- package/vanilla/UiSpinner.js +1 -1
- package/vanilla/UiStack.d.ts +1 -1
- package/vanilla/UiStack.js +1 -1
- package/vanilla/UiStatusMessage.d.ts +1 -1
- package/vanilla/UiStatusMessage.js +1 -1
- package/vanilla/UiSwitch.d.ts +1 -1
- package/vanilla/UiSwitch.js +1 -1
- package/vanilla/UiSwitcher.d.ts +1 -1
- package/vanilla/UiSwitcher.js +1 -1
- package/vanilla/UiTable.d.ts +1 -1
- package/vanilla/UiTable.js +1 -1
- package/vanilla/UiTabs.d.ts +1 -1
- package/vanilla/UiTabs.js +1 -1
- package/vanilla/UiText.d.ts +1 -1
- package/vanilla/UiText.js +1 -1
- package/vanilla/UiTextarea.d.ts +1 -1
- package/vanilla/UiTextarea.js +1 -1
- package/vanilla/UiToast.d.ts +1 -1
- package/vanilla/UiToast.js +1 -1
- package/vanilla/UiToastRegion.d.ts +1 -1
- package/vanilla/UiToastRegion.js +1 -1
- package/vanilla/UiTooltip.d.ts +1 -1
- package/vanilla/UiTooltip.js +1 -1
- package/vanilla/UiTopBar.d.ts +1 -1
- package/vanilla/UiTopBar.js +1 -1
- package/vanilla/UiTree.d.ts +1 -1
- package/vanilla/UiTree.js +1 -1
- package/vanilla/UiTreeItem.d.ts +1 -1
- package/vanilla/UiTreeItem.js +7 -19
- package/vanilla/index.js +1 -0
- package/vue/UiActionBar.vue +46 -78
- package/vue/UiAffordanceScope.vue +27 -66
- package/vue/UiAvatar.d.ts +10 -7
- package/vue/UiAvatar.vue +100 -218
- package/vue/UiAvatarGroup.d.ts +10 -7
- package/vue/UiAvatarGroup.vue +78 -160
- package/vue/UiBadge.d.ts +1 -1
- package/vue/UiBadge.vue +483 -520
- package/vue/UiBreadcrumbItem.vue +52 -123
- package/vue/UiBreadcrumbs.vue +27 -70
- package/vue/UiButton.d.ts +2 -2
- package/vue/UiButton.vue +420 -471
- package/vue/UiCallout.vue +131 -175
- package/vue/UiCard.d.ts +1 -1
- package/vue/UiCard.vue +112 -151
- package/vue/UiCheckbox.d.ts +13 -10
- package/vue/UiCheckbox.vue +160 -221
- package/vue/UiCluster.d.ts +1 -1
- package/vue/UiCluster.vue +84 -128
- package/vue/UiCombobox.d.ts +71 -68
- package/vue/UiCombobox.vue +417 -743
- package/vue/UiContainer.vue +43 -87
- package/vue/UiContextMenu.d.ts +6 -6
- package/vue/UiContextMenu.vue +78 -117
- package/vue/UiCover.vue +29 -69
- package/vue/UiDescriptionItem.vue +39 -82
- package/vue/UiDescriptionList.vue +64 -107
- package/vue/UiDialog.d.ts +22 -19
- package/vue/UiDialog.vue +193 -246
- package/vue/UiDisclosure.d.ts +22 -19
- package/vue/UiDisclosure.vue +192 -283
- package/vue/UiEditable.d.ts +5 -5
- package/vue/UiEditable.vue +124 -207
- package/vue/UiEditorImageToolbar.d.ts +54 -0
- package/vue/UiEditorImageToolbar.js +2 -0
- package/vue/UiEditorImageToolbar.vue +415 -0
- package/vue/UiEditorInsertTableGrid.d.ts +2 -2
- package/vue/UiEditorInsertTableGrid.vue +98 -266
- package/vue/UiEditorMentionMenu.d.ts +15 -12
- package/vue/UiEditorMentionMenu.vue +150 -239
- package/vue/UiEditorMentionMenuItem.vue +66 -110
- package/vue/UiEditorSlashMenu.d.ts +16 -13
- package/vue/UiEditorSlashMenu.vue +172 -253
- package/vue/UiEditorSlashMenuGroup.vue +25 -69
- package/vue/UiEditorSlashMenuItem.vue +67 -115
- package/vue/UiEditorSurface.vue +19 -63
- package/vue/UiEditorTableContextMenu.vue +135 -269
- package/vue/UiEditorTableOverlay.d.ts +2 -2
- package/vue/UiEditorTableOverlay.vue +310 -479
- package/vue/UiEditorTableToolbar.d.ts +2 -2
- package/vue/UiEditorTableToolbar.vue +182 -357
- package/vue/UiEditorToolbar.vue +143 -183
- package/vue/UiFloatingActionButton.vue +70 -114
- package/vue/UiFormField.d.ts +1 -1
- package/vue/UiFormField.vue +76 -117
- package/vue/UiGrid.d.ts +1 -1
- package/vue/UiGrid.vue +65 -103
- package/vue/UiIcon.vue +32 -106
- package/vue/UiIconButton.d.ts +2 -2
- package/vue/UiIconButton.vue +359 -411
- package/vue/UiImage.vue +145 -180
- package/vue/UiInput.vue +158 -190
- package/vue/UiInputGroup.vue +199 -239
- package/vue/UiList.vue +67 -109
- package/vue/UiListItem.vue +154 -201
- package/vue/UiListbox.d.ts +10 -7
- package/vue/UiListbox.vue +86 -260
- package/vue/UiMenu.d.ts +11 -11
- package/vue/UiMenu.vue +118 -152
- package/vue/UiMenuGroup.vue +21 -67
- package/vue/UiMenuItem.d.ts +19 -16
- package/vue/UiMenuItem.vue +169 -262
- package/vue/UiMeter.d.ts +4 -4
- package/vue/UiMeter.vue +166 -680
- package/vue/UiNavItem.vue +140 -213
- package/vue/UiPageHeader.vue +81 -127
- package/vue/UiPopover.d.ts +7 -7
- package/vue/UiPopover.vue +90 -129
- package/vue/UiRadio.d.ts +12 -9
- package/vue/UiRadio.vue +91 -149
- package/vue/UiRadioGroup.d.ts +2 -2
- package/vue/UiRadioGroup.vue +47 -85
- package/vue/UiReel.vue +89 -130
- package/vue/UiScrollArea.d.ts +1 -1
- package/vue/UiScrollArea.vue +78 -119
- package/vue/UiSearchResultRow.vue +96 -149
- package/vue/UiSearchShell.d.ts +2 -2
- package/vue/UiSearchShell.vue +159 -196
- package/vue/UiSection.vue +105 -149
- package/vue/UiSelect.d.ts +1 -1
- package/vue/UiSelect.vue +114 -146
- package/vue/UiSeparator.vue +42 -85
- package/vue/UiSidebar.d.ts +10 -7
- package/vue/UiSidebar.vue +193 -260
- package/vue/UiSpinner.vue +54 -130
- package/vue/UiStack.vue +66 -107
- package/vue/UiStatusMessage.d.ts +1 -1
- package/vue/UiStatusMessage.vue +61 -135
- package/vue/UiSwitch.d.ts +12 -9
- package/vue/UiSwitch.vue +136 -194
- package/vue/UiSwitcher.vue +69 -107
- package/vue/UiTable.vue +121 -156
- package/vue/UiTabs.d.ts +1 -1
- package/vue/UiTabs.vue +139 -267
- package/vue/UiText.d.ts +4 -4
- package/vue/UiText.vue +145 -184
- package/vue/UiTextarea.d.ts +2 -2
- package/vue/UiTextarea.vue +162 -194
- package/vue/UiToast.vue +70 -118
- package/vue/UiToastRegion.vue +157 -276
- package/vue/UiTooltip.d.ts +21 -18
- package/vue/UiTooltip.vue +174 -234
- package/vue/UiTopBar.vue +48 -76
- package/vue/UiTree.vue +46 -79
- package/vue/UiTreeItem.d.ts +18 -15
- package/vue/UiTreeItem.vue +526 -668
- package/vue/chunks/UiActionBar.js +5 -17
- package/vue/chunks/UiAffordanceScope.js +7 -26
- package/vue/chunks/UiAvatar.js +20 -74
- package/vue/chunks/UiAvatarGroup.js +14 -56
- package/vue/chunks/UiBadge.js +10 -28
- package/vue/chunks/UiBreadcrumbItem.js +11 -41
- package/vue/chunks/UiBreadcrumbs.js +7 -25
- package/vue/chunks/UiButton.js +80 -103
- package/vue/chunks/UiCallout.js +9 -27
- package/vue/chunks/UiCard.js +33 -53
- package/vue/chunks/UiCheckbox.js +14 -46
- package/vue/chunks/UiCluster.js +13 -31
- package/vue/chunks/UiCombobox.js +99 -237
- package/vue/chunks/UiContainer.js +9 -27
- package/vue/chunks/UiContextMenu.js +7 -26
- package/vue/chunks/UiCover.js +7 -25
- package/vue/chunks/UiDescriptionItem.js +6 -24
- package/vue/chunks/UiDescriptionList.js +9 -27
- package/vue/chunks/UiDialog.js +18 -47
- package/vue/chunks/UiDisclosure.js +19 -66
- package/vue/chunks/UiEditable.js +18 -60
- package/vue/chunks/UiEditorImageToolbar.js +515 -0
- package/vue/chunks/UiEditorInsertTableGrid.js +14 -112
- package/vue/chunks/UiEditorMentionMenu.js +16 -54
- package/vue/chunks/UiEditorMentionMenuItem.js +10 -28
- package/vue/chunks/UiEditorSlashMenu.js +17 -46
- package/vue/chunks/UiEditorSlashMenuGroup.js +7 -25
- package/vue/chunks/UiEditorSlashMenuItem.js +16 -31
- package/vue/chunks/UiEditorSurface.js +7 -25
- package/vue/chunks/UiEditorTableContextMenu.js +25 -102
- package/vue/chunks/UiEditorTableOverlay.js +41 -104
- package/vue/chunks/UiEditorTableToolbar.js +44 -122
- package/vue/chunks/UiEditorToolbar.js +7 -26
- package/vue/chunks/UiFloatingActionButton.js +10 -28
- package/vue/chunks/UiFormField.js +8 -27
- package/vue/chunks/UiGrid.js +10 -28
- package/vue/chunks/UiIcon.js +21 -74
- package/vue/chunks/UiIconButton.js +18 -50
- package/vue/chunks/UiImage.js +14 -33
- package/vue/chunks/UiInput.js +12 -31
- package/vue/chunks/UiInputGroup.js +10 -29
- package/vue/chunks/UiList.js +17 -37
- package/vue/chunks/UiListItem.js +12 -30
- package/vue/chunks/UiListbox.js +22 -124
- package/vue/chunks/UiMenu.js +13 -32
- package/vue/chunks/UiMenuGroup.js +8 -26
- package/vue/chunks/UiMenuItem.js +35 -80
- package/vue/chunks/UiMeter.js +17 -351
- package/vue/chunks/UiNavItem.js +23 -57
- package/vue/chunks/UiPageHeader.js +7 -25
- package/vue/chunks/UiPopover.js +9 -28
- package/vue/chunks/UiRadio.js +11 -43
- package/vue/chunks/UiRadioGroup.js +13 -32
- package/vue/chunks/UiReel.js +10 -28
- package/vue/chunks/UiScrollArea.js +10 -29
- package/vue/chunks/UiSearchResultRow.js +10 -28
- package/vue/chunks/UiSearchShell.js +9 -28
- package/vue/chunks/UiSection.js +9 -27
- package/vue/chunks/UiSelect.js +10 -29
- package/vue/chunks/UiSeparator.js +10 -28
- package/vue/chunks/UiSidebar.js +15 -47
- package/vue/chunks/UiSpinner.js +8 -44
- package/vue/chunks/UiStack.js +12 -30
- package/vue/chunks/UiStatusMessage.js +10 -42
- package/vue/chunks/UiSwitch.js +11 -43
- package/vue/chunks/UiSwitcher.js +9 -28
- package/vue/chunks/UiTable.js +7 -26
- package/vue/chunks/UiTabs.js +15 -91
- package/vue/chunks/UiText.js +148 -168
- package/vue/chunks/UiTextarea.js +16 -35
- package/vue/chunks/UiToast.js +13 -37
- package/vue/chunks/UiToastRegion.js +16 -88
- package/vue/chunks/UiTooltip.js +14 -46
- package/vue/chunks/UiTopBar.js +5 -17
- package/vue/chunks/UiTree.js +13 -32
- package/vue/chunks/UiTreeItem.js +34 -90
- package/vue/chunks/_plugin-vue_export-helper.js +904 -1
- package/vue/chunks/control.js +3 -0
- package/vue/chunks/props.js +1 -1
- package/vue/components.css +7902 -7830
- package/vue/editor/index.js +42 -171
- package/vue/editor/primitives.d.ts +9 -1
- package/vue/host.ts +999 -14
- package/vue/index.d.ts +1 -0
- package/vue/index.js +9 -8
- package/vue/chunks/host.js +0 -443
- package/vue/editor/image-controls.d.ts +0 -19
package/vue/host.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
// Generated by HTML Next 1.0.0-alpha.
|
|
1
|
+
// Generated by HTML Next 1.0.0-alpha.51 for Vue 3.5. Do not edit.
|
|
2
2
|
import {
|
|
3
3
|
computed,
|
|
4
|
+
createTextVNode,
|
|
5
|
+
defineComponent,
|
|
4
6
|
Fragment,
|
|
5
7
|
getCurrentInstance,
|
|
8
|
+
h,
|
|
9
|
+
inject,
|
|
6
10
|
onBeforeUnmount,
|
|
7
11
|
onBeforeUpdate,
|
|
8
12
|
onMounted,
|
|
@@ -12,6 +16,7 @@ import {
|
|
|
12
16
|
watch,
|
|
13
17
|
watchEffect,
|
|
14
18
|
} from 'vue'
|
|
19
|
+
import type { PropType, VNode } from 'vue'
|
|
15
20
|
|
|
16
21
|
interface ControllerStateAccess {
|
|
17
22
|
readonly get: () => unknown
|
|
@@ -22,7 +27,10 @@ interface ControllerNamespaces {
|
|
|
22
27
|
readonly computed: Readonly<Record<string, () => unknown>>
|
|
23
28
|
readonly data?: Readonly<Record<string, () => unknown>>
|
|
24
29
|
readonly acceptsState?: (name: string, keys: readonly string[], value: unknown) => boolean
|
|
25
|
-
|
|
30
|
+
/** A read below a state root, by its path from the root. */
|
|
31
|
+
readonly read?: (name: string, keys: readonly string[]) => void
|
|
32
|
+
/** A write below a state root changed the value at this path from the root. */
|
|
33
|
+
readonly changed?: (name: string, keys: readonly string[]) => void
|
|
26
34
|
}
|
|
27
35
|
|
|
28
36
|
function controllerNamespaces(options: ControllerNamespaces, source: string): {
|
|
@@ -67,6 +75,10 @@ function controllerNamespaces(options: ControllerNamespaces, source: string): {
|
|
|
67
75
|
} else apply()
|
|
68
76
|
return true
|
|
69
77
|
}
|
|
78
|
+
// Each proxy's own object: a value read through host.state and written back is the same value.
|
|
79
|
+
const plain = new WeakMap<object, unknown>()
|
|
80
|
+
const unwrap = (value: unknown): unknown =>
|
|
81
|
+
typeof value === 'object' && value !== null && plain.has(value) ? plain.get(value) : value
|
|
70
82
|
const wrap = (
|
|
71
83
|
value: unknown,
|
|
72
84
|
name: string,
|
|
@@ -85,12 +97,24 @@ function controllerNamespaces(options: ControllerNamespaces, source: string): {
|
|
|
85
97
|
: value
|
|
86
98
|
if (readonly && Array.isArray(value)) surface.length = value.length
|
|
87
99
|
const proxy = new Proxy(surface, {
|
|
88
|
-
get: (_target, key) =>
|
|
89
|
-
|
|
90
|
-
|
|
100
|
+
get: (_target, key) => {
|
|
101
|
+
if (!readonly) options.read?.(name, [...keys, String(key)])
|
|
102
|
+
return wrap(Reflect.get(value, key), name, [...keys, String(key)], readonly)
|
|
103
|
+
},
|
|
104
|
+
set: (_target, key, written) => {
|
|
105
|
+
const next = unwrap(written)
|
|
106
|
+
return write(name, [...keys, String(key)], next, readonly, () => {
|
|
91
107
|
const previous = Reflect.get(value, key)
|
|
92
|
-
|
|
93
|
-
|
|
108
|
+
const length = Array.isArray(value) ? value.length : undefined
|
|
109
|
+
if (Reflect.set(value, key, next) && !Object.is(previous, next)) {
|
|
110
|
+
options.changed?.(name, [...keys, String(key)])
|
|
111
|
+
}
|
|
112
|
+
// Setting an index past the end extends the list before any write of its length.
|
|
113
|
+
if (length !== undefined && length !== (value as unknown[]).length && key !== 'length') {
|
|
114
|
+
options.changed?.(name, [...keys, 'length'])
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
},
|
|
94
118
|
has: (_target, key) => Reflect.has(value, key),
|
|
95
119
|
ownKeys: () => Reflect.ownKeys(value),
|
|
96
120
|
getOwnPropertyDescriptor: (_target, key) => {
|
|
@@ -107,7 +131,9 @@ function controllerNamespaces(options: ControllerNamespaces, source: string): {
|
|
|
107
131
|
},
|
|
108
132
|
deleteProperty: (_target, key) =>
|
|
109
133
|
write(name, [...keys, String(key)], undefined, readonly, () => {
|
|
110
|
-
if (Reflect.has(value, key) && Reflect.deleteProperty(value, key))
|
|
134
|
+
if (Reflect.has(value, key) && Reflect.deleteProperty(value, key)) {
|
|
135
|
+
options.changed?.(name, [...keys, String(key)])
|
|
136
|
+
}
|
|
111
137
|
}),
|
|
112
138
|
defineProperty: (_target, key, descriptor) => {
|
|
113
139
|
if (
|
|
@@ -118,11 +144,12 @@ function controllerNamespaces(options: ControllerNamespaces, source: string): {
|
|
|
118
144
|
return false
|
|
119
145
|
}
|
|
120
146
|
const changed = Reflect.defineProperty(value, key, descriptor)
|
|
121
|
-
if (changed) options.changed?.(name)
|
|
147
|
+
if (changed) options.changed?.(name, [...keys, String(key)])
|
|
122
148
|
return changed
|
|
123
149
|
},
|
|
124
150
|
})
|
|
125
151
|
paths.set(path, proxy)
|
|
152
|
+
plain.set(proxy, value)
|
|
126
153
|
return proxy
|
|
127
154
|
}
|
|
128
155
|
const state = new Proxy({} as Record<string, unknown>, {
|
|
@@ -140,9 +167,9 @@ function controllerNamespaces(options: ControllerNamespaces, source: string): {
|
|
|
140
167
|
write(
|
|
141
168
|
String(name),
|
|
142
169
|
[],
|
|
143
|
-
value,
|
|
170
|
+
unwrap(value),
|
|
144
171
|
typeof name !== 'string' || !Object.hasOwn(options.state, name),
|
|
145
|
-
() => options.state[String(name)]!.set(value),
|
|
172
|
+
() => options.state[String(name)]!.set(unwrap(value)),
|
|
146
173
|
),
|
|
147
174
|
deleteProperty: (_target, name) => {
|
|
148
175
|
warn(String(name), true)
|
|
@@ -220,6 +247,15 @@ function dataURL(
|
|
|
220
247
|
return url.href
|
|
221
248
|
}
|
|
222
249
|
|
|
250
|
+
/** What the from-parameters send, as dataURL writes them: an equal key requests nothing new. */
|
|
251
|
+
function dataKey(values: readonly unknown[]): string {
|
|
252
|
+
return JSON.stringify(
|
|
253
|
+
values.map((value) =>
|
|
254
|
+
value == null ? null : Array.isArray(value) ? value.map(String) : String(value)
|
|
255
|
+
),
|
|
256
|
+
)
|
|
257
|
+
}
|
|
258
|
+
|
|
223
259
|
/** A Vue-owned declared read; no request is made during SSR. */
|
|
224
260
|
export function useDataRead(state: { value: any }, options: DataReadOptions): void {
|
|
225
261
|
let value: unknown = null
|
|
@@ -281,8 +317,13 @@ export function useDataRead(state: { value: any }, options: DataReadOptions): vo
|
|
|
281
317
|
}
|
|
282
318
|
}
|
|
283
319
|
}
|
|
320
|
+
let requested: string | undefined
|
|
284
321
|
const update = (sources: readonly unknown[]): void => {
|
|
285
322
|
if (sources.includes(Symbol.for('html-next.invalid-result'))) return
|
|
323
|
+
// A deep watch fires on any write it reaches; only changed parameters request again.
|
|
324
|
+
const key = dataKey(sources)
|
|
325
|
+
if (connected && key === requested) return
|
|
326
|
+
requested = key
|
|
286
327
|
connected = true
|
|
287
328
|
cancel()
|
|
288
329
|
const current = ++generation
|
|
@@ -344,6 +385,36 @@ function observeConnection(element: Element, check: () => void): () => void {
|
|
|
344
385
|
}
|
|
345
386
|
}
|
|
346
387
|
|
|
388
|
+
/**
|
|
389
|
+
* Runs the root's on:connect and on:disconnect handlers each time it joins and leaves a document.
|
|
390
|
+
* Vue hooks cover mount and unmount; the shared native observer sees the root detached and
|
|
391
|
+
* reinserted outside Vue.
|
|
392
|
+
*/
|
|
393
|
+
export function useLifecycle(
|
|
394
|
+
root: Readable<Element | null>,
|
|
395
|
+
connect?: () => void,
|
|
396
|
+
disconnect?: () => void,
|
|
397
|
+
): void {
|
|
398
|
+
let connected = false
|
|
399
|
+
let stop: (() => void) | undefined
|
|
400
|
+
const synchronize = (): void => {
|
|
401
|
+
if ((root.value?.isConnected === true) === connected) return
|
|
402
|
+
connected = !connected
|
|
403
|
+
;(connected ? connect : disconnect)?.()
|
|
404
|
+
}
|
|
405
|
+
onMounted(() => {
|
|
406
|
+
if (root.value !== null) stop = observeConnection(root.value, synchronize)
|
|
407
|
+
synchronize()
|
|
408
|
+
})
|
|
409
|
+
onBeforeUnmount(() => {
|
|
410
|
+
stop?.()
|
|
411
|
+
if (connected) {
|
|
412
|
+
connected = false
|
|
413
|
+
disconnect?.()
|
|
414
|
+
}
|
|
415
|
+
})
|
|
416
|
+
}
|
|
417
|
+
|
|
347
418
|
/** Keep native focus on the corresponding control when a root-level $match replaces its element. */
|
|
348
419
|
export function preserveRootFocus(root: Readable<Element | null>): void {
|
|
349
420
|
const focusable =
|
|
@@ -485,7 +556,7 @@ export function useComponentHost(
|
|
|
485
556
|
{
|
|
486
557
|
type: string
|
|
487
558
|
callback: (event: Event) => void | (() => void)
|
|
488
|
-
target?: Element
|
|
559
|
+
target?: Element | undefined
|
|
489
560
|
cleanup?: void | (() => void)
|
|
490
561
|
}
|
|
491
562
|
>()
|
|
@@ -508,9 +579,11 @@ export function useComponentHost(
|
|
|
508
579
|
const propHandles = Object.create(null) as Record<string, unknown>
|
|
509
580
|
for (const name of propNames) {
|
|
510
581
|
const validity = () => options.propValidity?.(name)
|
|
582
|
+
// One computed per prop: a reader of this prop does not run again when another prop changes.
|
|
583
|
+
const value = computed(() => props?.value[name])
|
|
511
584
|
propHandles[name] = Object.freeze({
|
|
512
585
|
get value() {
|
|
513
|
-
return
|
|
586
|
+
return value.value
|
|
514
587
|
},
|
|
515
588
|
get inputValue() {
|
|
516
589
|
return propInputs(name)
|
|
@@ -545,7 +618,7 @@ export function useComponentHost(
|
|
|
545
618
|
data: Object.fromEntries(
|
|
546
619
|
Object.entries(data).map(([name, value]) => [name, () => value.value]),
|
|
547
620
|
),
|
|
548
|
-
acceptsState: options.acceptsState,
|
|
621
|
+
...(options.acceptsState === undefined ? {} : { acceptsState: options.acceptsState }),
|
|
549
622
|
}, options.controllerSource?.definition ?? '<component>'),
|
|
550
623
|
on(type: string, callback: (event: Event) => void | (() => void)): () => void {
|
|
551
624
|
const entry: Subscription = { type, callback }
|
|
@@ -794,3 +867,915 @@ export function createDispatch(
|
|
|
794
867
|
return root.value?.dispatchEvent(event) ?? true
|
|
795
868
|
}
|
|
796
869
|
}
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* A consumer's attributes for the root. Vue sets a name that is also an object property, such as
|
|
873
|
+
* `constructor`, as a DOM property; capitalized, it stays an attribute, which HTML lowercases.
|
|
874
|
+
*/
|
|
875
|
+
export function nativeAttrs(attrs: Record<string, unknown>): Record<string, unknown> {
|
|
876
|
+
const names = Object.keys(attrs)
|
|
877
|
+
if (!names.some((name) => Object.hasOwn(Object.prototype, name))) return attrs
|
|
878
|
+
return Object.fromEntries(names.map((name) => [
|
|
879
|
+
Object.hasOwn(Object.prototype, name)
|
|
880
|
+
? name === '__proto__' ? '__Proto__' : name[0]!.toUpperCase() + name.slice(1)
|
|
881
|
+
: name,
|
|
882
|
+
attrs[name],
|
|
883
|
+
]))
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
/** Hydrating server markup needs its root to be the element this component renders. */
|
|
887
|
+
export function checkHydratedRoot(expected: string | undefined, tag: string): void {
|
|
888
|
+
const node = getCurrentInstance()?.vnode.el
|
|
889
|
+
if (
|
|
890
|
+
typeof window === 'undefined' || node == null
|
|
891
|
+
|| node instanceof Element && node.localName === expected
|
|
892
|
+
) return
|
|
893
|
+
const message = `Server markup for <${tag}> has an incompatible root.`
|
|
894
|
+
throw Object.assign(new Error(`HR005: ${message}`), {
|
|
895
|
+
name: 'HtmlDiagnosticError',
|
|
896
|
+
diagnostic: Object.freeze({ code: 'HR005', message }),
|
|
897
|
+
})
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
function diagnostic(code: string, message: string): Error {
|
|
901
|
+
return Object.assign(new Error(`${code}: ${message}`), {
|
|
902
|
+
name: 'HtmlDiagnosticError',
|
|
903
|
+
diagnostic: Object.freeze({ code, message }),
|
|
904
|
+
})
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
/** A component's context value from the ancestor that provides it; a missing provider is an error. */
|
|
908
|
+
export function injectContext(tag: string, from: string, name: string): any {
|
|
909
|
+
const value = inject<any>(`html-next:${from}:${name}`)
|
|
910
|
+
if (value === undefined) {
|
|
911
|
+
throw diagnostic('HR009', `<${tag}> requires context \`${name}\` from <${from}>.`)
|
|
912
|
+
}
|
|
913
|
+
return value
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
/** Vue may return an in-progress computed value on a cyclic read; guard the public value access instead. */
|
|
917
|
+
export function cycleCheckedComputed<T>(evaluate: () => T) {
|
|
918
|
+
const value = computed(evaluate)
|
|
919
|
+
let reading = false
|
|
920
|
+
return new Proxy(value, {
|
|
921
|
+
get(target, property) {
|
|
922
|
+
if (property !== 'value') return Reflect.get(target, property)
|
|
923
|
+
if (reading) throw diagnostic('HR006', 'A reactive computed value depends on itself.')
|
|
924
|
+
reading = true
|
|
925
|
+
try {
|
|
926
|
+
return target.value
|
|
927
|
+
} finally {
|
|
928
|
+
reading = false
|
|
929
|
+
}
|
|
930
|
+
},
|
|
931
|
+
})
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
/**
|
|
935
|
+
* A declared prop's reflected root attribute: the authored literal until the consumer passes the
|
|
936
|
+
* prop, then its value as text, absent for null.
|
|
937
|
+
*/
|
|
938
|
+
export function useReflectedProp() {
|
|
939
|
+
const instance = getCurrentInstance()
|
|
940
|
+
return (
|
|
941
|
+
name: string,
|
|
942
|
+
kebab: string,
|
|
943
|
+
value: unknown,
|
|
944
|
+
fallback: string | undefined,
|
|
945
|
+
bound: boolean,
|
|
946
|
+
separator?: string,
|
|
947
|
+
): string | undefined => {
|
|
948
|
+
const incoming = instance?.vnode.props
|
|
949
|
+
if (
|
|
950
|
+
!bound
|
|
951
|
+
&& (incoming === null || incoming === undefined
|
|
952
|
+
|| (!Object.hasOwn(incoming, name) && !Object.hasOwn(incoming, kebab)))
|
|
953
|
+
) return fallback
|
|
954
|
+
if (value === null || value === undefined) return undefined
|
|
955
|
+
return Array.isArray(value) && separator !== undefined
|
|
956
|
+
? value.join(separator)
|
|
957
|
+
: typeof value === 'object'
|
|
958
|
+
? JSON.stringify(value)
|
|
959
|
+
: String(value)
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
/** A scoped slot's name, which the consumer must fill with a `<template slot>` rather than plain children. */
|
|
964
|
+
export function useScopedSlotName() {
|
|
965
|
+
const slots = useSlots()
|
|
966
|
+
return (value: unknown): string => {
|
|
967
|
+
const name = value == null ? '' : String(value)
|
|
968
|
+
if (slots[name] === undefined && slots.default?.().some((node) => node.props?.slot === name)) {
|
|
969
|
+
throw diagnostic(
|
|
970
|
+
'HR007',
|
|
971
|
+
'Scoped slot `' + name + '` requires a consumer <template slot="' + name + '">.',
|
|
972
|
+
)
|
|
973
|
+
}
|
|
974
|
+
return name
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
/** An inline text part that keeps its last accepted text while its value is invalid. */
|
|
979
|
+
export type InlineTextSegment = {
|
|
980
|
+
readonly value: unknown
|
|
981
|
+
readonly text: string
|
|
982
|
+
readonly accepted: boolean
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
export function inlineTextSegment(
|
|
986
|
+
value: unknown,
|
|
987
|
+
accepted: boolean,
|
|
988
|
+
text: (value: any) => string,
|
|
989
|
+
): InlineTextSegment {
|
|
990
|
+
return {
|
|
991
|
+
value,
|
|
992
|
+
accepted,
|
|
993
|
+
text: accepted && value !== Symbol.for('html-next.invalid-result') ? text(value) ?? '' : '',
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
export const RetainedInlineText = /* @__PURE__ */ defineComponent({
|
|
998
|
+
props: { segments: { type: Array as PropType<InlineTextSegment[]>, required: true } },
|
|
999
|
+
setup(props) {
|
|
1000
|
+
const previous: string[] = []
|
|
1001
|
+
return () =>
|
|
1002
|
+
createTextVNode(
|
|
1003
|
+
props.segments.map((segment, index) => {
|
|
1004
|
+
if (
|
|
1005
|
+
segment.accepted && segment.value !== Symbol.for('html-next.invalid-result')
|
|
1006
|
+
) previous[index] = segment.text
|
|
1007
|
+
return previous[index] ?? ''
|
|
1008
|
+
}).join(''),
|
|
1009
|
+
)
|
|
1010
|
+
},
|
|
1011
|
+
})
|
|
1012
|
+
|
|
1013
|
+
const KeyedFailure = /* @__PURE__ */ defineComponent({
|
|
1014
|
+
props: { error: { type: null, required: true } },
|
|
1015
|
+
setup(props) {
|
|
1016
|
+
return () => {
|
|
1017
|
+
throw props.error
|
|
1018
|
+
}
|
|
1019
|
+
},
|
|
1020
|
+
})
|
|
1021
|
+
|
|
1022
|
+
/** A keyed list that produces a duplicate key keeps its last rows and reports the error in the browser. */
|
|
1023
|
+
export const KeyedBoundary = /* @__PURE__ */ defineComponent({
|
|
1024
|
+
setup(_props, { slots }) {
|
|
1025
|
+
let last: VNode[] = []
|
|
1026
|
+
return () => {
|
|
1027
|
+
try {
|
|
1028
|
+
last = slots.default?.() ?? []
|
|
1029
|
+
return last
|
|
1030
|
+
} catch (error) {
|
|
1031
|
+
if (
|
|
1032
|
+
!(error instanceof Error)
|
|
1033
|
+
|| (error as Error & { diagnostic?: { code?: string } }).diagnostic?.code !== 'HR004'
|
|
1034
|
+
) throw error
|
|
1035
|
+
if (typeof window === 'undefined') throw error
|
|
1036
|
+
return [...last, h(KeyedFailure, { error })]
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
},
|
|
1040
|
+
})
|
|
1041
|
+
|
|
1042
|
+
const warned = new Set<string>()
|
|
1043
|
+
/** An authored value's check: false warns once per authored location. */
|
|
1044
|
+
export function warnUnless(accepted: boolean, location: string, message: string): boolean {
|
|
1045
|
+
if (!accepted && !warned.has(location)) {
|
|
1046
|
+
warned.add(location)
|
|
1047
|
+
console.warn(message)
|
|
1048
|
+
}
|
|
1049
|
+
return accepted
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
/**
|
|
1053
|
+
* A handler's checked write to a ref or through a setter. An invalid result, such as a number that
|
|
1054
|
+
* is not finite, writes nothing. A value neither absent nor of the destination's type warns once for
|
|
1055
|
+
* its authored write (the state `path` in `file`'s `handler`), as the live runtime does, and
|
|
1056
|
+
* leaves the destination as it was.
|
|
1057
|
+
*/
|
|
1058
|
+
export function write(
|
|
1059
|
+
to: { value: unknown } | ((value: any) => void),
|
|
1060
|
+
value: unknown,
|
|
1061
|
+
check?: (value: any) => boolean,
|
|
1062
|
+
file?: string,
|
|
1063
|
+
handler?: string,
|
|
1064
|
+
path?: string,
|
|
1065
|
+
): void {
|
|
1066
|
+
if (
|
|
1067
|
+
value === Symbol.for('html-next.invalid-result')
|
|
1068
|
+
|| (typeof value === 'number' && !Number.isFinite(value))
|
|
1069
|
+
) return
|
|
1070
|
+
if (check !== undefined && value !== undefined && !check(value)) {
|
|
1071
|
+
warnUnless(
|
|
1072
|
+
false,
|
|
1073
|
+
`${file}#${handler}:${path}`,
|
|
1074
|
+
`${file}: HR007: State \`${path}\` does not satisfy its declared type.`,
|
|
1075
|
+
)
|
|
1076
|
+
} else if (typeof to === 'function') to(value)
|
|
1077
|
+
else to.value = value
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
export const isString = (value: unknown): value is string => typeof value === 'string'
|
|
1081
|
+
export const isNumber = (value: unknown): value is number =>
|
|
1082
|
+
typeof value === 'number' && Number.isFinite(value)
|
|
1083
|
+
export const isInteger = (value: unknown): value is number => Number.isInteger(value)
|
|
1084
|
+
export const isBoolean = (value: unknown): value is boolean => typeof value === 'boolean'
|
|
1085
|
+
|
|
1086
|
+
export function truthy(value: unknown): boolean {
|
|
1087
|
+
if (Array.isArray(value)) return value.length > 0
|
|
1088
|
+
if (value instanceof Error) return true
|
|
1089
|
+
if (value !== null && typeof value === 'object') {
|
|
1090
|
+
if (typeof Event !== 'undefined' && 'type' in value) {
|
|
1091
|
+
try {
|
|
1092
|
+
Object.getOwnPropertyDescriptor(Event.prototype, 'type')!.get!.call(value)
|
|
1093
|
+
return true
|
|
1094
|
+
} catch { /* ordinary objects use their fields */ }
|
|
1095
|
+
}
|
|
1096
|
+
return Object.keys(value).length > 0
|
|
1097
|
+
}
|
|
1098
|
+
return Boolean(value)
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
export function text(value: unknown): string {
|
|
1102
|
+
if (Array.isArray(value)) return value.map(text).join(' ')
|
|
1103
|
+
return value === null || value === undefined || typeof value === 'object' ? '' : String(value)
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
export function attribute(value: unknown): any {
|
|
1107
|
+
if (
|
|
1108
|
+
value === null || value === undefined || value === false
|
|
1109
|
+
|| (typeof value === 'object' && !Array.isArray(value))
|
|
1110
|
+
) return undefined
|
|
1111
|
+
return value === true ? '' : text(value)
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
export function list(value: unknown): any[] {
|
|
1115
|
+
return Array.isArray(value) ? value : []
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
export function number(value: unknown): number | undefined {
|
|
1119
|
+
return typeof value === 'number' && !Number.isNaN(value) ? value : undefined
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
// A nonempty scalar call cannot produce the invalid-result sentinel.
|
|
1123
|
+
export type ConcatResult<T extends unknown[]> = T extends
|
|
1124
|
+
[
|
|
1125
|
+
string | number | boolean | null | undefined,
|
|
1126
|
+
...(string | number | boolean | null | undefined)[],
|
|
1127
|
+
] ? string | undefined
|
|
1128
|
+
: string | symbol | undefined
|
|
1129
|
+
export function concat<T extends unknown[]>(...values: T): ConcatResult<T> {
|
|
1130
|
+
if (values.some(value => value === Symbol.for('html-next.invalid-result'))) {
|
|
1131
|
+
return Symbol.for('html-next.invalid-result') as ConcatResult<T>
|
|
1132
|
+
}
|
|
1133
|
+
if (values.some(value => value === undefined)) return undefined as ConcatResult<T>
|
|
1134
|
+
if (values.length === 0 || values.some(value => typeof value === 'object' && value !== null)) {
|
|
1135
|
+
return Symbol.for('html-next.invalid-result') as ConcatResult<T>
|
|
1136
|
+
}
|
|
1137
|
+
return values.map(value => value === null ? '' : String(value)).join('') as ConcatResult<T>
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
export function join(...values: unknown[]): string | symbol | undefined {
|
|
1141
|
+
if (values.some(value => value === Symbol.for('html-next.invalid-result'))) {
|
|
1142
|
+
return Symbol.for('html-next.invalid-result')
|
|
1143
|
+
}
|
|
1144
|
+
if (values.some(value => value === undefined)) return undefined
|
|
1145
|
+
if (values.length !== 2) return Symbol.for('html-next.invalid-result')
|
|
1146
|
+
const [items, separator] = values
|
|
1147
|
+
if (!Array.isArray(items) || typeof separator !== 'string') {
|
|
1148
|
+
return Symbol.for('html-next.invalid-result')
|
|
1149
|
+
}
|
|
1150
|
+
if (items.some(value => value === Symbol.for('html-next.invalid-result'))) {
|
|
1151
|
+
return Symbol.for('html-next.invalid-result')
|
|
1152
|
+
}
|
|
1153
|
+
if (items.some(value => value === undefined)) return undefined
|
|
1154
|
+
if (items.some(value => typeof value === 'object' && value !== null)) {
|
|
1155
|
+
return Symbol.for('html-next.invalid-result')
|
|
1156
|
+
}
|
|
1157
|
+
if (new Set(items.filter(value => value !== null).map(value => typeof value)).size > 1) {
|
|
1158
|
+
return Symbol.for('html-next.invalid-result')
|
|
1159
|
+
}
|
|
1160
|
+
return items.map(value => value === null ? '' : String(value)).join(separator)
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
export function math(
|
|
1164
|
+
fn: string,
|
|
1165
|
+
kinds: readonly string[],
|
|
1166
|
+
values: readonly unknown[],
|
|
1167
|
+
): number | string | symbol | undefined {
|
|
1168
|
+
if (values.includes(Symbol.for('html-next.invalid-result'))) {
|
|
1169
|
+
return Symbol.for('html-next.invalid-result')
|
|
1170
|
+
}
|
|
1171
|
+
if (values.some(value => value === undefined || value === null)) return undefined
|
|
1172
|
+
if (
|
|
1173
|
+
(fn === 'abs' || fn === 'negate') && values.length !== 1
|
|
1174
|
+
|| fn === 'round' && (values.length < 1 || values.length > 2)
|
|
1175
|
+
|| (fn === 'min' || fn === 'max') && values.length === 0
|
|
1176
|
+
|| fn === 'clamp' && values.length !== 3
|
|
1177
|
+
) return Symbol.for('html-next.invalid-result')
|
|
1178
|
+
const kind = kinds[0]
|
|
1179
|
+
const dimensional = kind === 'length' || kind === 'percentage' || kind === 'duration'
|
|
1180
|
+
if (!dimensional && kind !== 'number') return Symbol.for('html-next.invalid-result')
|
|
1181
|
+
let unit: string | undefined
|
|
1182
|
+
const numbers: number[] = []
|
|
1183
|
+
for (let index = 0; index < values.length; index++) {
|
|
1184
|
+
if (kinds[index] !== kind) return Symbol.for('html-next.invalid-result')
|
|
1185
|
+
const value = values[index]
|
|
1186
|
+
if (!dimensional) {
|
|
1187
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
1188
|
+
return Symbol.for('html-next.invalid-result')
|
|
1189
|
+
}
|
|
1190
|
+
numbers.push(value)
|
|
1191
|
+
continue
|
|
1192
|
+
}
|
|
1193
|
+
if (typeof value !== 'string') return Symbol.for('html-next.invalid-result')
|
|
1194
|
+
const match =
|
|
1195
|
+
/^(-?(?:\d+(?:\.\d+)?|\.\d+))(vmin|vmax|rem|px|em|vw|vh|ch|ex|cm|mm|in|pt|pc|q|ms|s|%)$/.exec(
|
|
1196
|
+
value,
|
|
1197
|
+
)
|
|
1198
|
+
if (match === null || unit !== undefined && match[2] !== unit) {
|
|
1199
|
+
return Symbol.for('html-next.invalid-result')
|
|
1200
|
+
}
|
|
1201
|
+
unit = match[2]
|
|
1202
|
+
if (
|
|
1203
|
+
(unit === '%' ? 'percentage' : unit === 'ms' || unit === 's' ? 'duration' : 'length') !== kind
|
|
1204
|
+
) return Symbol.for('html-next.invalid-result')
|
|
1205
|
+
const number = Number(match[1])
|
|
1206
|
+
if (!Number.isFinite(number)) return Symbol.for('html-next.invalid-result')
|
|
1207
|
+
numbers.push(number)
|
|
1208
|
+
}
|
|
1209
|
+
let result: number
|
|
1210
|
+
switch (fn) {
|
|
1211
|
+
case 'abs':
|
|
1212
|
+
result = Math.abs(numbers[0]!)
|
|
1213
|
+
break
|
|
1214
|
+
case 'negate':
|
|
1215
|
+
result = -numbers[0]!
|
|
1216
|
+
break
|
|
1217
|
+
case 'round': {
|
|
1218
|
+
const step = Math.abs(numbers[1] ?? 1)
|
|
1219
|
+
if (step === 0) return Symbol.for('html-next.invalid-result')
|
|
1220
|
+
result = Math.round(numbers[0]! / step) * step
|
|
1221
|
+
break
|
|
1222
|
+
}
|
|
1223
|
+
case 'min':
|
|
1224
|
+
result = Math.min(...numbers)
|
|
1225
|
+
break
|
|
1226
|
+
case 'max':
|
|
1227
|
+
result = Math.max(...numbers)
|
|
1228
|
+
break
|
|
1229
|
+
case 'clamp':
|
|
1230
|
+
result = Math.max(numbers[0]!, Math.min(numbers[1]!, numbers[2]!))
|
|
1231
|
+
break
|
|
1232
|
+
default:
|
|
1233
|
+
return Symbol.for('html-next.invalid-result')
|
|
1234
|
+
}
|
|
1235
|
+
return !Number.isFinite(result)
|
|
1236
|
+
? Symbol.for('html-next.invalid-result')
|
|
1237
|
+
: unit === undefined
|
|
1238
|
+
? result
|
|
1239
|
+
: String(result) + unit
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
export function arithmetic(
|
|
1243
|
+
op: string,
|
|
1244
|
+
leftKind: string,
|
|
1245
|
+
rightKind: string,
|
|
1246
|
+
left: unknown,
|
|
1247
|
+
right: unknown,
|
|
1248
|
+
operate: (a: number, b: number) => number,
|
|
1249
|
+
): string | symbol | undefined {
|
|
1250
|
+
const invalid = Symbol.for('html-next.invalid-result')
|
|
1251
|
+
if (left === invalid || right === invalid) return invalid
|
|
1252
|
+
if (left === null || left === undefined || right === null || right === undefined) return undefined
|
|
1253
|
+
const isDimension = (kind: string): boolean =>
|
|
1254
|
+
kind === 'length' || kind === 'percentage' || kind === 'duration'
|
|
1255
|
+
const parse = (value: unknown, kind: string): { number: number; unit: string } | undefined => {
|
|
1256
|
+
if (typeof value !== 'string' || !isDimension(kind)) return undefined
|
|
1257
|
+
const match =
|
|
1258
|
+
/^(-?(?:\d+(?:\.\d+)?|\.\d+))(vmin|vmax|rem|px|em|vw|vh|ch|ex|cm|mm|in|pt|pc|q|ms|s|%)$/.exec(
|
|
1259
|
+
value,
|
|
1260
|
+
)
|
|
1261
|
+
if (match === null) return undefined
|
|
1262
|
+
const unit = match[2]!
|
|
1263
|
+
if (
|
|
1264
|
+
(unit === '%' ? 'percentage' : unit === 'ms' || unit === 's' ? 'duration' : 'length') !== kind
|
|
1265
|
+
) return undefined
|
|
1266
|
+
const number = Number(match[1])
|
|
1267
|
+
return Number.isFinite(number) ? { number, unit } : undefined
|
|
1268
|
+
}
|
|
1269
|
+
const x = parse(left, leftKind), y = parse(right, rightKind)
|
|
1270
|
+
if (isDimension(leftKind) && x === undefined || isDimension(rightKind) && y === undefined) {
|
|
1271
|
+
return invalid
|
|
1272
|
+
}
|
|
1273
|
+
let value: number, unit: string
|
|
1274
|
+
if (op === '+' || op === '-') {
|
|
1275
|
+
if (x === undefined || y === undefined || leftKind !== rightKind || x.unit !== y.unit) {
|
|
1276
|
+
return invalid
|
|
1277
|
+
}
|
|
1278
|
+
value = operate(x.number, y.number)
|
|
1279
|
+
unit = x.unit
|
|
1280
|
+
} else if (op === '*') {
|
|
1281
|
+
const quantity = x ?? y
|
|
1282
|
+
const factor = x === undefined ? left : right
|
|
1283
|
+
if (
|
|
1284
|
+
quantity === undefined || x !== undefined && y !== undefined || typeof factor !== 'number'
|
|
1285
|
+
|| !Number.isFinite(factor)
|
|
1286
|
+
) return invalid
|
|
1287
|
+
value = operate(quantity.number, factor)
|
|
1288
|
+
unit = quantity.unit
|
|
1289
|
+
} else {
|
|
1290
|
+
if (
|
|
1291
|
+
op !== '/' || x === undefined || y !== undefined || typeof right !== 'number'
|
|
1292
|
+
|| !Number.isFinite(right) || right === 0
|
|
1293
|
+
) return invalid
|
|
1294
|
+
value = operate(x.number, right)
|
|
1295
|
+
unit = x.unit
|
|
1296
|
+
}
|
|
1297
|
+
return Number.isFinite(value) ? String(value) + unit : invalid
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
export function sortBy(items: any[], keys: readonly string[]): any[] {
|
|
1301
|
+
const field = (item: any, path: string): unknown =>
|
|
1302
|
+
path !== '' && item !== null && typeof item === 'object' && !Array.isArray(item)
|
|
1303
|
+
? path.split('.').reduce((value, key) => value?.[key], item)
|
|
1304
|
+
: item
|
|
1305
|
+
return items.slice().sort((a, b) => {
|
|
1306
|
+
for (const key of keys) {
|
|
1307
|
+
const descending = key.startsWith('-')
|
|
1308
|
+
const path = descending ? key.slice(1) : key
|
|
1309
|
+
const x = field(a, path), y = field(b, path)
|
|
1310
|
+
const order = typeof x === 'number' && typeof y === 'number'
|
|
1311
|
+
? x - y
|
|
1312
|
+
: text(x).localeCompare(text(y))
|
|
1313
|
+
if (order !== 0) return descending ? -order : order
|
|
1314
|
+
}
|
|
1315
|
+
return 0
|
|
1316
|
+
})
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
export function eachRows<T>(
|
|
1320
|
+
items: readonly T[],
|
|
1321
|
+
): {
|
|
1322
|
+
item: T
|
|
1323
|
+
index: number
|
|
1324
|
+
loop: { index: number; first: boolean; last: boolean; count: number }
|
|
1325
|
+
}[] {
|
|
1326
|
+
return items.map((item, index) => ({
|
|
1327
|
+
item,
|
|
1328
|
+
index,
|
|
1329
|
+
loop: { index, first: index === 0, last: index === items.length - 1, count: items.length },
|
|
1330
|
+
}))
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
export function uniqueKeys<T>(
|
|
1334
|
+
items: readonly T[],
|
|
1335
|
+
keyOf: (
|
|
1336
|
+
item: T,
|
|
1337
|
+
index: number,
|
|
1338
|
+
loop: { index: number; first: boolean; last: boolean; count: number },
|
|
1339
|
+
) => unknown,
|
|
1340
|
+
): readonly T[] {
|
|
1341
|
+
const seen = new Set<unknown>()
|
|
1342
|
+
for (let index = 0; index < items.length; index += 1) {
|
|
1343
|
+
const key = keyOf(items[index]!, index, {
|
|
1344
|
+
index,
|
|
1345
|
+
first: index === 0,
|
|
1346
|
+
last: index === items.length - 1,
|
|
1347
|
+
count: items.length,
|
|
1348
|
+
})
|
|
1349
|
+
if (seen.has(key)) {
|
|
1350
|
+
const message = `A keyed list produced duplicate key \`${text(key)}\`.`
|
|
1351
|
+
throw Object.assign(new Error(`HR004: ${message}`), {
|
|
1352
|
+
name: 'HtmlDiagnosticError',
|
|
1353
|
+
diagnostic: Object.freeze({ code: 'HR004', message }),
|
|
1354
|
+
})
|
|
1355
|
+
}
|
|
1356
|
+
seen.add(key)
|
|
1357
|
+
}
|
|
1358
|
+
return items
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
/**
|
|
1362
|
+
* Digits after the point in `value`'s shortest round-trip text, as `Number#toString` writes it:
|
|
1363
|
+
* 2 for 1.25, 7 for 1e-7, and 0 for an integer, 1e21, NaN or Infinity. Writing that text made an
|
|
1364
|
+
* addition about ten times slower (200 ns against 20 ns), so up to 22 places, where 10^places is
|
|
1365
|
+
* exact, the fewest places at which `value` survives rounding stand in for it. That count is never
|
|
1366
|
+
* below the text's, and differs only for a value of more than 15 significant digits, which
|
|
1367
|
+
* `decimalResult` leaves as a double either way.
|
|
1368
|
+
*/
|
|
1369
|
+
export function decimalPlaces(value: number): number {
|
|
1370
|
+
if (Number.isInteger(value)) return 0
|
|
1371
|
+
for (let places = 1; places <= 22; places += 1) {
|
|
1372
|
+
if (Math.round(value * 10 ** places) / 10 ** places === value) return places
|
|
1373
|
+
}
|
|
1374
|
+
const [digits, exponent] = String(value).split('e')
|
|
1375
|
+
const point = digits!.indexOf('.')
|
|
1376
|
+
return Math.max(0, (point < 0 ? 0 : digits!.length - point - 1) - Number(exponent ?? 0))
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
/**
|
|
1380
|
+
* The double nearest the exact decimal result: `a` and `b` become integers at `left` and `right`
|
|
1381
|
+
* places, `combine` joins them, and the integer is read back at `places`. Every step is exact while
|
|
1382
|
+
* the operands and `result` stay below 10^15 at `places`, the 15 significant digits a double always
|
|
1383
|
+
* holds; past that (or for a non-finite value, or with no places) the double `result` stands.
|
|
1384
|
+
* Dividing by 10^places is exact only up to 10^22, so past 22 places a nonzero integer is read back
|
|
1385
|
+
* by parsing its decimal text, which rounds exactly too. A zero divides, keeping its sign.
|
|
1386
|
+
*/
|
|
1387
|
+
export function decimalResult(
|
|
1388
|
+
result: number,
|
|
1389
|
+
a: number,
|
|
1390
|
+
b: number,
|
|
1391
|
+
left: number,
|
|
1392
|
+
right: number,
|
|
1393
|
+
places: number,
|
|
1394
|
+
combine: (a: number, b: number) => number,
|
|
1395
|
+
): number {
|
|
1396
|
+
if (!(places > 0 && Math.max(Math.abs(a), Math.abs(b), Math.abs(result)) * 10 ** places < 1e15)) {
|
|
1397
|
+
return result
|
|
1398
|
+
}
|
|
1399
|
+
const integer = combine(Math.round(a * 10 ** left), Math.round(b * 10 ** right))
|
|
1400
|
+
return places > 22 && integer !== 0 ? Number(`${integer}e-${places}`) : integer / 10 ** places
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
/** `a + b` at the larger operand's decimal places: `decimalAdd(0.1, 0.2)` is 0.3. */
|
|
1404
|
+
export function decimalAdd(a: number, b: number): number {
|
|
1405
|
+
const places = Math.max(decimalPlaces(a), decimalPlaces(b))
|
|
1406
|
+
return decimalResult(a + b, a, b, places, places, places, (x, y) => x + y)
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
/** `a - b` at the larger operand's decimal places: `decimalSubtract(0.3, 0.1)` is 0.2. */
|
|
1410
|
+
export function decimalSubtract(a: number, b: number): number {
|
|
1411
|
+
const places = Math.max(decimalPlaces(a), decimalPlaces(b))
|
|
1412
|
+
return decimalResult(a - b, a, b, places, places, places, (x, y) => x - y)
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
/** `a * b` at the sum of the operands' decimal places: `decimalMultiply(0.1, 0.2)` is 0.02. */
|
|
1416
|
+
export function decimalMultiply(a: number, b: number): number {
|
|
1417
|
+
const left = decimalPlaces(a)
|
|
1418
|
+
const right = decimalPlaces(b)
|
|
1419
|
+
return decimalResult(a * b, a, b, left, right, left + right, (x, y) => x * y)
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
/** `a / b` at full double precision: `decimalDivide(1, 3)` is 0.3333333333333333 and `decimalDivide(1, 0)` is Infinity. */
|
|
1423
|
+
export function decimalDivide(a: number, b: number): number {
|
|
1424
|
+
return a / b
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
/**
|
|
1428
|
+
* `a % b` at the larger operand's decimal places, with the dividend's sign as JavaScript's `%`:
|
|
1429
|
+
* `decimalRemainder(0.3, 0.1)` is 0, where JavaScript gives 0.09999999999999998.
|
|
1430
|
+
*/
|
|
1431
|
+
export function decimalRemainder(a: number, b: number): number {
|
|
1432
|
+
const places = Math.max(decimalPlaces(a), decimalPlaces(b))
|
|
1433
|
+
return decimalResult(a % b, a, b, places, places, places, (x, y) => x % y)
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
export function createFormatValue(): any {
|
|
1437
|
+
// FIFO bounds native instances and retained keys; only constructor inputs, never values, are cached.
|
|
1438
|
+
const cache = new Map()
|
|
1439
|
+
function formatterFor(name: any, locale: any, options: any) {
|
|
1440
|
+
const constructor = Reflect.get(Intl, name)
|
|
1441
|
+
// There is no cheap native query for changes to the default time zone. Keep native lookup for
|
|
1442
|
+
// instants without an explicit zone; civil HTML values already supply their fixed UTC anchor.
|
|
1443
|
+
if (name === 'DateTimeFormat' && options.timeZone === undefined) {
|
|
1444
|
+
return new constructor(locale, options)
|
|
1445
|
+
}
|
|
1446
|
+
const entries = []
|
|
1447
|
+
for (const key of Object.keys(options).sort()) {
|
|
1448
|
+
const value = options[key]
|
|
1449
|
+
if (value === undefined) {
|
|
1450
|
+
continue
|
|
1451
|
+
}
|
|
1452
|
+
// Native option coercion can observe mutable objects. Bypass caching rather than stringify
|
|
1453
|
+
// them or collapse NaN/infinity into null, which would change validation and side effects.
|
|
1454
|
+
if (
|
|
1455
|
+
value !== null && typeof value !== 'string' && typeof value !== 'boolean'
|
|
1456
|
+
&& typeof value !== 'number'
|
|
1457
|
+
|| typeof value === 'number' && !Number.isFinite(value)
|
|
1458
|
+
) {
|
|
1459
|
+
return new constructor(locale, options)
|
|
1460
|
+
}
|
|
1461
|
+
entries.push([key, typeof value, Object.is(value, -0) ? '-0' : value])
|
|
1462
|
+
}
|
|
1463
|
+
const language = locale === undefined && typeof navigator !== 'undefined'
|
|
1464
|
+
? navigator.language
|
|
1465
|
+
: undefined
|
|
1466
|
+
const key = JSON.stringify([name, locale, language, entries])
|
|
1467
|
+
if (key.length > 4096) {
|
|
1468
|
+
return new constructor(locale, options)
|
|
1469
|
+
}
|
|
1470
|
+
const cached = cache.get(key)
|
|
1471
|
+
if (cached !== undefined && cached.constructor === constructor) {
|
|
1472
|
+
return cached.value
|
|
1473
|
+
}
|
|
1474
|
+
const value = new constructor(locale, options)
|
|
1475
|
+
if (cache.size >= 128 && !cache.has(key)) {
|
|
1476
|
+
cache.delete(cache.keys().next().value)
|
|
1477
|
+
}
|
|
1478
|
+
cache.set(key, { constructor, value })
|
|
1479
|
+
return value
|
|
1480
|
+
}
|
|
1481
|
+
/**
|
|
1482
|
+
* The native Intl adapter. Self-contained so converters emit this same implementation only when
|
|
1483
|
+
* authored expressions use formatting, without importing the live parser or interpreter.
|
|
1484
|
+
* undefined is absence; the shared invalid-result symbol tells each target to retain its output.
|
|
1485
|
+
*/
|
|
1486
|
+
return function formatValue(value: any, inputType: any, operation: any, ...args: any[]) {
|
|
1487
|
+
const invalid = Symbol.for('html-next.invalid-result')
|
|
1488
|
+
if (value === invalid || args.includes(invalid)) {
|
|
1489
|
+
return invalid
|
|
1490
|
+
}
|
|
1491
|
+
if (value === undefined || value === null || args.some((arg: any) => arg === undefined)) {
|
|
1492
|
+
return undefined
|
|
1493
|
+
}
|
|
1494
|
+
const end = operation === 'formatRange' ? args.shift() : undefined
|
|
1495
|
+
if (operation === 'formatRange' && (end === null || end === undefined)) {
|
|
1496
|
+
return undefined
|
|
1497
|
+
}
|
|
1498
|
+
const explicit = typeof args[0] === 'string'
|
|
1499
|
+
const defaults = {
|
|
1500
|
+
number: 'number',
|
|
1501
|
+
integer: 'number',
|
|
1502
|
+
percentage: 'percent',
|
|
1503
|
+
date: 'date',
|
|
1504
|
+
time: 'time',
|
|
1505
|
+
datetime: 'dateTime',
|
|
1506
|
+
'datetime-local': 'dateTime',
|
|
1507
|
+
duration: 'duration',
|
|
1508
|
+
list: 'list',
|
|
1509
|
+
}
|
|
1510
|
+
const format = explicit ? args.shift() : Reflect.get(defaults, inputType ?? '')
|
|
1511
|
+
const options = args.length > 0 ? args.shift() : {}
|
|
1512
|
+
const locale = args.shift()
|
|
1513
|
+
if (
|
|
1514
|
+
args.length > 0 || !format || options === null || typeof options !== 'object'
|
|
1515
|
+
|| Array.isArray(options) || locale !== undefined && typeof locale !== 'string'
|
|
1516
|
+
) {
|
|
1517
|
+
return invalid
|
|
1518
|
+
}
|
|
1519
|
+
try {
|
|
1520
|
+
const durationFields = [
|
|
1521
|
+
'years',
|
|
1522
|
+
'months',
|
|
1523
|
+
'weeks',
|
|
1524
|
+
'days',
|
|
1525
|
+
'hours',
|
|
1526
|
+
'minutes',
|
|
1527
|
+
'seconds',
|
|
1528
|
+
'milliseconds',
|
|
1529
|
+
'microseconds',
|
|
1530
|
+
'nanoseconds',
|
|
1531
|
+
]
|
|
1532
|
+
const settings = { ...options }
|
|
1533
|
+
let formatter
|
|
1534
|
+
let input = value
|
|
1535
|
+
let last = end
|
|
1536
|
+
let zoneFree = false
|
|
1537
|
+
if (['number', 'currency', 'percent', 'unit'].includes(format)) {
|
|
1538
|
+
if (format !== 'number') {
|
|
1539
|
+
if (settings.style !== undefined && settings.style !== format) {
|
|
1540
|
+
return invalid
|
|
1541
|
+
}
|
|
1542
|
+
settings.style = format
|
|
1543
|
+
}
|
|
1544
|
+
const quantity = (candidate: any) => {
|
|
1545
|
+
if (
|
|
1546
|
+
inputType === 'percentage' && typeof candidate === 'string'
|
|
1547
|
+
&& /^-?(?:\d+(?:\.\d+)?|\.\d+)%$/.test(candidate)
|
|
1548
|
+
) {
|
|
1549
|
+
return Number(candidate.slice(0, -1)) / 100
|
|
1550
|
+
}
|
|
1551
|
+
return candidate
|
|
1552
|
+
}
|
|
1553
|
+
input = quantity(input)
|
|
1554
|
+
last = quantity(last)
|
|
1555
|
+
if (
|
|
1556
|
+
typeof input !== 'number' || !Number.isFinite(input)
|
|
1557
|
+
|| operation === 'formatRange' && (typeof last !== 'number' || !Number.isFinite(last))
|
|
1558
|
+
) {
|
|
1559
|
+
return invalid
|
|
1560
|
+
}
|
|
1561
|
+
formatter = formatterFor('NumberFormat', locale, settings)
|
|
1562
|
+
} else if (['date', 'time', 'dateTime'].includes(format)) {
|
|
1563
|
+
const civil = inputType === 'date' || inputType === 'time' || inputType === 'datetime-local'
|
|
1564
|
+
|| typeof input === 'string' && !/(?:Z|[+-]\d\d:\d\d)$/.test(input)
|
|
1565
|
+
zoneFree = civil
|
|
1566
|
+
if (civil && (settings.timeZone !== undefined || settings.timeZoneName !== undefined)) {
|
|
1567
|
+
return invalid
|
|
1568
|
+
}
|
|
1569
|
+
if (
|
|
1570
|
+
format === 'date'
|
|
1571
|
+
&& (settings.timeStyle !== undefined
|
|
1572
|
+
|| ['hour', 'minute', 'second', 'dayPeriod', 'fractionalSecondDigits'].some((
|
|
1573
|
+
key: any,
|
|
1574
|
+
) => settings[key] !== undefined))
|
|
1575
|
+
|| format === 'time'
|
|
1576
|
+
&& (settings.dateStyle !== undefined
|
|
1577
|
+
|| ['year', 'month', 'day', 'weekday', 'era'].some((key: any) =>
|
|
1578
|
+
settings[key] !== undefined
|
|
1579
|
+
))
|
|
1580
|
+
) {
|
|
1581
|
+
return invalid
|
|
1582
|
+
}
|
|
1583
|
+
const fields = [
|
|
1584
|
+
'year',
|
|
1585
|
+
'month',
|
|
1586
|
+
'day',
|
|
1587
|
+
'weekday',
|
|
1588
|
+
'era',
|
|
1589
|
+
'hour',
|
|
1590
|
+
'minute',
|
|
1591
|
+
'second',
|
|
1592
|
+
'dayPeriod',
|
|
1593
|
+
'fractionalSecondDigits',
|
|
1594
|
+
]
|
|
1595
|
+
if (
|
|
1596
|
+
settings.dateStyle === undefined && settings.timeStyle === undefined
|
|
1597
|
+
&& !fields.some((key: any) => settings[key] !== undefined)
|
|
1598
|
+
) {
|
|
1599
|
+
if (format !== 'time') {
|
|
1600
|
+
settings.dateStyle = 'medium'
|
|
1601
|
+
}
|
|
1602
|
+
if (format !== 'date') {
|
|
1603
|
+
settings.timeStyle = 'medium'
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
if (civil) {
|
|
1607
|
+
settings.timeZone = 'UTC'
|
|
1608
|
+
}
|
|
1609
|
+
const asDate = (candidate: any) => {
|
|
1610
|
+
if (typeof candidate === 'number' && Number.isFinite(candidate)) {
|
|
1611
|
+
return new Date(candidate)
|
|
1612
|
+
}
|
|
1613
|
+
if (typeof candidate !== 'string') {
|
|
1614
|
+
return undefined
|
|
1615
|
+
}
|
|
1616
|
+
let written = candidate.replace(' ', 'T')
|
|
1617
|
+
if (/^\d\d:\d\d(?::\d\d(?:\.\d{1,3})?)?$/.test(written)) {
|
|
1618
|
+
if (format !== 'time') {
|
|
1619
|
+
return undefined
|
|
1620
|
+
}
|
|
1621
|
+
written = '1970-01-01T' + written + 'Z'
|
|
1622
|
+
} else if (/^\d{4,}-\d\d-\d\d$/.test(written)) {
|
|
1623
|
+
if (format !== 'date') {
|
|
1624
|
+
return undefined
|
|
1625
|
+
}
|
|
1626
|
+
written += 'T00:00:00Z'
|
|
1627
|
+
} else if (/^\d{4,}-\d\d-\d\dT\d\d:\d\d(?::\d\d(?:\.\d{1,3})?)?$/.test(written)) {
|
|
1628
|
+
written += 'Z'
|
|
1629
|
+
} else if (
|
|
1630
|
+
!/^\d{4,}-\d\d-\d\dT\d\d:\d\d(?::\d\d(?:\.\d{1,3})?)?(?:Z|[+-]\d\d:\d\d)$/.test(written)
|
|
1631
|
+
) {
|
|
1632
|
+
return undefined
|
|
1633
|
+
}
|
|
1634
|
+
const date = new Date(
|
|
1635
|
+
written.replace(/^(\d{5,})-/, (_match: any, year: any) =>
|
|
1636
|
+
'+' + year.padStart(6, '0') + '-'),
|
|
1637
|
+
)
|
|
1638
|
+
if (!Number.isFinite(date.getTime())) {
|
|
1639
|
+
return undefined
|
|
1640
|
+
}
|
|
1641
|
+
// Native Date normalizes invalid days; authored HTML dates must not roll into another day.
|
|
1642
|
+
const match = /^(\d{4,})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d))?/.exec(written)
|
|
1643
|
+
if (!match) {
|
|
1644
|
+
return undefined
|
|
1645
|
+
}
|
|
1646
|
+
const probe = new Date(0)
|
|
1647
|
+
probe.setUTCFullYear(Number(match[1]), Number(match[2]) - 1, Number(match[3]))
|
|
1648
|
+
if (
|
|
1649
|
+
Number(match[1]) === 0 || probe.getUTCMonth() !== Number(match[2]) - 1
|
|
1650
|
+
|| probe.getUTCDate() !== Number(match[3]) || Number(match[4]) > 23
|
|
1651
|
+
|| Number(match[5]) > 59 || Number(match[6] ?? 0) > 59
|
|
1652
|
+
) {
|
|
1653
|
+
return undefined
|
|
1654
|
+
}
|
|
1655
|
+
return date
|
|
1656
|
+
}
|
|
1657
|
+
input = asDate(input)
|
|
1658
|
+
last = operation === 'formatRange' ? asDate(last) : undefined
|
|
1659
|
+
if (!input || operation === 'formatRange' && !last) {
|
|
1660
|
+
return invalid
|
|
1661
|
+
}
|
|
1662
|
+
formatter = formatterFor('DateTimeFormat', locale, settings)
|
|
1663
|
+
} else {
|
|
1664
|
+
if (operation === 'formatRange') {
|
|
1665
|
+
return invalid
|
|
1666
|
+
}
|
|
1667
|
+
if (format === 'relativeTime') {
|
|
1668
|
+
if (
|
|
1669
|
+
typeof input !== 'number' || !Number.isFinite(input)
|
|
1670
|
+
|| typeof settings.unit !== 'string'
|
|
1671
|
+
) {
|
|
1672
|
+
return invalid
|
|
1673
|
+
}
|
|
1674
|
+
const unit = settings.unit
|
|
1675
|
+
delete settings.unit
|
|
1676
|
+
formatter = formatterFor('RelativeTimeFormat', locale, settings)
|
|
1677
|
+
return operation === 'formatParts'
|
|
1678
|
+
? formatter.formatToParts(input, unit)
|
|
1679
|
+
: formatter.format(input, unit)
|
|
1680
|
+
}
|
|
1681
|
+
if (format === 'list') {
|
|
1682
|
+
if (!Array.isArray(input) || input.some((item: any) => typeof item !== 'string')) {
|
|
1683
|
+
return invalid
|
|
1684
|
+
}
|
|
1685
|
+
formatter = formatterFor('ListFormat', locale, settings)
|
|
1686
|
+
} else if (format === 'duration') {
|
|
1687
|
+
if (inputType === 'duration' && typeof input === 'string') {
|
|
1688
|
+
const match = /^(-?(?:\d+(?:\.\d+)?|\.\d+))(ms|s)$/.exec(input)
|
|
1689
|
+
if (!match) {
|
|
1690
|
+
return invalid
|
|
1691
|
+
}
|
|
1692
|
+
const milliseconds = Number(match[1]) * (match[2] === 's' ? 1000 : 1)
|
|
1693
|
+
if (!Number.isFinite(milliseconds)) {
|
|
1694
|
+
return invalid
|
|
1695
|
+
}
|
|
1696
|
+
const hours = Math.trunc(milliseconds / 3600000)
|
|
1697
|
+
const minutes = Math.trunc((milliseconds % 3600000) / 60000)
|
|
1698
|
+
const seconds = Math.trunc((milliseconds % 60000) / 1000)
|
|
1699
|
+
input = {
|
|
1700
|
+
hours,
|
|
1701
|
+
minutes,
|
|
1702
|
+
seconds,
|
|
1703
|
+
milliseconds: Math.trunc(milliseconds % 1000),
|
|
1704
|
+
microseconds: Math.trunc((milliseconds - Math.trunc(milliseconds)) * 1000),
|
|
1705
|
+
nanoseconds: Math.round(
|
|
1706
|
+
(milliseconds * 1000 - Math.trunc(milliseconds * 1000)) * 1000,
|
|
1707
|
+
),
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
if (
|
|
1711
|
+
input === null || typeof input !== 'object' || Array.isArray(input)
|
|
1712
|
+
|| Object.keys(input).some((key: any) => !durationFields.includes(key))
|
|
1713
|
+
) {
|
|
1714
|
+
return invalid
|
|
1715
|
+
}
|
|
1716
|
+
const DurationFormat = Reflect.get(Intl, 'DurationFormat')
|
|
1717
|
+
if (!DurationFormat) {
|
|
1718
|
+
return invalid
|
|
1719
|
+
}
|
|
1720
|
+
formatter = formatterFor('DurationFormat', locale, settings)
|
|
1721
|
+
} else if (format === 'displayName') {
|
|
1722
|
+
if (typeof input !== 'string' || operation === 'formatParts') {
|
|
1723
|
+
return invalid
|
|
1724
|
+
}
|
|
1725
|
+
return formatterFor('DisplayNames', locale, settings).of(input)
|
|
1726
|
+
} else if (format === 'plural') {
|
|
1727
|
+
if (typeof input !== 'number' || !Number.isFinite(input) || operation === 'formatParts') {
|
|
1728
|
+
return invalid
|
|
1729
|
+
}
|
|
1730
|
+
const forms = settings.forms
|
|
1731
|
+
if (
|
|
1732
|
+
!forms || typeof forms !== 'object' || Array.isArray(forms)
|
|
1733
|
+
|| typeof forms.other !== 'string' || Object.entries(forms).some(([key, entry]: any) =>
|
|
1734
|
+
!['zero', 'one', 'two', 'few', 'many', 'other'].includes(key)
|
|
1735
|
+
|| typeof entry !== 'string'
|
|
1736
|
+
)
|
|
1737
|
+
) {
|
|
1738
|
+
return invalid
|
|
1739
|
+
}
|
|
1740
|
+
delete settings.forms
|
|
1741
|
+
const category = formatterFor('PluralRules', locale, settings).select(input)
|
|
1742
|
+
const message = forms[category] ?? forms.other
|
|
1743
|
+
const numberOptions = { ...settings }
|
|
1744
|
+
delete numberOptions.type
|
|
1745
|
+
delete numberOptions.forms
|
|
1746
|
+
const number = formatterFor('NumberFormat', locale, numberOptions).format(input)
|
|
1747
|
+
return message.replace(/#/g, () => number)
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
if (!zoneFree && operation !== 'formatParts') {
|
|
1751
|
+
return operation === 'formatRange'
|
|
1752
|
+
? formatter.formatRange(input, last)
|
|
1753
|
+
: formatter.format(input)
|
|
1754
|
+
}
|
|
1755
|
+
const parts = operation === 'formatRange'
|
|
1756
|
+
? formatter.formatRangeToParts(input, last)
|
|
1757
|
+
: formatter.formatToParts(input)
|
|
1758
|
+
if (
|
|
1759
|
+
zoneFree && operation !== 'formatParts'
|
|
1760
|
+
&& !parts.some((part: any) => part.type === 'timeZoneName')
|
|
1761
|
+
) {
|
|
1762
|
+
return operation === 'formatRange'
|
|
1763
|
+
? formatter.formatRange(input, last)
|
|
1764
|
+
: formatter.format(input)
|
|
1765
|
+
}
|
|
1766
|
+
const visible = zoneFree
|
|
1767
|
+
? parts.filter((part: any, index: any) =>
|
|
1768
|
+
part.type !== 'timeZoneName'
|
|
1769
|
+
&& !(part.type === 'literal' && /^[\s(),]*$/.test(part.value)
|
|
1770
|
+
&& (parts[index - 1]?.type === 'timeZoneName'
|
|
1771
|
+
|| parts[index + 1]?.type === 'timeZoneName'))
|
|
1772
|
+
)
|
|
1773
|
+
: parts
|
|
1774
|
+
return operation === 'formatParts' ? visible : visible.map((part: any) => part.value).join('')
|
|
1775
|
+
} catch {
|
|
1776
|
+
return invalid
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
export const formatValue = /* @__PURE__ */ createFormatValue()
|