From 047865368aefada8a2311c7f1c45fec49128f625 Mon Sep 17 00:00:00 2001 From: Moritz Bischof Date: Mon, 3 May 2021 17:28:25 +0200 Subject: [PATCH 1/9] remove redundant menuOffsetX --- src/ContextMenu/ContextMenu.svelte | 4 ---- src/ContextMenu/ContextMenuOption.svelte | 8 +------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/src/ContextMenu/ContextMenu.svelte b/src/ContextMenu/ContextMenu.svelte index 1a75c18dd5..15e8bcebc5 100644 --- a/src/ContextMenu/ContextMenu.svelte +++ b/src/ContextMenu/ContextMenu.svelte @@ -26,7 +26,6 @@ const position = writable([x, y]); const currentIndex = writable(-1); const hasPopup = writable(false); - const menuOffsetX = writable(0); const ctx = getContext("ContextMenu"); let options = []; @@ -45,7 +44,6 @@ } setContext("ContextMenu", { - menuOffsetX, currentIndex, position, close, @@ -91,8 +89,6 @@ } if (open || y === 0) { - menuOffsetX.set(e.x); - if (window.innerHeight - height < e.y) { y = e.y - height; } else { diff --git a/src/ContextMenu/ContextMenuOption.svelte b/src/ContextMenu/ContextMenuOption.svelte index a4f2ce455c..cad43a19fd 100644 --- a/src/ContextMenu/ContextMenuOption.svelte +++ b/src/ContextMenu/ContextMenuOption.svelte @@ -70,16 +70,11 @@ let role = "menuitem"; let submenuOpen = false; let submenuPosition = [0, 0]; - let menuOffsetX = 0; const unsubPosition = ctx.position.subscribe((position) => { rootMenuPosition = position; }); - const unsubMenuOffsetX = ctx.menuOffsetX.subscribe((_menuOffsetX) => { - menuOffsetX = _menuOffsetX; - }); - function handleClick(opts = {}) { if (disabled) return ctx.close(); if (subOptions) return; @@ -117,7 +112,6 @@ return () => { unsubPosition(); - unsubMenuOffsetX(); if (unsubCurrentIds) unsubCurrentIds(); if (unsubCurrentId) unsubCurrentId(); if (typeof timeoutHover === "number") clearTimeout(timeoutHover); @@ -132,7 +126,7 @@ const { width, y } = ref.getBoundingClientRect(); let x = rootMenuPosition[0] + width; - if (window.innerWidth - menuOffsetX < width) { + if (window.innerWidth - rootMenuPosition[0] < width) { x = rootMenuPosition[0] - width; } From fd09e96f71251763de4a0689dd184893c6de1a5f Mon Sep 17 00:00:00 2001 From: Moritz Bischof Date: Mon, 3 May 2021 17:29:23 +0200 Subject: [PATCH 2/9] explicitly declare `level` --- src/ContextMenu/ContextMenu.svelte | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ContextMenu/ContextMenu.svelte b/src/ContextMenu/ContextMenu.svelte index 15e8bcebc5..73f6e58120 100644 --- a/src/ContextMenu/ContextMenu.svelte +++ b/src/ContextMenu/ContextMenu.svelte @@ -33,6 +33,7 @@ let prevX = 0; let prevY = 0; let focusIndex = -1; + let level; function close() { open = false; From 92d1eba45c179ca8a0be19ed2d9a09d0d54c88c4 Mon Sep 17 00:00:00 2001 From: Moritz Bischof Date: Mon, 3 May 2021 17:44:13 +0200 Subject: [PATCH 3/9] remove explicit store subscription --- src/ContextMenu/ContextMenuOption.svelte | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/ContextMenu/ContextMenuOption.svelte b/src/ContextMenu/ContextMenuOption.svelte index cad43a19fd..1c9dbd9132 100644 --- a/src/ContextMenu/ContextMenuOption.svelte +++ b/src/ContextMenu/ContextMenuOption.svelte @@ -58,23 +58,20 @@ const ctxGroup = getContext("ContextMenuGroup"); const ctxRadioGroup = getContext("ContextMenuRadioGroup"); + const rootMenuPosition = ctx.position; + // "moderate-01" duration (ms) from Carbon motion recommended for small expansion, short distance movements const moderate01 = 150; let unsubCurrentIds = undefined; let unsubCurrentId = undefined; let timeoutHover = undefined; - let rootMenuPosition = [0, 0]; let focusIndex = 0; let options = []; let role = "menuitem"; let submenuOpen = false; let submenuPosition = [0, 0]; - const unsubPosition = ctx.position.subscribe((position) => { - rootMenuPosition = position; - }); - function handleClick(opts = {}) { if (disabled) return ctx.close(); if (subOptions) return; @@ -111,7 +108,6 @@ } return () => { - unsubPosition(); if (unsubCurrentIds) unsubCurrentIds(); if (unsubCurrentId) unsubCurrentId(); if (typeof timeoutHover === "number") clearTimeout(timeoutHover); @@ -123,11 +119,12 @@ $: subOptions = $$slots.default; $: ctx.setPopup(submenuOpen); $: if (submenuOpen) { + const rootMenuX = $rootMenuPosition[0]; const { width, y } = ref.getBoundingClientRect(); - let x = rootMenuPosition[0] + width; + let x = rootMenuX + width; - if (window.innerWidth - rootMenuPosition[0] < width) { - x = rootMenuPosition[0] - width; + if (window.innerWidth - rootMenuX < width) { + x = rootMenuX - width; } submenuPosition = [x, y]; From 3308fd4116719c0d9f032fa13e469086bd26f0c1 Mon Sep 17 00:00:00 2001 From: Moritz Bischof Date: Mon, 3 May 2021 19:22:37 +0200 Subject: [PATCH 4/9] extract ContextMenuInner.svelte --- src/ContextMenu/ContextMenu.svelte | 121 +++------------------- src/ContextMenu/ContextMenuInner.svelte | 126 +++++++++++++++++++++++ src/ContextMenu/ContextMenuOption.svelte | 9 +- 3 files changed, 146 insertions(+), 110 deletions(-) create mode 100644 src/ContextMenu/ContextMenuInner.svelte diff --git a/src/ContextMenu/ContextMenu.svelte b/src/ContextMenu/ContextMenu.svelte index 73f6e58120..7c157c8a46 100644 --- a/src/ContextMenu/ContextMenu.svelte +++ b/src/ContextMenu/ContextMenu.svelte @@ -1,4 +1,6 @@ -
    + -
+ diff --git a/src/ContextMenu/ContextMenuInner.svelte b/src/ContextMenu/ContextMenuInner.svelte new file mode 100644 index 0000000000..9376100c9a --- /dev/null +++ b/src/ContextMenu/ContextMenuInner.svelte @@ -0,0 +1,126 @@ + + +
    + +
diff --git a/src/ContextMenu/ContextMenuOption.svelte b/src/ContextMenu/ContextMenuOption.svelte index 1c9dbd9132..f2452a8d11 100644 --- a/src/ContextMenu/ContextMenuOption.svelte +++ b/src/ContextMenu/ContextMenuOption.svelte @@ -59,6 +59,7 @@ const ctxRadioGroup = getContext("ContextMenuRadioGroup"); const rootMenuPosition = ctx.position; + const rootMenuDimensions = ctx.dimensions; // "moderate-01" duration (ms) from Carbon motion recommended for small expansion, short distance movements const moderate01 = 150; @@ -120,11 +121,15 @@ $: ctx.setPopup(submenuOpen); $: if (submenuOpen) { const rootMenuX = $rootMenuPosition[0]; + const rootMenuWidth = $rootMenuDimensions[0]; const { width, y } = ref.getBoundingClientRect(); - let x = rootMenuX + width; - if (window.innerWidth - rootMenuX < width) { + let x; + if (window.innerWidth < rootMenuX + rootMenuWidth + width) { + // submenu is too far to the right, so we display it on the left side x = rootMenuX - width; + } else { + x = rootMenuX + width; } submenuPosition = [x, y]; From d964704752b936da57a5533be6b92f40014958f1 Mon Sep 17 00:00:00 2001 From: Moritz Bischof Date: Mon, 3 May 2021 19:28:29 +0200 Subject: [PATCH 5/9] add ContextMenuInner to index.js and ContextMenu/index.js --- src/ContextMenu/index.js | 1 + src/index.js | 1 + 2 files changed, 2 insertions(+) diff --git a/src/ContextMenu/index.js b/src/ContextMenu/index.js index 3b04911fc7..c7d2e24216 100644 --- a/src/ContextMenu/index.js +++ b/src/ContextMenu/index.js @@ -1,4 +1,5 @@ export { default as ContextMenu } from "./ContextMenu.svelte"; +export { default as ContextMenuInner } from "./ContextMenuInner.svelte"; export { default as ContextMenuDivider } from "./ContextMenuDivider.svelte"; export { default as ContextMenuGroup } from "./ContextMenuGroup.svelte"; export { default as ContextMenuOption } from "./ContextMenuOption.svelte"; diff --git a/src/index.js b/src/index.js index ff4dfa7987..7d8a1d1ce8 100644 --- a/src/index.js +++ b/src/index.js @@ -6,6 +6,7 @@ export { Checkbox, CheckboxSkeleton } from "./Checkbox"; export { ContentSwitcher, Switch } from "./ContentSwitcher"; export { ContextMenu, + ContextMenuInner, ContextMenuDivider, ContextMenuGroup, ContextMenuOption, From 6b95fb6db0bb7db545f102659c3504a3a1f52c42 Mon Sep 17 00:00:00 2001 From: Moritz Bischof Date: Mon, 3 May 2021 19:50:55 +0200 Subject: [PATCH 6/9] add JSDocs --- src/ContextMenu/ContextMenu.svelte | 16 +++++++++++++--- src/ContextMenu/ContextMenuInner.svelte | 16 +++++++++++++--- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/ContextMenu/ContextMenu.svelte b/src/ContextMenu/ContextMenu.svelte index 7c157c8a46..5ff73439d4 100644 --- a/src/ContextMenu/ContextMenu.svelte +++ b/src/ContextMenu/ContextMenu.svelte @@ -4,16 +4,26 @@ /** * Set to `true` to open the menu * Either `x` and `y` must be greater than zero + * @type {boolean} */ export let open = false; - /** Specify the horizontal offset of the menu position */ + /** + * Specify the horizontal offset of the menu position + * @type {number} + */ export let x = 0; - /** Specify the vertical offset of the menu position */ + /** + * Specify the vertical offset of the menu position + * @type {number} + */ export let y = 0; - /** Obtain a reference to the unordered list HTML element */ + /** + * Obtain a reference to the unordered list HTML element + * @type {HTMLUListElement | null} + */ export let ref = null; import { getContext } from "svelte"; diff --git a/src/ContextMenu/ContextMenuInner.svelte b/src/ContextMenu/ContextMenuInner.svelte index 9376100c9a..6a1c95b26f 100644 --- a/src/ContextMenu/ContextMenuInner.svelte +++ b/src/ContextMenu/ContextMenuInner.svelte @@ -2,16 +2,26 @@ /** * Set to `true` to open the menu * Either `x` and `y` must be greater than zero + * @type {boolean} */ export let open = false; - /** Specify the horizontal offset of the menu position */ + /** + * Specify the horizontal offset of the menu position + * @type {number} + */ export let x = 0; - /** Specify the vertical offset of the menu position */ + /** + * Specify the vertical offset of the menu position + * @type {number} + */ export let y = 0; - /** Obtain a reference to the unordered list HTML element */ + /** + * Obtain a reference to the unordered list HTML element + * @type {HTMLUListElement | null} + */ export let ref = null; import { From cfc99c53bf106da2e34aee595ad56ca01fc4d483 Mon Sep 17 00:00:00 2001 From: Moritz Bischof Date: Mon, 3 May 2021 21:22:11 +0200 Subject: [PATCH 7/9] only preventDefault on root context menu --- src/ContextMenu/ContextMenu.svelte | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ContextMenu/ContextMenu.svelte b/src/ContextMenu/ContextMenu.svelte index 5ff73439d4..bb3709da44 100644 --- a/src/ContextMenu/ContextMenu.svelte +++ b/src/ContextMenu/ContextMenu.svelte @@ -41,6 +41,7 @@ function onContextMenu(e) { if (level > 1) return; + e.preventDefault(); open = true; x = e.x; y = e.y; @@ -48,7 +49,7 @@ @@ -14,4 +14,8 @@ In the examples, right click anywhere within the iframe. ### Radio groups - \ No newline at end of file + + +### On specific element + + diff --git a/docs/src/pages/framed/ContextMenu/ContextMenuInner.svelte b/docs/src/pages/framed/ContextMenu/ContextMenuInner.svelte new file mode 100644 index 0000000000..c070d0c5ba --- /dev/null +++ b/docs/src/pages/framed/ContextMenu/ContextMenuInner.svelte @@ -0,0 +1,59 @@ + + + + +
+ + + + + + + + + From ee10776a7e922d4a1a00a950968c0440222e1dd0 Mon Sep 17 00:00:00 2001 From: Moritz Bischof Date: Mon, 3 May 2021 21:26:36 +0200 Subject: [PATCH 9/9] run `yarn prepack` --- COMPONENT_INDEX.md | 38 +++++++++++--- docs/src/COMPONENT_API.json | 69 +++++++++++++++++++++---- preprocess/api.json | 3 ++ types/ContextMenu/ContextMenu.d.ts | 12 ++--- types/ContextMenu/ContextMenuInner.d.ts | 41 +++++++++++++++ types/index.d.ts | 1 + 6 files changed, 137 insertions(+), 27 deletions(-) create mode 100644 types/ContextMenu/ContextMenuInner.d.ts diff --git a/COMPONENT_INDEX.md b/COMPONENT_INDEX.md index e565a00a3b..49b4e96840 100644 --- a/COMPONENT_INDEX.md +++ b/COMPONENT_INDEX.md @@ -1,6 +1,6 @@ # Component Index -> 167 components exported from carbon-components-svelte@0.33.0. +> 168 components exported from carbon-components-svelte@0.33.0. ## Components @@ -27,6 +27,7 @@ - [`ContextMenu`](#contextmenu) - [`ContextMenuDivider`](#contextmenudivider) - [`ContextMenuGroup`](#contextmenugroup) +- [`ContextMenuInner`](#contextmenuinner) - [`ContextMenuOption`](#contextmenuoption) - [`ContextMenuRadioGroup`](#contextmenuradiogroup) - [`Copy`](#copy) @@ -737,7 +738,7 @@ None. | Prop name | Kind | Reactive | Type | Default value | Description | | :-------- | :--------------- | :------- | :---------------------------------------- | ------------------ | -------------------------------------------------------------------------------- | -| ref | let | Yes | null | HTMLUListElement | null | Obtain a reference to the unordered list HTML element | +| ref | let | Yes | HTMLUListElement | null | null | Obtain a reference to the unordered list HTML element | | y | let | Yes | number | 0 | Specify the vertical offset of the menu position | | x | let | Yes | number | 0 | Specify the horizontal offset of the menu position | | open | let | Yes | boolean | false | Set to `true` to open the menu
Either `x` and `y` must be greater than zero | @@ -750,12 +751,7 @@ None. ### Events -| Event name | Type | Detail | -| :--------- | :--------- | :----- | -| click | forwarded | -- | -| keydown | forwarded | -- | -| open | dispatched | -- | -| close | dispatched | -- | +None. ## `ContextMenuDivider` @@ -790,6 +786,32 @@ None. None. +## `ContextMenuInner` + +### Props + +| Prop name | Kind | Reactive | Type | Default value | Description | +| :-------- | :--------------- | :------- | :---------------------------------------- | ------------------ | -------------------------------------------------------------------------------- | +| ref | let | Yes | null | HTMLUListElement | null | Obtain a reference to the unordered list HTML element | +| y | let | Yes | number | 0 | Specify the vertical offset of the menu position | +| x | let | Yes | number | 0 | Specify the horizontal offset of the menu position | +| open | let | Yes | boolean | false | Set to `true` to open the menu
Either `x` and `y` must be greater than zero | + +### Slots + +| Slot name | Default | Props | Fallback | +| :-------- | :------ | :---- | :------- | +| -- | Yes | -- | -- | + +### Events + +| Event name | Type | Detail | +| :--------- | :--------- | :----- | +| click | forwarded | -- | +| keydown | forwarded | -- | +| open | dispatched | -- | +| close | dispatched | -- | + ## `ContextMenuOption` ### Props diff --git a/docs/src/COMPONENT_API.json b/docs/src/COMPONENT_API.json index bccb25f41d..247553689e 100644 --- a/docs/src/COMPONENT_API.json +++ b/docs/src/COMPONENT_API.json @@ -1,5 +1,5 @@ { - "total": 167, + "total": 168, "components": [ { "moduleName": "Accordion", @@ -1609,7 +1609,7 @@ "name": "ref", "kind": "let", "description": "Obtain a reference to the unordered list HTML element", - "type": "null | HTMLUListElement", + "type": "HTMLUListElement | null", "value": "null", "isFunction": false, "constant": false, @@ -1617,14 +1617,8 @@ } ], "slots": [{ "name": "__default__", "default": true, "slot_props": "{}" }], - "events": [ - { "type": "forwarded", "name": "click", "element": "ul" }, - { "type": "forwarded", "name": "keydown", "element": "ul" }, - { "type": "dispatched", "name": "open" }, - { "type": "dispatched", "name": "close" } - ], - "typedefs": [], - "rest_props": { "type": "Element", "name": "ul" } + "events": [], + "typedefs": [] }, { "moduleName": "ContextMenuDivider", @@ -1662,6 +1656,61 @@ "events": [], "typedefs": [] }, + { + "moduleName": "ContextMenuInner", + "filePath": "src/ContextMenu/ContextMenuInner.svelte", + "props": [ + { + "name": "open", + "kind": "let", + "description": "Set to `true` to open the menu\nEither `x` and `y` must be greater than zero", + "type": "boolean", + "value": "false", + "isFunction": false, + "constant": false, + "reactive": true + }, + { + "name": "x", + "kind": "let", + "description": "Specify the horizontal offset of the menu position", + "type": "number", + "value": "0", + "isFunction": false, + "constant": false, + "reactive": true + }, + { + "name": "y", + "kind": "let", + "description": "Specify the vertical offset of the menu position", + "type": "number", + "value": "0", + "isFunction": false, + "constant": false, + "reactive": true + }, + { + "name": "ref", + "kind": "let", + "description": "Obtain a reference to the unordered list HTML element", + "type": "null | HTMLUListElement", + "value": "null", + "isFunction": false, + "constant": false, + "reactive": true + } + ], + "slots": [{ "name": "__default__", "default": true, "slot_props": "{}" }], + "events": [ + { "type": "forwarded", "name": "click", "element": "ul" }, + { "type": "forwarded", "name": "keydown", "element": "ul" }, + { "type": "dispatched", "name": "open" }, + { "type": "dispatched", "name": "close" } + ], + "typedefs": [], + "rest_props": { "type": "Element", "name": "ul" } + }, { "moduleName": "ContextMenuOption", "filePath": "src/ContextMenu/ContextMenuOption.svelte", diff --git a/preprocess/api.json b/preprocess/api.json index a25317b5bd..6e696de7b4 100644 --- a/preprocess/api.json +++ b/preprocess/api.json @@ -76,6 +76,9 @@ "ContextMenuGroup": { "path": "carbon-components-svelte/src/ContextMenu/ContextMenuGroup.svelte" }, + "ContextMenuInner": { + "path": "carbon-components-svelte/src/ContextMenu/ContextMenuInner.svelte" + }, "ContextMenuOption": { "path": "carbon-components-svelte/src/ContextMenu/ContextMenuOption.svelte" }, diff --git a/types/ContextMenu/ContextMenu.d.ts b/types/ContextMenu/ContextMenu.d.ts index 17a4b5479f..f174d6310e 100644 --- a/types/ContextMenu/ContextMenu.d.ts +++ b/types/ContextMenu/ContextMenu.d.ts @@ -1,8 +1,7 @@ /// import { SvelteComponentTyped } from "svelte"; -export interface ContextMenuProps - extends svelte.JSX.HTMLAttributes { +export interface ContextMenuProps { /** * Set to `true` to open the menu * Either `x` and `y` must be greater than zero @@ -26,16 +25,11 @@ export interface ContextMenuProps * Obtain a reference to the unordered list HTML element * @default null */ - ref?: null | HTMLUListElement; + ref?: HTMLUListElement | null; } export default class ContextMenu extends SvelteComponentTyped< ContextMenuProps, - { - click: WindowEventMap["click"]; - keydown: WindowEventMap["keydown"]; - open: CustomEvent; - close: CustomEvent; - }, + {}, { default: {} } > {} diff --git a/types/ContextMenu/ContextMenuInner.d.ts b/types/ContextMenu/ContextMenuInner.d.ts new file mode 100644 index 0000000000..3db8a08e02 --- /dev/null +++ b/types/ContextMenu/ContextMenuInner.d.ts @@ -0,0 +1,41 @@ +/// +import { SvelteComponentTyped } from "svelte"; + +export interface ContextMenuInnerProps + extends svelte.JSX.HTMLAttributes { + /** + * Set to `true` to open the menu + * Either `x` and `y` must be greater than zero + * @default false + */ + open?: boolean; + + /** + * Specify the horizontal offset of the menu position + * @default 0 + */ + x?: number; + + /** + * Specify the vertical offset of the menu position + * @default 0 + */ + y?: number; + + /** + * Obtain a reference to the unordered list HTML element + * @default null + */ + ref?: null | HTMLUListElement; +} + +export default class ContextMenuInner extends SvelteComponentTyped< + ContextMenuInnerProps, + { + click: WindowEventMap["click"]; + keydown: WindowEventMap["keydown"]; + open: CustomEvent; + close: CustomEvent; + }, + { default: {} } +> {} diff --git a/types/index.d.ts b/types/index.d.ts index f1446a1b04..73eec917a1 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -13,6 +13,7 @@ export { default as CheckboxSkeleton } from "./Checkbox/CheckboxSkeleton"; export { default as ContentSwitcher } from "./ContentSwitcher/ContentSwitcher"; export { default as Switch } from "./ContentSwitcher/Switch"; export { default as ContextMenu } from "./ContextMenu/ContextMenu"; +export { default as ContextMenuInner } from "./ContextMenu/ContextMenuInner"; export { default as ContextMenuDivider } from "./ContextMenu/ContextMenuDivider"; export { default as ContextMenuGroup } from "./ContextMenu/ContextMenuGroup"; export { default as ContextMenuOption } from "./ContextMenu/ContextMenuOption";